From 47c68f925062a6013235d06add85a59f7d91e0fb Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Thu, 8 Oct 2026 02:48:45 +0000 Subject: [PATCH] io_uring/io-wq: no new workers once the exit has begun io_wq_exit_start() sets IO_WQ_BIT_EXIT, but worker creation task_work queued before that stays queued until io_wq_exit_workers() cancels it. In between the task runs its task_work while canceling requests, and a worker can still get created. That's a problem for exec. begin_new_exec() calls io_uring_task_cancel() after de_thread(), when it's supposedly the only thread left, and io_wq_exit_workers() only waits for ->worker_done, which a worker completes before do_exit(). So the exiting worker is still in the thread group, using the mm, files and sighand, while exec replaces them. Easy to hit with a signal pending when create_io_thread() fails and the retry gets queued right before execve(). Cancel the queued creation task_work as soon as the exit bit is set. Pending work that isn't otherwise canceled now ends with -ECANCELED when the io-wq is destroyed, rather than depending on whether the task happened to run its task_work first. Fixes: 685fe7feedb9 ("io-wq: eliminate the need for a manager thread") Signed-off-by: Josef Bacik [axboe: the barrier is already upstream, see 0e5663856b69, only the cancel is new] Signed-off-by: Jens Axboe --- io_uring/io-wq.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index e93cfa82b850..28533174d0da 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1420,6 +1420,12 @@ void io_wq_exit_start(struct io_wq *wq) set_bit(IO_WQ_BIT_EXIT, &wq->state); /* Pairs with task_work_add() in io_queue_worker_create(). */ smp_mb__after_atomic(); + /* + * No more workers from here on. The caller is going to run its task + * work, and on exec a thread that is created now would still be + * around, exiting, by the time that the caller must be on its own. + */ + io_wq_cancel_tw_create(wq); } static void io_wq_cancel_tw_create(struct io_wq *wq)