* FAILED: patch "[PATCH] io_uring/io-wq: fix worker accounting when canceling creation" failed to apply to 6.6-stable tree
@ 2026-08-24 12:18 gregkh
2026-08-25 16:52 ` [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks Vishnu Razdan via B4 Relay
2026-08-25 19:48 ` [PATCH 6.6.y 1/3] io_uring/io-wq: add io_worker.acct pointer Sasha Levin
0 siblings, 2 replies; 8+ messages in thread
From: gregkh @ 2026-08-24 12:18 UTC (permalink / raw)
To: vrazdan, axboe, krisman; +Cc: stable
The patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 297b5ccea4acacaa47c150f043bce695202afbf1
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026082423-fineness-promotion-9ac5@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 297b5ccea4acacaa47c150f043bce695202afbf1 Mon Sep 17 00:00:00 2001
From: Vishnu Razdan <vrazdan@openai.com>
Date: Tue, 11 Aug 2026 00:01:27 -0700
Subject: [PATCH] io_uring/io-wq: fix worker accounting when canceling creation
callbacks
create_worker_cb() reserves an io-wq worker slot only after its
task-work callback runs. If the callback is canceled before then,
io_worker_cancel_cb() still decrements acct->nr_workers. When an
existing worker retires with its creation callback pending, that
worker has already decremented the same account's worker count.
The resulting undercount permits worker creation beyond the account's
configured limit. On an AST2600 OpenBMC system, an unchanged sensor
daemon reached 4,291 threads with the original kernel. With an
equivalent downstream fix, 25 passive samples under its normal
workload showed 6-9 threads.
Decrement nr_workers only when the canceled callback is not
create_worker_cb(). Continuation callbacks still release their reserved
slot, and both callback types retain the existing running-count,
reference-count, and create-state cleanup.
Fixes: 1d5f5ea7cb7d ("io-wq: remove worker to owner tw dependency")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Vishnu Razdan <vrazdan@openai.com>
Reviewed-by: Gabriel Krisman Bertazi <krisman@suse.de>
Link: https://patch.msgid.link/20260811-vrazdan-io-wq-b4-submit-v1-1-719ced16c921@openai.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c
index be8d75731d24..2ca223e47d41 100644
--- a/io_uring/io-wq.c
+++ b/io_uring/io-wq.c
@@ -212,9 +212,12 @@ static void io_worker_cancel_cb(struct io_worker *worker)
struct io_wq *wq = worker->wq;
atomic_dec(&acct->nr_running);
- raw_spin_lock(&acct->workers_lock);
- acct->nr_workers--;
- raw_spin_unlock(&acct->workers_lock);
+ /* create_worker_cb() has not reserved a worker slot yet. */
+ if (worker->create_work.func != create_worker_cb) {
+ raw_spin_lock(&acct->workers_lock);
+ acct->nr_workers--;
+ raw_spin_unlock(&acct->workers_lock);
+ }
io_worker_ref_put(wq);
clear_bit_unlock(0, &worker->create_state);
io_worker_release(worker);
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks 2026-08-24 12:18 FAILED: patch "[PATCH] io_uring/io-wq: fix worker accounting when canceling creation" failed to apply to 6.6-stable tree gregkh @ 2026-08-25 16:52 ` Vishnu Razdan via B4 Relay 2026-08-28 1:46 ` Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 1/3] io_uring/io-wq: add io_worker.acct pointer Sasha Levin 1 sibling, 1 reply; 8+ messages in thread From: Vishnu Razdan via B4 Relay @ 2026-08-25 16:52 UTC (permalink / raw) To: stable; +Cc: asml.silence, vrazdan, krisman, axboe, io-uring, gregkh From: Vishnu Razdan <vrazdan@openai.com> commit 297b5ccea4acacaa47c150f043bce695202afbf1 upstream. create_worker_cb() reserves an io-wq worker slot only after its task-work callback runs. If the callback is canceled before then, io_worker_cancel_cb() still decrements acct->nr_workers. When an existing worker retires with its creation callback pending, that worker has already decremented the same account's worker count. The resulting undercount permits worker creation beyond the account's configured limit. On an AST2600 OpenBMC system, an unchanged sensor daemon reached 4,291 threads with the original kernel. With an equivalent downstream fix, 25 passive samples under its normal workload showed 6-9 threads. Decrement nr_workers only when the canceled callback is not create_worker_cb(). Continuation callbacks still release their reserved slot, and both callback types retain the existing running-count, reference-count, and create-state cleanup. [ Backport: retain the existing wq->lock protecting worker accounting. ] Fixes: 1d5f5ea7cb7d ("io-wq: remove worker to owner tw dependency") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Reviewed-by: Gabriel Krisman Bertazi <krisman@suse.de> Link: https://patch.msgid.link/20260811-vrazdan-io-wq-b4-submit-v1-1-719ced16c921@openai.com Signed-off-by: Vishnu Razdan <vrazdan@openai.com> --- Stable backport for linux-6.6.y. --- io_uring/io-wq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 37725c5031b4c2..a20be1a43aafcf 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -193,9 +193,12 @@ static void io_worker_cancel_cb(struct io_worker *worker) struct io_wq *wq = worker->wq; atomic_dec(&acct->nr_running); - raw_spin_lock(&wq->lock); - acct->nr_workers--; - raw_spin_unlock(&wq->lock); + /* create_worker_cb() has not reserved a worker slot yet. */ + if (worker->create_work.func != create_worker_cb) { + raw_spin_lock(&wq->lock); + acct->nr_workers--; + raw_spin_unlock(&wq->lock); + } io_worker_ref_put(wq); clear_bit_unlock(0, &worker->create_state); io_worker_release(worker); --- base-commit: dc6160265ffc795a1832bc1424f58291d152c7bb change-id: 20260825-stable-io-wq-6-6-y-ba2c3fc5c2c7 Best regards, -- Vishnu Razdan <vrazdan@openai.com> ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks 2026-08-25 16:52 ` [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks Vishnu Razdan via B4 Relay @ 2026-08-28 1:46 ` Sasha Levin 2026-08-28 2:02 ` Jens Axboe 0 siblings, 1 reply; 8+ messages in thread From: Sasha Levin @ 2026-08-28 1:46 UTC (permalink / raw) To: stable Cc: Sasha Levin, asml.silence, vrazdan, krisman, axboe, io-uring, gregkh, Vishnu Razdan via B4 Relay > Decrement nr_workers only when the canceled callback is not > create_worker_cb(). Continuation callbacks still release their reserved > slot, and both callback types retain the existing running-count, > reference-count, and create-state cleanup. > > [ Backport: retain the existing wq->lock protecting worker accounting. ] Queued for 6.6, thanks. I also used this patch for 6.12. -- Thanks, Sasha ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks 2026-08-28 1:46 ` Sasha Levin @ 2026-08-28 2:02 ` Jens Axboe 2026-08-28 14:54 ` Sasha Levin 0 siblings, 1 reply; 8+ messages in thread From: Jens Axboe @ 2026-08-28 2:02 UTC (permalink / raw) To: Sasha Levin, stable Cc: asml.silence, vrazdan, krisman, io-uring, gregkh, Vishnu Razdan via B4 Relay On 8/27/26 7:46 PM, Sasha Levin wrote: >> Decrement nr_workers only when the canceled callback is not >> create_worker_cb(). Continuation callbacks still release their reserved >> slot, and both callback types retain the existing running-count, >> reference-count, and create-state cleanup. >> >> [ Backport: retain the existing wq->lock protecting worker accounting. ] > > Queued for 6.6, thanks. > > I also used this patch for 6.12. Thanks for doing this - and I'm assuming the AUTOSEL stuff is both adapting patches so they apply, and pulling in prerequisites? If so, I love it, imho this is exactly what it should be doing. Helps maintainers out a lot. -- Jens Axboe ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks 2026-08-28 2:02 ` Jens Axboe @ 2026-08-28 14:54 ` Sasha Levin 0 siblings, 0 replies; 8+ messages in thread From: Sasha Levin @ 2026-08-28 14:54 UTC (permalink / raw) To: Jens Axboe Cc: stable, asml.silence, vrazdan, krisman, io-uring, gregkh, Vishnu Razdan via B4 Relay On Thu, Aug 27, 2026 at 08:02:34PM -0600, Jens Axboe wrote: >On 8/27/26 7:46 PM, Sasha Levin wrote: >>> Decrement nr_workers only when the canceled callback is not >>> create_worker_cb(). Continuation callbacks still release their reserved >>> slot, and both callback types retain the existing running-count, >>> reference-count, and create-state cleanup. >>> >>> [ Backport: retain the existing wq->lock protecting worker accounting. ] >> >> Queued for 6.6, thanks. >> >> I also used this patch for 6.12. > >Thanks for doing this - and I'm assuming the AUTOSEL stuff is both >adapting patches so they apply, and pulling in prerequisites? If Exactly! It can also do minor adaptations to the prereqs as needed (think cases like tree-wide changes). >so, I love it, imho this is exactly what it should be doing. Helps >maintainers out a lot. Thanks for the feedback! -- Thanks, Sasha ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 6.6.y 1/3] io_uring/io-wq: add io_worker.acct pointer 2026-08-24 12:18 FAILED: patch "[PATCH] io_uring/io-wq: fix worker accounting when canceling creation" failed to apply to 6.6-stable tree gregkh 2026-08-25 16:52 ` [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks Vishnu Razdan via B4 Relay @ 2026-08-25 19:48 ` Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 2/3] io_uring/io-wq: move worker lists to struct io_wq_acct Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 3/3] io_uring/io-wq: fix worker accounting when canceling creation callbacks Sasha Levin 1 sibling, 2 replies; 8+ messages in thread From: Sasha Levin @ 2026-08-25 19:48 UTC (permalink / raw) To: stable; +Cc: Max Kellermann, Jens Axboe, Sasha Levin From: Max Kellermann <max.kellermann@ionos.com> [ Upstream commit 3d3bafd35fb422eb36cfc5709473cef7400588e7 ] This replaces the `IO_WORKER_F_BOUND` flag. All code that checks this flag is not interested in knowing whether this is a "bound" worker; all it does with this flag is determine the `io_wq_acct` pointer. At the cost of an extra pointer field, we can eliminate some fragile pointer arithmetic. In turn, the `create_index` and `index` fields are not needed anymore. Signed-off-by: Max Kellermann <max.kellermann@ionos.com> Link: https://lore.kernel.org/r/20250128133927.3989681-3-max.kellermann@ionos.com Signed-off-by: Jens Axboe <axboe@kernel.dk> Stable-dep-of: 297b5ccea4ac ("io_uring/io-wq: fix worker accounting when canceling creation callbacks") Signed-off-by: Sasha Levin <sashal@kernel.org> --- io_uring/io-wq.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 37725c5031b4c..e99c81453dd53 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -30,7 +30,6 @@ enum { IO_WORKER_F_UP = 0, /* up and active */ IO_WORKER_F_RUNNING = 1, /* account as running */ IO_WORKER_F_FREE = 2, /* worker on free list */ - IO_WORKER_F_BOUND = 3, /* is doing bounded work */ }; enum { @@ -46,12 +45,12 @@ enum { */ struct io_worker { refcount_t ref; - int create_index; unsigned long flags; struct hlist_nulls_node nulls_node; struct list_head all_list; struct task_struct *task; struct io_wq *wq; + struct io_wq_acct *acct; struct io_wq_work *cur_work; struct io_wq_work *next_work; @@ -80,7 +79,6 @@ struct io_worker { struct io_wq_acct { unsigned nr_workers; unsigned max_workers; - int index; atomic_t nr_running; raw_spinlock_t lock; struct io_wq_work_list work_list; @@ -136,7 +134,7 @@ struct io_cb_cancel_data { bool cancel_all; }; -static bool create_io_worker(struct io_wq *wq, int index); +static bool create_io_worker(struct io_wq *wq, struct io_wq_acct *acct); static void io_wq_dec_running(struct io_worker *worker); static bool io_acct_cancel_pending_work(struct io_wq *wq, struct io_wq_acct *acct, @@ -168,7 +166,7 @@ static inline struct io_wq_acct *io_work_get_acct(struct io_wq *wq, static inline struct io_wq_acct *io_wq_get_acct(struct io_worker *worker) { - return io_get_acct(worker->wq, test_bit(IO_WORKER_F_BOUND, &worker->flags)); + return worker->acct; } static void io_worker_ref_put(struct io_wq *wq) @@ -324,7 +322,7 @@ static bool io_wq_create_worker(struct io_wq *wq, struct io_wq_acct *acct) raw_spin_unlock(&wq->lock); atomic_inc(&acct->nr_running); atomic_inc(&wq->worker_refs); - return create_io_worker(wq, acct->index); + return create_io_worker(wq, acct); } static void io_wq_inc_running(struct io_worker *worker) @@ -344,7 +342,7 @@ static void create_worker_cb(struct callback_head *cb) worker = container_of(cb, struct io_worker, create_work); wq = worker->wq; - acct = &wq->acct[worker->create_index]; + acct = worker->acct; raw_spin_lock(&wq->lock); if (acct->nr_workers < acct->max_workers) { @@ -353,7 +351,7 @@ static void create_worker_cb(struct callback_head *cb) } raw_spin_unlock(&wq->lock); if (do_create) { - create_io_worker(wq, worker->create_index); + create_io_worker(wq, acct); } else { atomic_dec(&acct->nr_running); io_worker_ref_put(wq); @@ -385,7 +383,6 @@ static bool io_queue_worker_create(struct io_worker *worker, atomic_inc(&wq->worker_refs); init_task_work(&worker->create_work, func); - worker->create_index = acct->index; if (!task_work_add(wq->task, &worker->create_work, TWA_SIGNAL)) { /* * EXIT may have been set after checking it above, check after @@ -835,9 +832,8 @@ static void io_workqueue_create(struct work_struct *work) kfree(worker); } -static bool create_io_worker(struct io_wq *wq, int index) +static bool create_io_worker(struct io_wq *wq, struct io_wq_acct *acct) { - struct io_wq_acct *acct = &wq->acct[index]; struct io_worker *worker; struct task_struct *tsk; @@ -856,12 +852,10 @@ static bool create_io_worker(struct io_wq *wq, int index) refcount_set(&worker->ref, 1); worker->wq = wq; + worker->acct = acct; raw_spin_lock_init(&worker->lock); init_completion(&worker->ref_done); - if (index == IO_WQ_ACCT_BOUND) - set_bit(IO_WORKER_F_BOUND, &worker->flags); - tsk = create_io_thread(io_wq_worker, worker, NUMA_NO_NODE); if (!IS_ERR(tsk)) { io_init_new_worker(wq, worker, tsk); @@ -1193,7 +1187,6 @@ struct io_wq *io_wq_create(unsigned bounded, struct io_wq_data *data) for (i = 0; i < IO_WQ_ACCT_NR; i++) { struct io_wq_acct *acct = &wq->acct[i]; - acct->index = i; atomic_set(&acct->nr_running, 0); INIT_WQ_LIST(&acct->work_list); raw_spin_lock_init(&acct->lock); -- 2.53.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 6.6.y 2/3] io_uring/io-wq: move worker lists to struct io_wq_acct 2026-08-25 19:48 ` [PATCH 6.6.y 1/3] io_uring/io-wq: add io_worker.acct pointer Sasha Levin @ 2026-08-25 19:48 ` Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 3/3] io_uring/io-wq: fix worker accounting when canceling creation callbacks Sasha Levin 1 sibling, 0 replies; 8+ messages in thread From: Sasha Levin @ 2026-08-25 19:48 UTC (permalink / raw) To: stable; +Cc: Max Kellermann, Jens Axboe, Sasha Levin From: Max Kellermann <max.kellermann@ionos.com> [ Upstream commit 751eedc4b4b79332ecf1a78c0dbeb47d573a8f59 ] Have separate linked lists for bounded and unbounded workers. This way, io_acct_activate_free_worker() sees only workers relevant to it and doesn't need to skip irrelevant ones. This speeds up the linked list traversal (under acct->lock). The `io_wq.lock` field is moved to `io_wq_acct.workers_lock`. It did not actually protect "access to elements below", that is, not all of them; it only protected access to the worker lists. By having two locks instead of one, contention on this lock is reduced. Signed-off-by: Max Kellermann <max.kellermann@ionos.com> Link: https://lore.kernel.org/r/20250128133927.3989681-4-max.kellermann@ionos.com Signed-off-by: Jens Axboe <axboe@kernel.dk> Stable-dep-of: 297b5ccea4ac ("io_uring/io-wq: fix worker accounting when canceling creation callbacks") Signed-off-by: Sasha Levin <sashal@kernel.org> --- io_uring/io-wq.c | 162 ++++++++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 66 deletions(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index e99c81453dd53..775c55e377922 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -77,9 +77,27 @@ struct io_worker { #define IO_WQ_NR_HASH_BUCKETS (1u << IO_WQ_HASH_ORDER) struct io_wq_acct { + /** + * Protects access to the worker lists. + */ + raw_spinlock_t workers_lock; + unsigned nr_workers; unsigned max_workers; atomic_t nr_running; + + /** + * The list of free workers. Protected by #workers_lock + * (write) and RCU (read). + */ + struct hlist_nulls_head free_list; + + /** + * The list of all workers. Protected by #workers_lock + * (write) and RCU (read). + */ + struct list_head all_list; + raw_spinlock_t lock; struct io_wq_work_list work_list; unsigned long flags; @@ -111,12 +129,6 @@ struct io_wq { struct io_wq_acct acct[IO_WQ_ACCT_NR]; - /* lock protects access to elements below */ - raw_spinlock_t lock; - - struct hlist_nulls_head free_list; - struct list_head all_list; - struct wait_queue_entry wait; struct io_wq_work *hash_tail[IO_WQ_NR_HASH_BUCKETS]; @@ -191,9 +203,9 @@ static void io_worker_cancel_cb(struct io_worker *worker) struct io_wq *wq = worker->wq; atomic_dec(&acct->nr_running); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); acct->nr_workers--; - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); io_worker_ref_put(wq); clear_bit_unlock(0, &worker->create_state); io_worker_release(worker); @@ -212,6 +224,7 @@ static bool io_task_worker_match(struct callback_head *cb, void *data) static void io_worker_exit(struct io_worker *worker) { struct io_wq *wq = worker->wq; + struct io_wq_acct *acct = io_wq_get_acct(worker); while (1) { struct callback_head *cb = task_work_cancel_match(wq->task, @@ -225,11 +238,11 @@ static void io_worker_exit(struct io_worker *worker) io_worker_release(worker); wait_for_completion(&worker->ref_done); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); if (test_bit(IO_WORKER_F_FREE, &worker->flags)) hlist_nulls_del_rcu(&worker->nulls_node); list_del_rcu(&worker->all_list); - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); io_wq_dec_running(worker); /* * this worker is a goner, clear ->worker_private to avoid any @@ -268,8 +281,7 @@ static inline bool io_acct_run_queue(struct io_wq_acct *acct) * Check head of free list for an available worker. If one isn't available, * caller must create one. */ -static bool io_wq_activate_free_worker(struct io_wq *wq, - struct io_wq_acct *acct) +static bool io_acct_activate_free_worker(struct io_wq_acct *acct) __must_hold(RCU) { struct hlist_nulls_node *n; @@ -280,13 +292,9 @@ static bool io_wq_activate_free_worker(struct io_wq *wq, * activate. If a given worker is on the free_list but in the process * of exiting, keep trying. */ - hlist_nulls_for_each_entry_rcu(worker, n, &wq->free_list, nulls_node) { + hlist_nulls_for_each_entry_rcu(worker, n, &acct->free_list, nulls_node) { if (!io_worker_get(worker)) continue; - if (io_wq_get_acct(worker) != acct) { - io_worker_release(worker); - continue; - } /* * If the worker is already running, it's either already * starting work or finishing work. In either case, if it does @@ -313,13 +321,13 @@ static bool io_wq_create_worker(struct io_wq *wq, struct io_wq_acct *acct) if (unlikely(!acct->max_workers)) pr_warn_once("io-wq is not configured for unbound workers"); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); if (acct->nr_workers >= acct->max_workers) { - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); return true; } acct->nr_workers++; - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); atomic_inc(&acct->nr_running); atomic_inc(&wq->worker_refs); return create_io_worker(wq, acct); @@ -343,13 +351,13 @@ static void create_worker_cb(struct callback_head *cb) worker = container_of(cb, struct io_worker, create_work); wq = worker->wq; acct = worker->acct; - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); if (acct->nr_workers < acct->max_workers) { acct->nr_workers++; do_create = true; } - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); if (do_create) { create_io_worker(wq, acct); } else { @@ -428,25 +436,25 @@ static void io_wq_dec_running(struct io_worker *worker) * Worker will start processing some work. Move it to the busy list, if * it's currently on the freelist */ -static void __io_worker_busy(struct io_wq *wq, struct io_worker *worker) +static void __io_worker_busy(struct io_wq_acct *acct, struct io_worker *worker) { if (test_bit(IO_WORKER_F_FREE, &worker->flags)) { clear_bit(IO_WORKER_F_FREE, &worker->flags); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); hlist_nulls_del_init_rcu(&worker->nulls_node); - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); } } /* * No work, worker going to sleep. Move to freelist. */ -static void __io_worker_idle(struct io_wq *wq, struct io_worker *worker) - __must_hold(wq->lock) +static void __io_worker_idle(struct io_wq_acct *acct, struct io_worker *worker) + __must_hold(acct->workers_lock) { if (!test_bit(IO_WORKER_F_FREE, &worker->flags)) { set_bit(IO_WORKER_F_FREE, &worker->flags); - hlist_nulls_add_head_rcu(&worker->nulls_node, &wq->free_list); + hlist_nulls_add_head_rcu(&worker->nulls_node, &acct->free_list); } } @@ -581,7 +589,7 @@ static void io_worker_handle_work(struct io_wq_acct *acct, if (!work) break; - __io_worker_busy(wq, worker); + __io_worker_busy(acct, worker); io_assign_current_work(worker, work); __set_current_state(TASK_RUNNING); @@ -652,20 +660,20 @@ static int io_wq_worker(void *data) while (io_acct_run_queue(acct)) io_worker_handle_work(acct, worker); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); /* * Last sleep timed out. Exit if we're not the last worker, * or if someone modified our affinity. */ if (last_timeout && (exit_mask || acct->nr_workers > 1)) { acct->nr_workers--; - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); __set_current_state(TASK_RUNNING); break; } last_timeout = false; - __io_worker_idle(wq, worker); - raw_spin_unlock(&wq->lock); + __io_worker_idle(acct, worker); + raw_spin_unlock(&acct->workers_lock); if (io_run_task_work()) continue; ret = schedule_timeout(WORKER_IDLE_TIMEOUT); @@ -726,18 +734,18 @@ void io_wq_worker_sleeping(struct task_struct *tsk) io_wq_dec_running(worker); } -static void io_init_new_worker(struct io_wq *wq, struct io_worker *worker, +static void io_init_new_worker(struct io_wq *wq, struct io_wq_acct *acct, struct io_worker *worker, struct task_struct *tsk) { tsk->worker_private = worker; worker->task = tsk; set_cpus_allowed_ptr(tsk, wq->cpu_mask); - raw_spin_lock(&wq->lock); - hlist_nulls_add_head_rcu(&worker->nulls_node, &wq->free_list); - list_add_tail_rcu(&worker->all_list, &wq->all_list); + raw_spin_lock(&acct->workers_lock); + hlist_nulls_add_head_rcu(&worker->nulls_node, &acct->free_list); + list_add_tail_rcu(&worker->all_list, &acct->all_list); set_bit(IO_WORKER_F_FREE, &worker->flags); - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); wake_up_new_task(tsk); } @@ -785,20 +793,20 @@ static void create_worker_cont(struct callback_head *cb) struct io_worker *worker; struct task_struct *tsk; struct io_wq *wq; + struct io_wq_acct *acct; worker = container_of(cb, struct io_worker, create_work); clear_bit_unlock(0, &worker->create_state); wq = worker->wq; + acct = io_wq_get_acct(worker); tsk = create_io_thread(io_wq_worker, worker, NUMA_NO_NODE); if (!IS_ERR(tsk)) { - io_init_new_worker(wq, worker, tsk); + io_init_new_worker(wq, acct, worker, tsk); io_worker_release(worker); return; } else if (!io_should_retry_thread(worker, PTR_ERR(tsk))) { - struct io_wq_acct *acct = io_wq_get_acct(worker); - atomic_dec(&acct->nr_running); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); acct->nr_workers--; if (!acct->nr_workers) { struct io_cb_cancel_data match = { @@ -806,11 +814,11 @@ static void create_worker_cont(struct callback_head *cb) .cancel_all = true, }; - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); while (io_acct_cancel_pending_work(wq, acct, &match)) ; } else { - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); } io_worker_ref_put(wq); kfree(worker); @@ -843,9 +851,9 @@ static bool create_io_worker(struct io_wq *wq, struct io_wq_acct *acct) if (!worker) { fail: atomic_dec(&acct->nr_running); - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); acct->nr_workers--; - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); io_worker_ref_put(wq); return false; } @@ -858,7 +866,7 @@ static bool create_io_worker(struct io_wq *wq, struct io_wq_acct *acct) tsk = create_io_thread(io_wq_worker, worker, NUMA_NO_NODE); if (!IS_ERR(tsk)) { - io_init_new_worker(wq, worker, tsk); + io_init_new_worker(wq, acct, worker, tsk); } else if (!io_should_retry_thread(worker, PTR_ERR(tsk))) { kfree(worker); goto fail; @@ -874,14 +882,14 @@ static bool create_io_worker(struct io_wq *wq, struct io_wq_acct *acct) * Iterate the passed in list and call the specific function for each * worker that isn't exiting */ -static bool io_wq_for_each_worker(struct io_wq *wq, - bool (*func)(struct io_worker *, void *), - void *data) +static bool io_acct_for_each_worker(struct io_wq_acct *acct, + bool (*func)(struct io_worker *, void *), + void *data) { struct io_worker *worker; bool ret = false; - list_for_each_entry_rcu(worker, &wq->all_list, all_list) { + list_for_each_entry_rcu(worker, &acct->all_list, all_list) { if (io_worker_get(worker)) { /* no task if node is/was offline */ if (worker->task) @@ -895,6 +903,18 @@ static bool io_wq_for_each_worker(struct io_wq *wq, return ret; } +static bool io_wq_for_each_worker(struct io_wq *wq, + bool (*func)(struct io_worker *, void *), + void *data) +{ + for (int i = 0; i < IO_WQ_ACCT_NR; i++) { + if (!io_acct_for_each_worker(&wq->acct[i], func, data)) + return false; + } + + return true; +} + static bool io_wq_worker_wake(struct io_worker *worker, void *data) { __set_notify_signal(worker->task); @@ -964,7 +984,7 @@ void io_wq_enqueue(struct io_wq *wq, struct io_wq_work *work) raw_spin_unlock(&acct->lock); rcu_read_lock(); - do_create = !io_wq_activate_free_worker(wq, acct); + do_create = !io_acct_activate_free_worker(acct); rcu_read_unlock(); if (do_create && ((work_flags & IO_WQ_WORK_CONCURRENT) || @@ -975,12 +995,12 @@ void io_wq_enqueue(struct io_wq *wq, struct io_wq_work *work) if (likely(did_create)) return; - raw_spin_lock(&wq->lock); + raw_spin_lock(&acct->workers_lock); if (acct->nr_workers) { - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); return; } - raw_spin_unlock(&wq->lock); + raw_spin_unlock(&acct->workers_lock); /* fatal condition, failed to create the first worker */ io_acct_cancel_pending_work(wq, acct, &match); @@ -1089,11 +1109,22 @@ static void io_wq_cancel_pending_work(struct io_wq *wq, } } +static void io_acct_cancel_running_work(struct io_wq_acct *acct, + struct io_cb_cancel_data *match) +{ + raw_spin_lock(&acct->workers_lock); + io_acct_for_each_worker(acct, io_wq_worker_cancel, match); + raw_spin_unlock(&acct->workers_lock); +} + static void io_wq_cancel_running_work(struct io_wq *wq, struct io_cb_cancel_data *match) { rcu_read_lock(); - io_wq_for_each_worker(wq, io_wq_worker_cancel, match); + + for (int i = 0; i < IO_WQ_ACCT_NR; i++) + io_acct_cancel_running_work(&wq->acct[i], match); + rcu_read_unlock(); } @@ -1116,16 +1147,14 @@ enum io_wq_cancel io_wq_cancel_cb(struct io_wq *wq, work_cancel_fn *cancel, * as an indication that we attempt to signal cancellation. The * completion will run normally in this case. * - * Do both of these while holding the wq->lock, to ensure that + * Do both of these while holding the acct->workers_lock, to ensure that * we'll find a work item regardless of state. */ io_wq_cancel_pending_work(wq, &match); if (match.nr_pending && !match.cancel_all) return IO_WQ_CANCEL_OK; - raw_spin_lock(&wq->lock); io_wq_cancel_running_work(wq, &match); - raw_spin_unlock(&wq->lock); if (match.nr_running && !match.cancel_all) return IO_WQ_CANCEL_RUNNING; @@ -1149,7 +1178,7 @@ static int io_wq_hash_wake(struct wait_queue_entry *wait, unsigned mode, struct io_wq_acct *acct = &wq->acct[i]; if (test_and_clear_bit(IO_ACCT_STALLED_BIT, &acct->flags)) - io_wq_activate_free_worker(wq, acct); + io_acct_activate_free_worker(acct); } rcu_read_unlock(); return 1; @@ -1188,14 +1217,15 @@ struct io_wq *io_wq_create(unsigned bounded, struct io_wq_data *data) struct io_wq_acct *acct = &wq->acct[i]; atomic_set(&acct->nr_running, 0); + + raw_spin_lock_init(&acct->workers_lock); + INIT_HLIST_NULLS_HEAD(&acct->free_list, 0); + INIT_LIST_HEAD(&acct->all_list); + INIT_WQ_LIST(&acct->work_list); raw_spin_lock_init(&acct->lock); } - raw_spin_lock_init(&wq->lock); - INIT_HLIST_NULLS_HEAD(&wq->free_list, 0); - INIT_LIST_HEAD(&wq->all_list); - wq->task = get_task_struct(data->task); atomic_set(&wq->worker_refs, 1); init_completion(&wq->worker_done); @@ -1383,14 +1413,14 @@ int io_wq_max_workers(struct io_wq *wq, int *new_count) rcu_read_lock(); - raw_spin_lock(&wq->lock); for (i = 0; i < IO_WQ_ACCT_NR; i++) { acct = &wq->acct[i]; + raw_spin_lock(&acct->workers_lock); prev[i] = max_t(int, acct->max_workers, prev[i]); if (new_count[i]) acct->max_workers = new_count[i]; + raw_spin_unlock(&acct->workers_lock); } - raw_spin_unlock(&wq->lock); rcu_read_unlock(); for (i = 0; i < IO_WQ_ACCT_NR; i++) -- 2.53.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 6.6.y 3/3] io_uring/io-wq: fix worker accounting when canceling creation callbacks 2026-08-25 19:48 ` [PATCH 6.6.y 1/3] io_uring/io-wq: add io_worker.acct pointer Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 2/3] io_uring/io-wq: move worker lists to struct io_wq_acct Sasha Levin @ 2026-08-25 19:48 ` Sasha Levin 1 sibling, 0 replies; 8+ messages in thread From: Sasha Levin @ 2026-08-25 19:48 UTC (permalink / raw) To: stable; +Cc: Vishnu Razdan, Gabriel Krisman Bertazi, Jens Axboe, Sasha Levin From: Vishnu Razdan <vrazdan@openai.com> [ Upstream commit 297b5ccea4acacaa47c150f043bce695202afbf1 ] create_worker_cb() reserves an io-wq worker slot only after its task-work callback runs. If the callback is canceled before then, io_worker_cancel_cb() still decrements acct->nr_workers. When an existing worker retires with its creation callback pending, that worker has already decremented the same account's worker count. The resulting undercount permits worker creation beyond the account's configured limit. On an AST2600 OpenBMC system, an unchanged sensor daemon reached 4,291 threads with the original kernel. With an equivalent downstream fix, 25 passive samples under its normal workload showed 6-9 threads. Decrement nr_workers only when the canceled callback is not create_worker_cb(). Continuation callbacks still release their reserved slot, and both callback types retain the existing running-count, reference-count, and create-state cleanup. Fixes: 1d5f5ea7cb7d ("io-wq: remove worker to owner tw dependency") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Vishnu Razdan <vrazdan@openai.com> Reviewed-by: Gabriel Krisman Bertazi <krisman@suse.de> Link: https://patch.msgid.link/20260811-vrazdan-io-wq-b4-submit-v1-1-719ced16c921@openai.com Signed-off-by: Jens Axboe <axboe@kernel.dk> Signed-off-by: Sasha Levin <sashal@kernel.org> --- io_uring/io-wq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 775c55e377922..b09fa30927930 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -203,9 +203,12 @@ static void io_worker_cancel_cb(struct io_worker *worker) struct io_wq *wq = worker->wq; atomic_dec(&acct->nr_running); - raw_spin_lock(&acct->workers_lock); - acct->nr_workers--; - raw_spin_unlock(&acct->workers_lock); + /* create_worker_cb() has not reserved a worker slot yet. */ + if (worker->create_work.func != create_worker_cb) { + raw_spin_lock(&acct->workers_lock); + acct->nr_workers--; + raw_spin_unlock(&acct->workers_lock); + } io_worker_ref_put(wq); clear_bit_unlock(0, &worker->create_state); io_worker_release(worker); -- 2.53.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-28 14:54 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-24 12:18 FAILED: patch "[PATCH] io_uring/io-wq: fix worker accounting when canceling creation" failed to apply to 6.6-stable tree gregkh 2026-08-25 16:52 ` [PATCH 6.6.y] io_uring/io-wq: fix worker accounting when canceling creation callbacks Vishnu Razdan via B4 Relay 2026-08-28 1:46 ` Sasha Levin 2026-08-28 2:02 ` Jens Axboe 2026-08-28 14:54 ` Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 1/3] io_uring/io-wq: add io_worker.acct pointer Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 2/3] io_uring/io-wq: move worker lists to struct io_wq_acct Sasha Levin 2026-08-25 19:48 ` [PATCH 6.6.y 3/3] io_uring/io-wq: fix worker accounting when canceling creation callbacks Sasha Levin
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox