Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens when kernel delayed_work is rescheduled

Tags:

linux-kernel

I am using the kernel shared workqueue, and I have a delayed_work struct that I want to reschedule to run immediately.

Will the following code guarantee that the delayed_work will run as soon as possible?

cancel_delayed_work(work);
schedule_delayed_work(work, 0);

What happens in a situation where the work is already running? cancel_delayed_work will return 0, but I'm not sure what schedule_delayed_work will do if the work is currently running or is unscheduled.

like image 257
Xzhsh Avatar asked Feb 19 '13 19:02

Xzhsh


1 Answers

Well, you know what they say about necessity being the mother of all invention (or research in this case). I really needed this answer and got it by digging through kernel/workqueue.c. Although the answer is mostly contained in the doc comments combined with Documentation/workqueue.txt, it isn't clearly spelled out without reading the whole spec on the Concurrency Managed Workqueue (cmwq) subsystem and even then, some of the information is out of date!

Short Answer

Will [your code] guarantee that the delayed_work will run as soon as possible?

Yes (with the below caveat)

What happens in a situation where the work is already running?

It will run at some point after the currently running delayed_work function exits and on the same CPU as the last one, although any other work already queued on that workqueue (or delayed work that is due) will be run first. This is presuming that you have not re-initialized your delayed_work or work_struct object and that you have not changed the work->function pointer.

Long Answer

So first off, struct delayed_work uses pseudo-inheritance to derive from struct work_struct by embedding a struct work_struct as its first member. This subsystem uses some amazing atomic bit-frigging to have some serious concurrency. A work_struct is "owned" when it's data field has the WORK_STRUCT_PENDING bit set. When a worker executes your work, it releases ownership and records the last work pool via the private set_work_pool_and_clear_pending() function -- this is the last time the API modifies the work_struct object (until you re-schedule it, of course). Calling cancel_delayed_work() does the exact same thing.

So if you call cancel_delayed_work() when your work function has already begun executing, it returns false (as advertised) since it is no longer owned by anybody, even though it may still be running. However, when you try to re-add it with schedule_delayed_work(), it will examine the work to discover the last pool_workqueue and then find out if any of that pool_workqueue's workers are currently running your work. If they are (and you haven't changed the work->func pointer), it simply appends the work to the queue of that pool_workqueue and that's how it avoids re-entrancy! Otherwise, it will queue it on the pool for the current CPU. (The reason for the work->func pointer check is to allow for reuse of the work_struct object.)

Note however that simply calling schedule_delayed_work() without cancelling it first will result in no change if the work is still queued, so you definitely must cancel it first.

EDIT: Oh yeah, if you are confused by the discussion in Documentation/workqueue.txt about WQ_NON_REENTRANT, ignore it. This flag is deprecated and ignored and all workqueues are now non-reetrant.

like image 93
Daniel Santos Avatar answered Nov 21 '22 22:11

Daniel Santos