• Oleg Nesterov's avatar
    implement flush_work() · b89deed3
    Oleg Nesterov authored
    A basic problem with flush_scheduled_work() is that it blocks behind _all_
    presently-queued works, rather than just the work whcih the caller wants to
    flush.  If the caller holds some lock, and if one of the queued work happens
    to want that lock as well then accidental deadlocks can occur.
    
    One example of this is the phy layer: it wants to flush work while holding
    rtnl_lock().  But if a linkwatch event happens to be queued, the phy code will
    deadlock because the linkwatch callback function takes rtnl_lock.
    
    So we implement a new function which will flush a *single* work - just the one
    which the caller wants to free up.  Thus we avoid the accidental deadlocks
    which can arise from unrelated subsystems' callbacks taking shared locks.
    
    flush_work() non-blockingly dequeues the work_struct which we want to kill,
    then it waits for its handler to complete on all CPUs.
    
    Add ->current_work to the "struct cpu_workqueue_struct", it points to
    currently running "struct work_struct". When flush_work(work) detects
    ->current_work == work, it inserts a barrier at the _head_ of ->worklist
    (and thus right _after_ that work) and waits for completition. This means
    that the next work fired on that CPU will be this barrier, or another
    barrier queued by concurrent flush_work(), so the caller of flush_work()
    will be woken before any "regular" work has a chance to run.
    
    When wait_on_work() unlocks workqueue_mutex (or whatever we choose to protect
    against CPU hotplug), CPU may go away. But in that case take_over_work() will
    move a barrier we queued to another CPU, it will be fired sometime, and
    wait_on_work() will be woken.
    
    Actually, we are doing cleanup_workqueue_thread()->kthread_stop() before
    take_over_work(), so cwq->thread should complete its ->worklist (and thus
    the barrier), because currently we don't check kthread_should_stop() in
    run_workqueue(). But even if we did, everything should be ok.
    
    [akpm@osdl.org: cleanup]
    [akpm@osdl.org: add flush_work_keventd() wrapper]
    Signed-off-by: default avatarOleg Nesterov <oleg@tv-sign.ru>
    Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
    Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
    b89deed3
workqueue.c 22.6 KB