Task or work item involved in memory reclaim trying to flush a non-WQ_MEM_RECLAIM workqueue or one of its work items can lead to deadlock. Trigger WARN_ONCE() if such conditions are detected.
Showing the changes to kernel/workqueue.c (1 of 1 file). Show all
Unified · Split
| @@ -2316,6 +2316,37 @@ repeat: · 2315 unmodified lines | ||
| 2316 | 2316 | goto repeat; |
| 2317 | 2317 | } |
| 2318 | 2318 | |
| 2319 | +/** | |
| 2320 | + * check_flush_dependency - check for flush dependency sanity | |
| 2321 | + * @target_wq: workqueue being flushed | |
| 2322 | + * @target_work: work item being flushed (NULL for workqueue flushes) | |
| 2323 | + * | |
| 2324 | + * %current is trying to flush the whole @target_wq or @target_work on it. | |
| 2325 | + * If @target_wq doesn't have %WQ_MEM_RECLAIM, verify that %current is not | |
| 2326 | + * reclaiming memory or running on a workqueue which doesn't have | |
| 2327 | + * %WQ_MEM_RECLAIM as that can break forward-progress guarantee leading to | |
| 2328 | + * a deadlock. | |
| 2329 | + */ | |
| 2330 | +static void check_flush_dependency(struct workqueue_struct *target_wq, | |
| 2331 | + struct work_struct *target_work) | |
| 2332 | +{ | |
| 2333 | + work_func_t target_func = target_work ? target_work->func : NULL; | |
| 2334 | + struct worker *worker; | |
| 2335 | + | |
| 2336 | + if (target_wq->flags & WQ_MEM_RECLAIM) | |
| 2337 | + return; | |
| 2338 | + | |
| 2339 | + worker = current_wq_worker(); | |
| 2340 | + | |
| 2341 | + WARN_ONCE(current->flags & PF_MEMALLOC, | |
| 2342 | + "workqueue: PF_MEMALLOC task %d(%s) is flushing !WQ_MEM_RECLAIM %s:%pf", | |
| 2343 | + current->pid, current->comm, target_wq->name, target_func); | |
| 2344 | + WARN_ONCE(worker && (worker->current_pwq->wq->flags & WQ_MEM_RECLAIM), | |
| 2345 | + "workqueue: WQ_MEM_RECLAIM %s:%pf is flushing !WQ_MEM_RECLAIM %s:%pf", | |
| 2346 | + worker->current_pwq->wq->name, worker->current_func, | |
| 2347 | + target_wq->name, target_func); | |
| 2348 | +} | |
| 2349 | + | |
| 2319 | 2350 | struct wq_barrier { |
| 2320 | 2351 | struct work_struct work; |
| 2321 | 2352 | struct completion done; |
| @@ -2525,6 +2556,8 @@ void flush_workqueue(struct workqueue_struct *wq) · 203 unmodified lines | ||
| 2525 | 2556 | list_add_tail(&this_flusher.list, &wq->flusher_overflow); |
| 2526 | 2557 | } |
| 2527 | 2558 | |
| 2559 | + check_flush_dependency(wq, NULL); | |
| 2560 | + | |
| 2528 | 2561 | mutex_unlock(&wq->mutex); |
| 2529 | 2562 | |
| 2530 | 2563 | wait_for_completion(&this_flusher.done); |
| @@ -2697,6 +2730,8 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr) · 166 unmodified lines | ||
| 2697 | 2730 | pwq = worker->current_pwq; |
| 2698 | 2731 | } |
| 2699 | 2732 | |
| 2733 | + check_flush_dependency(pwq->wq, work); | |
| 2734 | + | |
| 2700 | 2735 | insert_wq_barrier(pwq, barr, work, worker); |
| 2701 | 2736 | spin_unlock_irq(&pool->lock); |
| 2702 | 2737 | |