* [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool
@ 2026-08-05 14:52 Breno Leitao
2026-08-05 14:52 ` [PATCH v2 1/9] workqueue: factor out get_percpu_pool() Breno Leitao
` (9 more replies)
0 siblings, 10 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
The goal is to unify per-cpu and unbound workqueues onto one backend, so
an unbound workqueue can run on the concurrency-managed per-cpu pools and
WQ_PERCPU just requests that backend [1]. This series is preparation and
changes no behaviour on its own.
These changes also is a reasonable readability cleanup.
Design principles, as discussed in [2].
a) There is one workqueue implementation -- what is today the unbound one --
and percpu becomes one of its affinity settings rather than a separate
type. The unbound_* helper names get renamed/removed once they stop
being unbound-specific. So, `alloc_unbound_pwq()` becomes
`alloc_pwq` and handle both cases.
b) There is one pwq path, and alloc_and_link_percpu_pwqs() goes away.
c) Which pool backs a pwq becomes a property of the attrs, resolved in
alloc_unbound_pwq(), which hands out either the static percpu pool for
that CPU or a hashed unbound one.
d) Both pool types stay -- concurrency management only exists on the percpu
ones -- so what gets merged is the machinery, not the pools.
e) This is the prep phase for the changes that will come once we are
happy with these changes.
What will come later:
* Introduce a concurrency-managed per-cpu affinity scope
(WQ_AFFN_PERCPU) — percpu as an affinity setting
* Direct pool selection (fold the create-then-convert),
* Dynamic switching between per-cpu and unbound backing for CPU isolation.
[1] https://lore.kernel.org/all/ak569WYSm3ygKl1-@slm.duckdns.org
[2] https://lore.kernel.org/all/anDImojbi6lq0zUg@slm.duckdns.org/
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Changes in v2:
- Removed the ugly __WQ_PERCPU_POOLS
- Renamed some helpers here and there
- additional patches, removing the "unbound" from function names
- Link to v1: https://patch.msgid.link/20260731-wq-pool-refactor-v1-0-8eaf71cdab5f@debian.org
---
Breno Leitao (9):
workqueue: factor out get_percpu_pool()
workqueue: factor out alloc_and_link_percpu_pwqs()
workqueue: release pwq pools by pool type
workqueue: account nr_active by the backing pool
workqueue: test WQ_UNBOUND explicitly in the hotplug loops
workqueue: rename wq->unbound_attrs to wq->attrs
workqueue: allocate attrs for all workqueues
workqueue: rename alloc_unbound_pwq() to alloc_pwq()
workqueue: skip the node_nr_active update for non-unbound workqueues
kernel/workqueue.c | 166 +++++++++++++++++++++++++--------------------
tools/workqueue/wq_dump.py | 6 +-
2 files changed, 96 insertions(+), 76 deletions(-)
---
base-commit: dbff1ec23f68640a743512e8ada125795d1b72c7
change-id: 20260729-wq-pool-refactor-0fb28721f83f
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 1/9] workqueue: factor out get_percpu_pool()
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 2/9] workqueue: factor out alloc_and_link_percpu_pwqs() Breno Leitao
` (8 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
Move the static per-cpu worker_pool lookup in alloc_and_link_pwqs() into
a helper, get_percpu_pool(), so the lookup can be shared by other
pool-selection paths.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 4ec3db31493d2..63a39bb3f5e47 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5350,6 +5350,20 @@ static void link_pwq(struct pool_workqueue *pwq)
list_add_tail_rcu(&pwq->pwqs_node, &wq->pwqs);
}
+/* Return the static per-cpu worker_pool that backs @wq on @cpu. */
+static struct worker_pool *get_percpu_pool(struct workqueue_struct *wq, int cpu)
+{
+ struct worker_pool __percpu *pools;
+ bool highpri = wq->flags & WQ_HIGHPRI;
+
+ if (wq->flags & WQ_BH)
+ pools = bh_worker_pools;
+ else
+ pools = cpu_worker_pools;
+
+ return &per_cpu_ptr(pools, cpu)[highpri];
+}
+
/* obtain a pool matching @attr and create a pwq associating the pool and @wq */
static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs)
@@ -5664,19 +5678,9 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
goto enomem;
if (!(wq->flags & WQ_UNBOUND)) {
- struct worker_pool __percpu *pools;
-
- if (wq->flags & WQ_BH)
- pools = bh_worker_pools;
- else
- pools = cpu_worker_pools;
-
for_each_possible_cpu(cpu) {
- struct pool_workqueue **pwq_p;
- struct worker_pool *pool;
-
- pool = &(per_cpu_ptr(pools, cpu)[highpri]);
- pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);
+ struct pool_workqueue **pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);
+ struct worker_pool *pool = get_percpu_pool(wq, cpu);
*pwq_p = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL,
pool->node);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 2/9] workqueue: factor out alloc_and_link_percpu_pwqs()
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
2026-08-05 14:52 ` [PATCH v2 1/9] workqueue: factor out get_percpu_pool() Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 3/9] workqueue: release pwq pools by pool type Breno Leitao
` (7 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
Move the per-cpu pwq allocation loop out of alloc_and_link_pwqs() into a
helper. The inner allocation-failure path now returns -ENOMEM and the
caller jumps to the existing enomem cleanup, equivalent to the previous
goto.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 43 ++++++++++++++++++++++++-------------------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 63a39bb3f5e47..b386a457c0381 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5666,6 +5666,28 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
put_pwq_unlocked(old_pwq);
}
+static int alloc_and_link_percpu_pwqs(struct workqueue_struct *wq)
+{
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ struct pool_workqueue **pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);
+ struct worker_pool *pool = get_percpu_pool(wq, cpu);
+
+ *pwq_p = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
+ if (!*pwq_p)
+ return -ENOMEM;
+
+ init_pwq(*pwq_p, wq, pool);
+
+ mutex_lock(&wq->mutex);
+ link_pwq(*pwq_p);
+ mutex_unlock(&wq->mutex);
+ }
+
+ return 0;
+}
+
static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
bool highpri = wq->flags & WQ_HIGHPRI;
@@ -5678,25 +5700,8 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
goto enomem;
if (!(wq->flags & WQ_UNBOUND)) {
- for_each_possible_cpu(cpu) {
- struct pool_workqueue **pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);
- struct worker_pool *pool = get_percpu_pool(wq, cpu);
-
- *pwq_p = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL,
- pool->node);
- if (!*pwq_p)
- goto enomem;
-
- init_pwq(*pwq_p, wq, pool);
-
- mutex_lock(&wq->mutex);
- link_pwq(*pwq_p);
- mutex_unlock(&wq->mutex);
- }
- return 0;
- }
-
- if (wq->flags & __WQ_ORDERED) {
+ ret = alloc_and_link_percpu_pwqs(wq);
+ } else if (wq->flags & __WQ_ORDERED) {
struct pool_workqueue *dfl_pwq;
ret = apply_workqueue_attrs_locked(wq, ordered_wq_attrs[highpri]);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 3/9] workqueue: release pwq pools by pool type
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
2026-08-05 14:52 ` [PATCH v2 1/9] workqueue: factor out get_percpu_pool() Breno Leitao
2026-08-05 14:52 ` [PATCH v2 2/9] workqueue: factor out alloc_and_link_percpu_pwqs() Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 4/9] workqueue: account nr_active by the backing pool Breno Leitao
` (6 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
Add is_percpu_pool() and test the pool directly for per cpu. Convert the
other open-coded pool->cpu checks -- in put_unbound_pool(),
pool_allowed_cpus() and the workqueue watchdog -- to the same helper.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index b386a457c0381..b96090c85bcaa 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -1604,6 +1604,12 @@ work_func_t wq_worker_last_func(struct task_struct *task)
return worker->last_func;
}
+/* True if @pool is a static per-cpu pool rather than an unbound one. */
+static bool is_percpu_pool(struct worker_pool *pool)
+{
+ return pool->cpu >= 0;
+}
+
/**
* wq_node_nr_active - Determine wq_node_nr_active to use
* @wq: workqueue of interest
@@ -2753,7 +2759,7 @@ static struct worker *alloc_worker(int node)
static cpumask_t *pool_allowed_cpus(struct worker_pool *pool)
{
- if (pool->cpu < 0 && pool->attrs->affn_strict)
+ if (!is_percpu_pool(pool) && pool->attrs->affn_strict)
return pool->attrs->__pod_cpumask;
else
return pool->attrs->cpumask;
@@ -5121,7 +5127,7 @@ static void put_unbound_pool(struct worker_pool *pool)
return;
/* sanity checks */
- if (WARN_ON(!(pool->cpu < 0)) ||
+ if (WARN_ON(is_percpu_pool(pool)) ||
WARN_ON(!list_empty(&pool->worklist)))
return;
@@ -5273,7 +5279,7 @@ static void pwq_release_workfn(struct kthread_work *work)
mutex_unlock(&wq->mutex);
}
- if (wq->flags & WQ_UNBOUND) {
+ if (!is_percpu_pool(pool)) {
mutex_lock(&wq_pool_mutex);
put_unbound_pool(pool);
mutex_unlock(&wq_pool_mutex);
@@ -7949,7 +7955,7 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
lockup_detected = true;
stall_time = jiffies_to_msecs(now - pool_ts) / 1000;
max_stall_time = max(max_stall_time, stall_time);
- if (pool->cpu >= 0 && !(pool->flags & POOL_BH)) {
+ if (is_percpu_pool(pool) && !(pool->flags & POOL_BH)) {
pool->cpu_stall = true;
cpu_pool_stall = true;
}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 4/9] workqueue: account nr_active by the backing pool
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (2 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 3/9] workqueue: release pwq pools by pool type Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-10 21:41 ` Tejun Heo
2026-08-05 14:52 ` [PATCH v2 5/9] workqueue: test WQ_UNBOUND explicitly in the hotplug loops Breno Leitao
` (5 subsequent siblings)
9 siblings, 1 reply; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
pwq_tryinc_nr_active() and pwq_dec_nr_active() choose between the shared
per-node nr_active and the plain per-pwq one by testing
wq_node_nr_active() for NULL.
Test the backing pool with is_percpu_pool() instead, so the accounting
follows the pool that runs the work rather than the workqueue type.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index b96090c85bcaa..7b20d459d6442 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -1615,9 +1615,8 @@ static bool is_percpu_pool(struct worker_pool *pool)
* @wq: workqueue of interest
* @node: NUMA node, can be %NUMA_NO_NODE
*
- * Determine wq_node_nr_active to use for @wq on @node. Returns:
- *
- * - %NULL for per-cpu workqueues as they don't need to use shared nr_active.
+ * Determine wq_node_nr_active to use for @wq on @node. @wq must be unbound.
+ * Returns:
*
* - node_nr_active[nr_node_ids] if @node is %NUMA_NO_NODE.
*
@@ -1626,7 +1625,7 @@ static bool is_percpu_pool(struct worker_pool *pool)
static struct wq_node_nr_active *wq_node_nr_active(struct workqueue_struct *wq,
int node)
{
- if (!(wq->flags & WQ_UNBOUND))
+ if (WARN_ON_ONCE(!(wq->flags & WQ_UNBOUND)))
return NULL;
if (node == NUMA_NO_NODE)
@@ -1782,13 +1781,16 @@ static bool pwq_tryinc_nr_active(struct pool_workqueue *pwq, bool fill)
{
struct workqueue_struct *wq = pwq->wq;
struct worker_pool *pool = pwq->pool;
- struct wq_node_nr_active *nna = wq_node_nr_active(wq, pool->node);
+ struct wq_node_nr_active *nna;
bool obtained = false;
lockdep_assert_held(&pool->lock);
- if (!nna) {
- /* BH or per-cpu workqueue, pwq->nr_active is sufficient */
+ /*
+ * A concurrency-managed per-cpu pool accounts nr_active per pwq, so
+ * pwq->nr_active against wq->max_active is sufficient.
+ */
+ if (is_percpu_pool(pool)) {
obtained = pwq->nr_active < READ_ONCE(wq->max_active);
goto out;
}
@@ -1796,6 +1798,8 @@ static bool pwq_tryinc_nr_active(struct pool_workqueue *pwq, bool fill)
if (unlikely(pwq->plugged))
return false;
+ nna = wq_node_nr_active(wq, pool->node);
+
/*
* Unbound workqueue uses per-node shared nr_active $nna. If @pwq is
* already waiting on $nna, pwq_dec_nr_active() will maintain the
@@ -2013,7 +2017,7 @@ static void node_activate_pending_pwq(struct wq_node_nr_active *nna,
static void pwq_dec_nr_active(struct pool_workqueue *pwq)
{
struct worker_pool *pool = pwq->pool;
- struct wq_node_nr_active *nna = wq_node_nr_active(pwq->wq, pool->node);
+ struct wq_node_nr_active *nna;
lockdep_assert_held(&pool->lock);
@@ -2024,14 +2028,16 @@ static void pwq_dec_nr_active(struct pool_workqueue *pwq)
pwq->nr_active--;
/*
- * For a percpu workqueue, it's simple. Just need to kick the first
+ * A concurrency-managed per-cpu pool only needs to kick the first
* inactive work item on @pwq itself.
*/
- if (!nna) {
+ if (is_percpu_pool(pool)) {
pwq_activate_first_inactive(pwq, false);
return;
}
+ nna = wq_node_nr_active(pwq->wq, pool->node);
+
/*
* If @pwq is for an unbound workqueue, it's more complicated because
* multiple pwqs and pools may be sharing the nr_active count. When a
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 5/9] workqueue: test WQ_UNBOUND explicitly in the hotplug loops
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (3 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 4/9] workqueue: account nr_active by the backing pool Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 6/9] workqueue: rename wq->unbound_attrs to wq->attrs Breno Leitao
` (4 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
workqueue_online_cpu() and workqueue_offline_cpu() decide whether a
workqueue needs a pod affinity update by testing wq->unbound_attrs for
NULL, which is only meaningful because the attrs are allocated for
unbound workqueues alone.
Test the flag instead, so the attrs can later be allocated for every
workqueue.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 7b20d459d6442..b6458ee53852f 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -6945,7 +6945,7 @@ int workqueue_online_cpu(unsigned int cpu)
list_for_each_entry(wq, &workqueues, list) {
struct workqueue_attrs *attrs = wq->unbound_attrs;
- if (attrs) {
+ if (wq->flags & WQ_UNBOUND) {
const struct wq_pod_type *pt = wqattrs_pod_type(attrs);
int tcpu;
@@ -6980,7 +6980,7 @@ int workqueue_offline_cpu(unsigned int cpu)
list_for_each_entry(wq, &workqueues, list) {
struct workqueue_attrs *attrs = wq->unbound_attrs;
- if (attrs) {
+ if (wq->flags & WQ_UNBOUND) {
const struct wq_pod_type *pt = wqattrs_pod_type(attrs);
int tcpu;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 6/9] workqueue: rename wq->unbound_attrs to wq->attrs
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (4 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 5/9] workqueue: test WQ_UNBOUND explicitly in the hotplug loops Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 7/9] workqueue: allocate attrs for all workqueues Breno Leitao
` (3 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
The unbound prefix says which workqueues currently have the field rather
than what it holds, and the next patch allocates it for every workqueue.
Rename it first so that change stays a single line.
tools/workqueue/wq_dump.py reads the field by name, so rename it there
too.
wq_sysfs_unbound_attrs[] keeps its name: it is the set of sysfs files
that only unbound workqueues expose.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 38 +++++++++++++++++++-------------------
tools/workqueue/wq_dump.py | 6 +++---
2 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index b6458ee53852f..fbe13c9be4c28 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -371,7 +371,7 @@ struct workqueue_struct {
int saved_max_active; /* WQ: saved max_active */
int saved_min_active; /* WQ: saved min_active */
- struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
+ struct workqueue_attrs *attrs; /* PW: workqueue attributes */
struct pool_workqueue __rcu *dfl_pwq; /* PW: only for unbound wqs */
#ifdef CONFIG_SYSFS
@@ -759,7 +759,7 @@ static struct pool_workqueue *unbound_pwq(struct workqueue_struct *wq, int cpu)
* unbound_effective_cpumask - effective cpumask of an unbound workqueue
* @wq: workqueue of interest
*
- * @wq->unbound_attrs->cpumask contains the cpumask requested by the user which
+ * @wq->attrs->cpumask contains the cpumask requested by the user which
* is masked with wq_unbound_cpumask to determine the effective cpumask. The
* default pwq is always mapped to the pool with the current effective cpumask.
*/
@@ -5098,7 +5098,7 @@ static void rcu_free_wq(struct rcu_head *rcu)
wq_free_lockdep(wq);
free_percpu(wq->cpu_pwq);
- free_workqueue_attrs(wq->unbound_attrs);
+ free_workqueue_attrs(wq->attrs);
kfree(wq);
}
@@ -5548,7 +5548,7 @@ static void apply_wqattrs_commit(struct apply_wqattrs_ctx *ctx)
/* all pwqs have been created successfully, let's install'em */
mutex_lock(&ctx->wq->mutex);
- copy_workqueue_attrs(ctx->wq->unbound_attrs, ctx->attrs);
+ copy_workqueue_attrs(ctx->wq->attrs, ctx->attrs);
/* save the previous pwqs and install the new ones */
for_each_possible_cpu(cpu)
@@ -5635,7 +5635,7 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
lockdep_assert_held(&wq_pool_mutex);
- if (!(wq->flags & WQ_UNBOUND) || wq->unbound_attrs->ordered)
+ if (!(wq->flags & WQ_UNBOUND) || wq->attrs->ordered)
return;
/*
@@ -5645,7 +5645,7 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
*/
target_attrs = unbound_wq_update_pwq_attrs_buf;
- copy_workqueue_attrs(target_attrs, wq->unbound_attrs);
+ copy_workqueue_attrs(target_attrs, wq->attrs);
wqattrs_actualize_cpumask(target_attrs, wq_unbound_cpumask);
/* nothing to do if the target cpumask matches the current pwq */
@@ -5903,8 +5903,8 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
return NULL;
if (flags & WQ_UNBOUND) {
- wq->unbound_attrs = alloc_workqueue_attrs_noprof();
- if (!wq->unbound_attrs)
+ wq->attrs = alloc_workqueue_attrs_noprof();
+ if (!wq->attrs)
goto err_free_wq;
}
@@ -5999,7 +5999,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
free_node_nr_active(wq->node_nr_active);
}
err_free_wq:
- free_workqueue_attrs(wq->unbound_attrs);
+ free_workqueue_attrs(wq->attrs);
kfree(wq);
return NULL;
err_unlock_destroy:
@@ -6943,7 +6943,7 @@ int workqueue_online_cpu(unsigned int cpu)
/* update pod affinity of unbound workqueues */
list_for_each_entry(wq, &workqueues, list) {
- struct workqueue_attrs *attrs = wq->unbound_attrs;
+ struct workqueue_attrs *attrs = wq->attrs;
if (wq->flags & WQ_UNBOUND) {
const struct wq_pod_type *pt = wqattrs_pod_type(attrs);
@@ -6978,7 +6978,7 @@ int workqueue_offline_cpu(unsigned int cpu)
cpumask_clear_cpu(cpu, wq_online_cpumask);
list_for_each_entry(wq, &workqueues, list) {
- struct workqueue_attrs *attrs = wq->unbound_attrs;
+ struct workqueue_attrs *attrs = wq->attrs;
if (wq->flags & WQ_UNBOUND) {
const struct wq_pod_type *pt = wqattrs_pod_type(attrs);
@@ -7158,7 +7158,7 @@ static int workqueue_apply_unbound_cpumask(const cpumask_var_t unbound_cpumask)
if (!(wq->flags & WQ_UNBOUND) || (wq->flags & __WQ_DESTROYING))
continue;
- ctx = apply_wqattrs_prepare(wq, wq->unbound_attrs, unbound_cpumask);
+ ctx = apply_wqattrs_prepare(wq, wq->attrs, unbound_cpumask);
if (IS_ERR(ctx)) {
ret = PTR_ERR(ctx);
break;
@@ -7376,7 +7376,7 @@ static ssize_t wq_nice_show(struct device *dev, struct device_attribute *attr,
int written;
mutex_lock(&wq->mutex);
- written = scnprintf(buf, PAGE_SIZE, "%d\n", wq->unbound_attrs->nice);
+ written = scnprintf(buf, PAGE_SIZE, "%d\n", wq->attrs->nice);
mutex_unlock(&wq->mutex);
return written;
@@ -7393,7 +7393,7 @@ static struct workqueue_attrs *wq_sysfs_prep_attrs(struct workqueue_struct *wq)
if (!attrs)
return NULL;
- copy_workqueue_attrs(attrs, wq->unbound_attrs);
+ copy_workqueue_attrs(attrs, wq->attrs);
return attrs;
}
@@ -7430,7 +7430,7 @@ static ssize_t wq_cpumask_show(struct device *dev,
mutex_lock(&wq->mutex);
written = scnprintf(buf, PAGE_SIZE, "%*pb\n",
- cpumask_pr_args(wq->unbound_attrs->cpumask));
+ cpumask_pr_args(wq->attrs->cpumask));
mutex_unlock(&wq->mutex);
return written;
}
@@ -7466,13 +7466,13 @@ static ssize_t wq_affn_scope_show(struct device *dev,
int written;
mutex_lock(&wq->mutex);
- if (wq->unbound_attrs->affn_scope == WQ_AFFN_DFL)
+ if (wq->attrs->affn_scope == WQ_AFFN_DFL)
written = scnprintf(buf, PAGE_SIZE, "%s (%s)\n",
wq_affn_names[WQ_AFFN_DFL],
wq_affn_names[wq_affn_dfl]);
else
written = scnprintf(buf, PAGE_SIZE, "%s\n",
- wq_affn_names[wq->unbound_attrs->affn_scope]);
+ wq_affn_names[wq->attrs->affn_scope]);
mutex_unlock(&wq->mutex);
return written;
@@ -7507,7 +7507,7 @@ static ssize_t wq_affinity_strict_show(struct device *dev,
struct workqueue_struct *wq = dev_to_wq(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
- wq->unbound_attrs->affn_strict);
+ wq->attrs->affn_strict);
}
static ssize_t wq_affinity_strict_store(struct device *dev,
@@ -7680,7 +7680,7 @@ int workqueue_sysfs_register(struct workqueue_struct *wq)
dev_set_name(&wq_dev->dev, "%s", wq->name);
/*
- * unbound_attrs are created separately. Suppress uevent until
+ * attrs are created separately. Suppress uevent until
* everything is ready.
*/
dev_set_uevent_suppress(&wq_dev->dev, true);
diff --git a/tools/workqueue/wq_dump.py b/tools/workqueue/wq_dump.py
index a0c72237531f9..e0a6936a2a37f 100644
--- a/tools/workqueue/wq_dump.py
+++ b/tools/workqueue/wq_dump.py
@@ -85,7 +85,7 @@ def wq_type_str(wq):
if wq.flags & WQ_ORDERED:
return f'{"ordered":{wq_type_len}}'
else:
- if wq.unbound_attrs.affn_strict:
+ if wq.attrs.affn_strict:
return f'{"unbound,S":{wq_type_len}}'
else:
return f'{"unbound":{wq_type_len}}'
@@ -205,8 +205,8 @@ for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(
continue
print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='')
- if wq.unbound_attrs.value_() != 0:
- print(f' {cpumask_str(wq.unbound_attrs.cpumask):{ucpus_len}}', end='')
+ if wq.attrs.value_() != 0:
+ print(f' {cpumask_str(wq.attrs.cpumask):{ucpus_len}}', end='')
else:
print(f' {"":{ucpus_len}}', end='')
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 7/9] workqueue: allocate attrs for all workqueues
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (5 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 6/9] workqueue: rename wq->unbound_attrs to wq->attrs Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 8/9] workqueue: rename alloc_unbound_pwq() to alloc_pwq() Breno Leitao
` (2 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
The attrs are where the affinity scope lives, and a per-cpu workqueue
will need one once per-cpu becomes a scope rather than a separate
backend. Allocate them unconditionally.
wq_dump.py used a non-NULL wq->attrs as its test for an unbound
workqueue, which no longer holds; test WQ_UNBOUND there instead.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 8 +++-----
tools/workqueue/wq_dump.py | 2 +-
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index fbe13c9be4c28..e13f223e8502d 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5902,11 +5902,9 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
if (!wq)
return NULL;
- if (flags & WQ_UNBOUND) {
- wq->attrs = alloc_workqueue_attrs_noprof();
- if (!wq->attrs)
- goto err_free_wq;
- }
+ wq->attrs = alloc_workqueue_attrs_noprof();
+ if (!wq->attrs)
+ goto err_free_wq;
name_len = vsnprintf(wq->name, sizeof(wq->name), fmt, args);
diff --git a/tools/workqueue/wq_dump.py b/tools/workqueue/wq_dump.py
index e0a6936a2a37f..31afc24ef17bf 100644
--- a/tools/workqueue/wq_dump.py
+++ b/tools/workqueue/wq_dump.py
@@ -205,7 +205,7 @@ for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(
continue
print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='')
- if wq.attrs.value_() != 0:
+ if wq.flags & WQ_UNBOUND:
print(f' {cpumask_str(wq.attrs.cpumask):{ucpus_len}}', end='')
else:
print(f' {"":{ucpus_len}}', end='')
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 8/9] workqueue: rename alloc_unbound_pwq() to alloc_pwq()
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (6 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 7/9] workqueue: allocate attrs for all workqueues Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 9/9] workqueue: skip the node_nr_active update for non-unbound workqueues Breno Leitao
2026-08-10 22:10 ` [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Tejun Heo
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
This allocates a pwq and binds it to the pool @attrs asks for.
Which pool that is becomes a property of the attrs (once per-cpu becomes
an affinity scope).
Remove the 'unbound" from the function name, given it will be bigger
than unbound.
No functional change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index e13f223e8502d..65ac75431c3be 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5377,7 +5377,7 @@ static struct worker_pool *get_percpu_pool(struct workqueue_struct *wq, int cpu)
}
/* obtain a pool matching @attr and create a pwq associating the pool and @wq */
-static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
+static struct pool_workqueue *alloc_pwq(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs)
{
struct worker_pool *pool;
@@ -5500,7 +5500,7 @@ apply_wqattrs_prepare(struct workqueue_struct *wq,
copy_workqueue_attrs(new_attrs, attrs);
wqattrs_actualize_cpumask(new_attrs, unbound_cpumask);
cpumask_copy(new_attrs->__pod_cpumask, new_attrs->cpumask);
- ctx->dfl_pwq = alloc_unbound_pwq(wq, new_attrs);
+ ctx->dfl_pwq = alloc_pwq(wq, new_attrs);
if (!ctx->dfl_pwq)
goto out_free;
@@ -5510,7 +5510,7 @@ apply_wqattrs_prepare(struct workqueue_struct *wq,
ctx->pwq_tbl[cpu] = ctx->dfl_pwq;
} else {
wq_calc_pod_cpumask(new_attrs, cpu);
- ctx->pwq_tbl[cpu] = alloc_unbound_pwq(wq, new_attrs);
+ ctx->pwq_tbl[cpu] = alloc_pwq(wq, new_attrs);
if (!ctx->pwq_tbl[cpu])
goto out_free;
}
@@ -5654,7 +5654,7 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
return;
/* create a new pwq */
- pwq = alloc_unbound_pwq(wq, target_attrs);
+ pwq = alloc_pwq(wq, target_attrs);
if (!pwq) {
pr_warn("workqueue: allocation failed while updating CPU pod affinity of \"%s\"\n",
wq->name);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 9/9] workqueue: skip the node_nr_active update for non-unbound workqueues
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (7 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 8/9] workqueue: rename alloc_unbound_pwq() to alloc_pwq() Breno Leitao
@ 2026-08-05 14:52 ` Breno Leitao
2026-08-10 22:10 ` [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Tejun Heo
9 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-05 14:52 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, marco.crivellari, Breno Leitao, kernel-team
apply_wqattrs_commit() updates node_nr_active->max unconditionally.
wq->node_nr_active[] is only allocated for unbound workqueues, so guard
the call before per-cpu workqueues start using this path.
No functional change: only unbound workqueues reach apply_wqattrs_*()
today.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 65ac75431c3be..8fd6af72ffd8d 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5556,8 +5556,9 @@ static void apply_wqattrs_commit(struct apply_wqattrs_ctx *ctx)
ctx->pwq_tbl[cpu]);
ctx->dfl_pwq = install_unbound_pwq(ctx->wq, -1, ctx->dfl_pwq);
- /* update node_nr_active->max */
- wq_update_node_max_active(ctx->wq, -1);
+ /* update node_nr_active->max, which only unbound workqueues have */
+ if (ctx->wq->flags & WQ_UNBOUND)
+ wq_update_node_max_active(ctx->wq, -1);
mutex_unlock(&ctx->wq->mutex);
}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v2 4/9] workqueue: account nr_active by the backing pool
2026-08-05 14:52 ` [PATCH v2 4/9] workqueue: account nr_active by the backing pool Breno Leitao
@ 2026-08-10 21:41 ` Tejun Heo
2026-08-11 10:15 ` Breno Leitao
0 siblings, 1 reply; 13+ messages in thread
From: Tejun Heo @ 2026-08-10 21:41 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, marco.crivellari, kernel-team
On Wed, Aug 05, 2026 at 07:52:31AM -0700, Breno Leitao wrote:
> static struct wq_node_nr_active *wq_node_nr_active(struct workqueue_struct *wq,
> int node)
> {
> - if (!(wq->flags & WQ_UNBOUND))
> + if (WARN_ON_ONCE(!(wq->flags & WQ_UNBOUND)))
> return NULL;
This just pushes the crash to the caller, right? Maybe do BUG_ON() instead?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (8 preceding siblings ...)
2026-08-05 14:52 ` [PATCH v2 9/9] workqueue: skip the node_nr_active update for non-unbound workqueues Breno Leitao
@ 2026-08-10 22:10 ` Tejun Heo
9 siblings, 0 replies; 13+ messages in thread
From: Tejun Heo @ 2026-08-10 22:10 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, marco.crivellari, kernel-team
Hello,
On Wed, Aug 05, 2026 at 07:52:27AM -0700, Breno Leitao wrote:
> Breno Leitao (9):
> workqueue: factor out get_percpu_pool()
> workqueue: factor out alloc_and_link_percpu_pwqs()
> workqueue: release pwq pools by pool type
> workqueue: account nr_active by the backing pool
> workqueue: test WQ_UNBOUND explicitly in the hotplug loops
> workqueue: rename wq->unbound_attrs to wq->attrs
> workqueue: allocate attrs for all workqueues
> workqueue: rename alloc_unbound_pwq() to alloc_pwq()
> workqueue: skip the node_nr_active update for non-unbound workqueues
Applied 1-9 to wq/for-7.3. The wq_node_nr_active() BUG_ON() change
suggested on patch 4 can be done incrementally later.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 4/9] workqueue: account nr_active by the backing pool
2026-08-10 21:41 ` Tejun Heo
@ 2026-08-11 10:15 ` Breno Leitao
0 siblings, 0 replies; 13+ messages in thread
From: Breno Leitao @ 2026-08-11 10:15 UTC (permalink / raw)
To: Tejun Heo; +Cc: Lai Jiangshan, linux-kernel, marco.crivellari, kernel-team
On Mon, Aug 10, 2026 at 11:41:23AM -1000, Tejun Heo wrote:
> On Wed, Aug 05, 2026 at 07:52:31AM -0700, Breno Leitao wrote:
> > static struct wq_node_nr_active *wq_node_nr_active(struct workqueue_struct *wq,
> > int node)
> > {
> > - if (!(wq->flags & WQ_UNBOUND))
> > + if (WARN_ON_ONCE(!(wq->flags & WQ_UNBOUND)))
> > return NULL;
>
> This just pushes the crash to the caller, right? Maybe do BUG_ON() instead?
Right. I will do a patch that transform this in BUG_ON() in the next
patchset.
Thanks for the review,
--breno
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-08-11 10:15 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 14:52 [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
2026-08-05 14:52 ` [PATCH v2 1/9] workqueue: factor out get_percpu_pool() Breno Leitao
2026-08-05 14:52 ` [PATCH v2 2/9] workqueue: factor out alloc_and_link_percpu_pwqs() Breno Leitao
2026-08-05 14:52 ` [PATCH v2 3/9] workqueue: release pwq pools by pool type Breno Leitao
2026-08-05 14:52 ` [PATCH v2 4/9] workqueue: account nr_active by the backing pool Breno Leitao
2026-08-10 21:41 ` Tejun Heo
2026-08-11 10:15 ` Breno Leitao
2026-08-05 14:52 ` [PATCH v2 5/9] workqueue: test WQ_UNBOUND explicitly in the hotplug loops Breno Leitao
2026-08-05 14:52 ` [PATCH v2 6/9] workqueue: rename wq->unbound_attrs to wq->attrs Breno Leitao
2026-08-05 14:52 ` [PATCH v2 7/9] workqueue: allocate attrs for all workqueues Breno Leitao
2026-08-05 14:52 ` [PATCH v2 8/9] workqueue: rename alloc_unbound_pwq() to alloc_pwq() Breno Leitao
2026-08-05 14:52 ` [PATCH v2 9/9] workqueue: skip the node_nr_active update for non-unbound workqueues Breno Leitao
2026-08-10 22:10 ` [PATCH v2 0/9] workqueue: base pwq pool release and nr_active on the backing pool Tejun Heo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox