aboutsummaryrefslogtreecommitdiff
path: root/kernel/workqueue.c
diff options
context:
space:
mode:
authorThomas Tuttle <ttuttle@chromium.org>2011-09-14 16:22:28 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2011-10-03 11:40:31 -0700
commitd5b1a08d0d0a73c716766275eb0c5648e143ca85 (patch)
tree2f62c1aa34c268658ca29b6776fdb73b71fcc7e9 /kernel/workqueue.c
parentc780713f786ee22532839502ed35f1bd1cb0fd1d (diff)
workqueue: lock cwq access in drain_workqueue
commit fa2563e41c3d6d6e8af437643981ed28ae0cb56d upstream. Take cwq->gcwq->lock to avoid racing between drain_workqueue checking to make sure the workqueues are empty and cwq_dec_nr_in_flight decrementing and then incrementing nr_active when it activates a delayed work. We discovered this when a corner case in one of our drivers resulted in us trying to destroy a workqueue in which the remaining work would always requeue itself again in the same workqueue. We would hit this race condition and trip the BUG_ON on workqueue.c:3080. Signed-off-by: Thomas Tuttle <ttuttle@chromium.org> Acked-by: Tejun Heo <tj@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'kernel/workqueue.c')
-rw-r--r--kernel/workqueue.c7
1 files changed, 6 insertions, 1 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 0400553f0d0..aec02b6a1c4 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -3026,8 +3026,13 @@ reflush:
for_each_cwq_cpu(cpu, wq) {
struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
+ bool drained;
- if (!cwq->nr_active && list_empty(&cwq->delayed_works))
+ spin_lock_irq(&cwq->gcwq->lock);
+ drained = !cwq->nr_active && list_empty(&cwq->delayed_works);
+ spin_unlock_irq(&cwq->gcwq->lock);
+
+ if (drained)
continue;
if (++flush_cnt == 10 ||