Skip to content

Commit d812796

Browse files
Lai Jiangshanhtejun
authored andcommitted
workqueue: Assign a color to barrier work items
There was no strong reason to or not to flush barrier work items in flush_workqueue(). And we have to make barrier work items not participate in nr_active so we had been using WORK_NO_COLOR for them which also makes them can't be flushed by flush_workqueue(). And the users of flush_workqueue() often do not intend to wait barrier work items issued by flush_work(). That made the choice sound perfect. But barrier work items have reference to internal structure (pool_workqueue) and the worker thread[s] is/are still busy for the workqueue user when the barrrier work items are not done. So it is reasonable to make flush_workqueue() also watch for flush_work() to make it more robust. And a problem[1] reported by Li Zhe shows that we need such robustness. The warning logs are listed below: WARNING: CPU: 0 PID: 19336 at kernel/workqueue.c:4430 destroy_workqueue+0x11a/0x2f0 ***** destroy_workqueue: test_workqueue9 has the following busy pwq pwq 4: cpus=2 node=0 flags=0x0 nice=0 active=0/1 refcnt=2 in-flight: 5658:wq_barrier_func Showing busy workqueues and worker pools: ***** It shows that even after drain_workqueue() returns, the barrier work item is still in flight and the pwq (and a worker) is still busy on it. The problem is caused by flush_workqueue() not watching flush_work(): Thread A Worker /* normal work item with linked */ process_scheduled_works() destroy_workqueue() process_one_work() drain_workqueue() /* run normal work item */ /-- pwq_dec_nr_in_flight() flush_workqueue() <---/ /* the last normal work item is done */ sanity_check process_one_work() /-- raw_spin_unlock_irq(&pool->lock) raw_spin_lock_irq(&pool->lock) <-/ /* maybe preempt */ *WARNING* wq_barrier_func() /* maybe preempt by cond_resched() */ Thread A can get the pool lock after the Worker unlocks the pool lock before running wq_barrier_func(). And if there is any preemption happen around wq_barrier_func(), destroy_workqueue()'s sanity check is more likely to get the lock and catch it. (Note: preemption is not necessary to cause the bug, the unlocking is enough to possibly trigger the WARNING.) A simple solution might be just executing all linked barrier work items once without releasing pool lock after the head work item's pwq_dec_nr_in_flight(). But this solution has two problems: 1) the head work item might also be barrier work item when the user-queued work item is cancelled. For example: thread 1: thread 2: queue_work(wq, &my_work) flush_work(&my_work) cancel_work_sync(&my_work); /* Neiter my_work nor the barrier work is scheduled. */ destroy_workqueue(wq); /* This is an easier way to catch the WARNING. */ 2) there might be too much linked barrier work items and running them all once without releasing pool lock just causes trouble. The only solution is to make flush_workqueue() aslo watch barrier work items. So we have to assign a color to these barrier work items which is the color of the head (user-queued) work item. Assigning a color doesn't cause any problem in ative management, because the prvious patch made barrier work items not participate in nr_active via WORK_STRUCT_INACTIVE rather than reliance on the (old) WORK_NO_COLOR. [1]: https://lore.kernel.org/lkml/[email protected]/ Reported-by: Li Zhe <[email protected]> Signed-off-by: Lai Jiangshan <[email protected]> Signed-off-by: Tejun Heo <[email protected]>
1 parent 018f3a1 commit d812796

File tree

2 files changed

+14
-9
lines changed

2 files changed

+14
-9
lines changed

kernel/workqueue.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,10 +1197,6 @@ static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, unsigned long work_
11971197
}
11981198
}
11991199

1200-
/* uncolored work items don't participate in flushing */
1201-
if (color == WORK_NO_COLOR)
1202-
goto out_put;
1203-
12041200
pwq->nr_in_flight[color]--;
12051201

12061202
/* is flush in progress and are we at the flushing tip? */
@@ -1307,7 +1303,7 @@ static int try_to_grab_pending(struct work_struct *work, bool is_dwork,
13071303
* canceled (see the comments in insert_wq_barrier()).
13081304
*
13091305
* An inactive work item cannot be grabbed directly because
1310-
* it might have linked NO_COLOR work items which, if left
1306+
* it might have linked barrier work items which, if left
13111307
* on the inactive_works list, will confuse pwq->nr_active
13121308
* management later on and cause stall. Make sure the work
13131309
* item is activated before grabbing.
@@ -2234,6 +2230,7 @@ __acquires(&pool->lock)
22342230
worker->current_func = work->func;
22352231
worker->current_pwq = pwq;
22362232
work_data = *work_data_bits(work);
2233+
worker->current_color = get_work_color(work_data);
22372234

22382235
/*
22392236
* Record wq name for cmdline and debug reporting, may get
@@ -2339,6 +2336,7 @@ __acquires(&pool->lock)
23392336
worker->current_work = NULL;
23402337
worker->current_func = NULL;
23412338
worker->current_pwq = NULL;
2339+
worker->current_color = INT_MAX;
23422340
pwq_dec_nr_in_flight(pwq, work_data);
23432341
}
23442342

@@ -2682,7 +2680,8 @@ static void insert_wq_barrier(struct pool_workqueue *pwq,
26822680
struct wq_barrier *barr,
26832681
struct work_struct *target, struct worker *worker)
26842682
{
2685-
unsigned int work_flags = work_color_to_flags(WORK_NO_COLOR);
2683+
unsigned int work_flags = 0;
2684+
unsigned int work_color;
26862685
struct list_head *head;
26872686

26882687
/*
@@ -2705,17 +2704,22 @@ static void insert_wq_barrier(struct pool_workqueue *pwq,
27052704
* If @target is currently being executed, schedule the
27062705
* barrier to the worker; otherwise, put it after @target.
27072706
*/
2708-
if (worker)
2707+
if (worker) {
27092708
head = worker->scheduled.next;
2710-
else {
2709+
work_color = worker->current_color;
2710+
} else {
27112711
unsigned long *bits = work_data_bits(target);
27122712

27132713
head = target->entry.next;
27142714
/* there can already be other linked works, inherit and set */
27152715
work_flags |= *bits & WORK_STRUCT_LINKED;
2716+
work_color = get_work_color(*bits);
27162717
__set_bit(WORK_STRUCT_LINKED_BIT, bits);
27172718
}
27182719

2720+
pwq->nr_in_flight[work_color]++;
2721+
work_flags |= work_color_to_flags(work_color);
2722+
27192723
debug_work_activate(&barr->work);
27202724
insert_work(pwq, &barr->work, head, work_flags);
27212725
}

kernel/workqueue_internal.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ struct worker {
3030

3131
struct work_struct *current_work; /* L: work being processed */
3232
work_func_t current_func; /* L: current_work's fn */
33-
struct pool_workqueue *current_pwq; /* L: current_work's pwq */
33+
struct pool_workqueue *current_pwq; /* L: current_work's pwq */
34+
unsigned int current_color; /* L: current_work's color */
3435
struct list_head scheduled; /* L: scheduled works */
3536

3637
/* 64 bytes boundary on 64bit, 32 on 32bit */

0 commit comments

Comments
 (0)