Re: [PATCH wq/for-7.4-fixes] workqueue: Fix drain_workqueue() missing work items chained across pwqs
From: Breno Leitao
Date: Mon Nov 02 2026 - 06:04:21 EST
On Fri, Oct 30, 2026 at 06:42:10AM -1000, Tejun Heo wrote:
> drain_workqueue() flushes the workqueue, then checks each pwq under its
> pool's lock and returns once a pass finds every pwq empty. Only chained
> queueing is allowed meanwhile, and a chained work item can land on a pwq
> other than the one the queueing work item is executing on. The pass misses
> it when:
>
> 1. The pass finds pwq A empty.
> 2. A work item executing on pwq B queues a work item on pwq A.
> 3. The work item on pwq B finishes.
> 4. The pass finds pwq B empty.
>
> drain_workqueue() returns with the work item on pwq A pending, and
> destroy_workqueue() can then trip WARN_ON(pwq_busy()) and leak the
> workqueue.
Very nice finding, basically drain_workqueue() could return while the
tail of a chain was still pending.
> Add wq->drain_seq, which counts chained queueings while the workqueue is
> draining. drain_workqueue() reads it before each flush and retries if it
> changed by the end of the check pass. Flushing once more after an empty pass
> is not enough, as a longer chain is missed the same way.
I am curious about a work function that keeps re-queueing itself
forever. That is a bug in the caller for sure, but the current code can
escape it by luck through this race (and then leak the workqueue), while
with this patch drain_workqueue() will loop forever. Is my reading
correct here?
> Verified with reproducers of both shapes: self-requeueing work items on an
> unbound workqueue and chained queue_work_on() of another work item on a
> per-cpu workqueue.
>
> Fixes: c8efcc258946 ("workqueue: allow chained queueing during destruction")
> Cc: stable@xxxxxxxxxxxxxxx # v2.6.38+
> Signed-off-by: Tejun Heo <tj@xxxxxxxxxx>
Reviewed-by: Breno Leitao <leitao@xxxxxxxxxx>