* [PATCH 1/6] workqueue: factor out get_percpu_pool()
2026-07-31 11:57 [PATCH 0/6] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
@ 2026-07-31 11:57 ` Breno Leitao
2026-07-31 11:57 ` [PATCH 2/6] workqueue: factor out alloc_and_link_percpu_pwqs() Breno Leitao
` (4 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-07-31 11:57 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, Breno Leitao, kernel-team, marco.crivellari
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] 15+ messages in thread* [PATCH 2/6] workqueue: factor out alloc_and_link_percpu_pwqs()
2026-07-31 11:57 [PATCH 0/6] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
2026-07-31 11:57 ` [PATCH 1/6] workqueue: factor out get_percpu_pool() Breno Leitao
@ 2026-07-31 11:57 ` Breno Leitao
2026-07-31 11:57 ` [PATCH 3/6] workqueue: release pwq pools by pool type Breno Leitao
` (3 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-07-31 11:57 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, Breno Leitao, kernel-team, marco.crivellari
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.
Now that the per-cpu branch returns a value through the helper, the
per-cpu, ordered and unbound cases share a single error-handling tail.
Turn the separate per-cpu block and the ordered/unbound if/else into one
if/else-if/else chain, dropping the early return and the duplicated 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] 15+ messages in thread* [PATCH 3/6] workqueue: release pwq pools by pool type
2026-07-31 11:57 [PATCH 0/6] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
2026-07-31 11:57 ` [PATCH 1/6] workqueue: factor out get_percpu_pool() Breno Leitao
2026-07-31 11:57 ` [PATCH 2/6] workqueue: factor out alloc_and_link_percpu_pwqs() Breno Leitao
@ 2026-07-31 11:57 ` Breno Leitao
2026-08-03 0:31 ` Tejun Heo
2026-07-31 11:57 ` [PATCH 4/6] workqueue: account nr_active by the backing pool Breno Leitao
` (2 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-07-31 11:57 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, Breno Leitao, kernel-team, marco.crivellari
pwq_release_workfn() calls put_unbound_pool() based on the WQ_UNBOUND
flag. That works today because an unbound workqueue only ever points at
unbound pools, but the flag is the wrong thing to test: what matters is
whether the pool is a refcounted unbound pool or a permanent per-cpu one.
Add is_pool_cpu_specific() and key the release on it instead of the flag.
This is equivalent for every existing workqueue and stays correct if an
unbound pwq is ever backed by a per-cpu pool. Convert the other
open-coded pool->cpu type 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..513193be00a7d 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 tied to a specific CPU, rather than an unbound pool. */
+static bool is_pool_cpu_specific(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_pool_cpu_specific(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_pool_cpu_specific(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_pool_cpu_specific(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_pool_cpu_specific(pool) && !(pool->flags & POOL_BH)) {
pool->cpu_stall = true;
cpu_pool_stall = true;
}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 3/6] workqueue: release pwq pools by pool type
2026-07-31 11:57 ` [PATCH 3/6] workqueue: release pwq pools by pool type Breno Leitao
@ 2026-08-03 0:31 ` Tejun Heo
2026-08-03 14:02 ` Breno Leitao
0 siblings, 1 reply; 15+ messages in thread
From: Tejun Heo @ 2026-08-03 0:31 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
On Fri, Jul 31, 2026 at 04:57:35AM -0700, Breno Leitao wrote:
> pwq_release_workfn() calls put_unbound_pool() based on the WQ_UNBOUND
> flag. That works today because an unbound workqueue only ever points at
> unbound pools, but the flag is the wrong thing to test: what matters is
> whether the pool is a refcounted unbound pool or a permanent per-cpu one.
>
> Add is_pool_cpu_specific() and key the release on it instead of the flag.
> This is equivalent for every existing workqueue and stays correct if an
> unbound pwq is ever backed by a per-cpu pool. Convert the other
> open-coded pool->cpu type checks -- in put_unbound_pool(),
> pool_allowed_cpus() and the workqueue watchdog -- to the same helper.
>
> No functional change.
The first para saying that it's testing something wrong and then the patch
not having any functional change reads odd. Can you please rewrite?
> +/* True if @pool is tied to a specific CPU, rather than an unbound pool. */
> +static bool is_pool_cpu_specific(struct worker_pool *pool)
I'm not sure about introducing a new term. Can we just stick to percpu?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 3/6] workqueue: release pwq pools by pool type
2026-08-03 0:31 ` Tejun Heo
@ 2026-08-03 14:02 ` Breno Leitao
2026-08-03 16:50 ` Tejun Heo
0 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-08-03 14:02 UTC (permalink / raw)
To: Tejun Heo; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
On Sun, Aug 02, 2026 at 02:31:27PM -1000, Tejun Heo wrote:
> On Fri, Jul 31, 2026 at 04:57:35AM -0700, Breno Leitao wrote:
> > pwq_release_workfn() calls put_unbound_pool() based on the WQ_UNBOUND
> > flag. That works today because an unbound workqueue only ever points at
> > unbound pools, but the flag is the wrong thing to test: what matters is
> > whether the pool is a refcounted unbound pool or a permanent per-cpu one.
> >
> > Add is_pool_cpu_specific() and key the release on it instead of the flag.
> > This is equivalent for every existing workqueue and stays correct if an
> > unbound pwq is ever backed by a per-cpu pool. Convert the other
> > open-coded pool->cpu type checks -- in put_unbound_pool(),
> > pool_allowed_cpus() and the workqueue watchdog -- to the same helper.
> >
> > No functional change.
>
> The first para saying that it's testing something wrong and then the patch
> not having any functional change reads odd. Can you please rewrite?
Fair, I meant that the two conditions agree today and nothing changes
here. I will get this better.
> > +/* True if @pool is tied to a specific CPU, rather than an unbound
> > pool. */ +static bool is_pool_cpu_specific(struct worker_pool *pool)
>
> I'm not sure about introducing a new term. Can we just stick to
> percpu?
Sure -- is_percpu_pool() any better?
Thanks,
--breno
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 3/6] workqueue: release pwq pools by pool type
2026-08-03 14:02 ` Breno Leitao
@ 2026-08-03 16:50 ` Tejun Heo
0 siblings, 0 replies; 15+ messages in thread
From: Tejun Heo @ 2026-08-03 16:50 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
Hello,
On Mon, Aug 03, 2026 at 07:02:46AM -0700, Breno Leitao wrote:
> > > +/* True if @pool is tied to a specific CPU, rather than an unbound
> > > pool. */ +static bool is_pool_cpu_specific(struct worker_pool *pool)
> >
> > I'm not sure about introducing a new term. Can we just stick to
> > percpu?
>
> Sure -- is_percpu_pool() any better?
Yeah, that sounds fine to me.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 4/6] workqueue: account nr_active by the backing pool
2026-07-31 11:57 [PATCH 0/6] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (2 preceding siblings ...)
2026-07-31 11:57 ` [PATCH 3/6] workqueue: release pwq pools by pool type Breno Leitao
@ 2026-07-31 11:57 ` Breno Leitao
2026-08-03 0:34 ` Tejun Heo
2026-07-31 11:57 ` [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs Breno Leitao
2026-07-31 11:57 ` [PATCH 6/6] workqueue: install per-cpu pwqs at creation for __WQ_PERCPU_POOLS Breno Leitao
5 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-07-31 11:57 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, Breno Leitao, kernel-team, marco.crivellari
Use is_pool_cpu_specific() instead of wq_node_nr_active() to check for
per workqueues.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 513193be00a7d..d17fdcaaf1685 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -1782,13 +1782,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_pool_cpu_specific(pool)) {
obtained = pwq->nr_active < READ_ONCE(wq->max_active);
goto out;
}
@@ -1796,6 +1799,10 @@ 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);
+ if (WARN_ON_ONCE(!nna))
+ return false;
+
/*
* 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 +2020,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 +2031,18 @@ 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_pool_cpu_specific(pool)) {
pwq_activate_first_inactive(pwq, false);
return;
}
+ nna = wq_node_nr_active(pwq->wq, pool->node);
+ if (WARN_ON_ONCE(!nna))
+ return;
+
/*
* 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] 15+ messages in thread* Re: [PATCH 4/6] workqueue: account nr_active by the backing pool
2026-07-31 11:57 ` [PATCH 4/6] workqueue: account nr_active by the backing pool Breno Leitao
@ 2026-08-03 0:34 ` Tejun Heo
2026-08-03 14:19 ` Breno Leitao
0 siblings, 1 reply; 15+ messages in thread
From: Tejun Heo @ 2026-08-03 0:34 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
Hello,
On Fri, Jul 31, 2026 at 04:57:36AM -0700, Breno Leitao wrote:
> @@ -1796,6 +1799,10 @@ 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);
> + if (WARN_ON_ONCE(!nna))
> + return false;
Would it make sense to turn WQ_UNBOUND test in wq_node_nr_active() into a
WARN_ON_ONCE()? And I don't think the return value needs a null check.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] workqueue: account nr_active by the backing pool
2026-08-03 0:34 ` Tejun Heo
@ 2026-08-03 14:19 ` Breno Leitao
0 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-08-03 14:19 UTC (permalink / raw)
To: Tejun Heo; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
On Sun, Aug 02, 2026 at 02:34:16PM -1000, Tejun Heo wrote:
> Hello,
>
> On Fri, Jul 31, 2026 at 04:57:36AM -0700, Breno Leitao wrote:
> > @@ -1796,6 +1799,10 @@ 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);
> > + if (WARN_ON_ONCE(!nna))
> > + return false;
>
> Would it make sense to turn WQ_UNBOUND test in wq_node_nr_active() into a
> WARN_ON_ONCE()? And I don't think the return value needs a null check.
Yes, that's the right place for it. wq->node_nr_active[] is only makes
sense for WQ_UNBOUND, so a NULL return was always a caller bug rather than an
answer.
I will improve it on v2.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs
2026-07-31 11:57 [PATCH 0/6] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (3 preceding siblings ...)
2026-07-31 11:57 ` [PATCH 4/6] workqueue: account nr_active by the backing pool Breno Leitao
@ 2026-07-31 11:57 ` Breno Leitao
2026-08-03 0:36 ` Tejun Heo
2026-07-31 11:57 ` [PATCH 6/6] workqueue: install per-cpu pwqs at creation for __WQ_PERCPU_POOLS Breno Leitao
5 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-07-31 11:57 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, Breno Leitao, kernel-team, marco.crivellari
Add alloc_percpu_pwq(), which binds a pwq to get_percpu_pool(wq, cpu),
and an internal __WQ_PERCPU_POOLS flag. unbound_wq_update_pwq() installs
such a pwq per CPU when the flag is set, reusing the existing
install/drain path. Pool release and nr_active are already keyed on the
backing pool, so a per-cpu-backed pwq is torn down and throttled
correctly.
PS: We can do this using if/else for per cpu/unbound as well, instead of
this labels:, would it be better?
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/workqueue.h | 1 +
kernel/workqueue.c | 33 +++++++++++++++++++++++++++++++++
2 files changed, 34 insertions(+)
diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h
index a283766a192aa..5bbbed94d2fa6 100644
--- a/include/linux/workqueue.h
+++ b/include/linux/workqueue.h
@@ -410,6 +410,7 @@ enum wq_flags {
__WQ_ORDERED = 1 << 17, /* internal: workqueue is ordered */
__WQ_LEGACY = 1 << 18, /* internal: create*_workqueue() */
__WQ_DEPRECATED = 1 << 19, /* internal: workqueue is deprecated */
+ __WQ_PERCPU_POOLS = 1 << 20, /* internal: back unbound pwqs with percpu pools */
/* BH wq only allows the following flags */
__WQ_BH_ALLOWS = WQ_BH | WQ_HIGHPRI | WQ_PERCPU,
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index d17fdcaaf1685..df4fc9ccb7b22 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5404,6 +5404,27 @@ static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
return pwq;
}
+/*
+ * Create a pwq backing @wq on @cpu with the static per-cpu pool instead of a
+ * dedicated unbound pool. Used by the unbound pwq machinery for a workqueue
+ * that requests the per-cpu backend.
+ */
+static struct pool_workqueue *alloc_percpu_pwq(struct workqueue_struct *wq,
+ int cpu)
+{
+ struct worker_pool *pool = get_percpu_pool(wq, cpu);
+ struct pool_workqueue *pwq;
+
+ lockdep_assert_held(&wq_pool_mutex);
+
+ pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
+ if (!pwq)
+ return NULL;
+
+ init_pwq(pwq, wq, pool);
+ return pwq;
+}
+
/**
* wq_calc_pod_cpumask - calculate a wq_attrs' cpumask for a pod
* @attrs: the wq_attrs of the default pwq of the target workqueue
@@ -5643,6 +5664,17 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
if (!(wq->flags & WQ_UNBOUND) || wq->unbound_attrs->ordered)
return;
+ if (wq->flags & __WQ_PERCPU_POOLS) {
+ /* nothing to do if @cpu is already backed by its per-cpu pool */
+ if (is_pool_cpu_specific(unbound_pwq(wq, cpu)->pool))
+ return;
+
+ pwq = alloc_percpu_pwq(wq, cpu);
+ if (!pwq)
+ goto use_dfl_pwq;
+ goto install;
+ }
+
/*
* We don't wanna alloc/free wq_attrs for each wq for each CPU.
* Let's use a preallocated one. The following buf is protected by
@@ -5666,6 +5698,7 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
goto use_dfl_pwq;
}
+install:
/* Install the new pwq. */
mutex_lock(&wq->mutex);
old_pwq = install_unbound_pwq(wq, cpu, pwq);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs
2026-07-31 11:57 ` [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs Breno Leitao
@ 2026-08-03 0:36 ` Tejun Heo
2026-08-03 16:35 ` Breno Leitao
0 siblings, 1 reply; 15+ messages in thread
From: Tejun Heo @ 2026-08-03 0:36 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
Hello,
On Fri, Jul 31, 2026 at 04:57:37AM -0700, Breno Leitao wrote:
> Add alloc_percpu_pwq(), which binds a pwq to get_percpu_pool(wq, cpu),
> and an internal __WQ_PERCPU_POOLS flag. unbound_wq_update_pwq() installs
> such a pwq per CPU when the flag is set, reusing the existing
> install/drain path. Pool release and nr_active are already keyed on the
> backing pool, so a per-cpu-backed pwq is torn down and throttled
> correctly.
>
> PS: We can do this using if/else for per cpu/unbound as well, instead of
> this labels:, would it be better?
So, we would have percpu-pool backed unbound workqueues in addition to the
existing percpu workqueues? I'm not sure that makes sense. What prevents
unifying them?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs
2026-08-03 0:36 ` Tejun Heo
@ 2026-08-03 16:35 ` Breno Leitao
2026-08-03 16:58 ` Tejun Heo
0 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-08-03 16:35 UTC (permalink / raw)
To: Tejun Heo; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
On Sun, Aug 02, 2026 at 02:36:52PM -1000, Tejun Heo wrote:
> Hello,
>
> On Fri, Jul 31, 2026 at 04:57:37AM -0700, Breno Leitao wrote:
> > Add alloc_percpu_pwq(), which binds a pwq to get_percpu_pool(wq, cpu),
> > and an internal __WQ_PERCPU_POOLS flag. unbound_wq_update_pwq() installs
> > such a pwq per CPU when the flag is set, reusing the existing
> > install/drain path. Pool release and nr_active are already keyed on the
> > backing pool, so a per-cpu-backed pwq is torn down and throttled
> > correctly.
> >
> > PS: We can do this using if/else for per cpu/unbound as well, instead of
> > this labels:, would it be better?
>
> So, we would have percpu-pool backed unbound workqueues in addition to the
> existing percpu workqueues? I'm not sure that makes sense. What prevents
> unifying them?
Nothing fundamental. What I'm working out here is how to plug the unbound
pwq machinery into the percpu pools, and I came up with this
__WQ_PERCPU_POOLS.
I am trying to following what you said here:
described in https://lore.kernel.org/all/ak569WYSm3ygKl1-@slm.duckdns.org :
> [...] just merge percpu and unbound workqueues. Unbound workqueues
> already have the ability to update pwqs (or rather install new ones and
> drain old ones), which is how attribute changes are implemented. If we
> make the pwqs be able to point to both unbound and percpu pools, the
> dynamic switch falls out naturally and percpu just becomes one of the
> affinity settings.
This is the design I am trying to convey to, please let me know if they
are not correct.
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 ugly __WQ_PERCPU_POOLS goes away, WQ_PERCPU carries the
intent, and the unbound path handles WQ_PERCPU directly. WQ_PERCPU
and WQ_UNBOUND stop being mutually exclusive types and become
a choice of backing, so the exclusivity check in alloc_workqueue()
changes meaning or goes away.
Thanks for the guidance here,
--breno
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs
2026-08-03 16:35 ` Breno Leitao
@ 2026-08-03 16:58 ` Tejun Heo
0 siblings, 0 replies; 15+ messages in thread
From: Tejun Heo @ 2026-08-03 16:58 UTC (permalink / raw)
To: Breno Leitao; +Cc: Lai Jiangshan, linux-kernel, kernel-team, marco.crivellari
On Mon, Aug 03, 2026 at 09:35:16AM -0700, Breno Leitao wrote:
> 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 ugly __WQ_PERCPU_POOLS goes away, WQ_PERCPU carries the
> intent, and the unbound path handles WQ_PERCPU directly. WQ_PERCPU
> and WQ_UNBOUND stop being mutually exclusive types and become
> a choice of backing, so the exclusivity check in alloc_workqueue()
> changes meaning or goes away.
Yeah, this makes sense to me.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 6/6] workqueue: install per-cpu pwqs at creation for __WQ_PERCPU_POOLS
2026-07-31 11:57 [PATCH 0/6] workqueue: base pwq pool release and nr_active on the backing pool Breno Leitao
` (4 preceding siblings ...)
2026-07-31 11:57 ` [PATCH 5/6] workqueue: add a per-cpu backend for unbound pwqs Breno Leitao
@ 2026-07-31 11:57 ` Breno Leitao
5 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-07-31 11:57 UTC (permalink / raw)
To: Tejun Heo, Lai Jiangshan
Cc: linux-kernel, Breno Leitao, kernel-team, marco.crivellari
alloc_and_link_pwqs() brings up an unbound workqueue with dedicated
unbound pools. For a workqueue that requests the per-cpu backend,
convert those pwqs to per-cpu-backed ones right after creation by
running the per-CPU update, so the backend is in place before the
workqueue is used rather than only after the first CPU hotplug event.
No workqueue sets __WQ_PERCPU_POOLS yet, so there is no functional
change.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/workqueue.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index df4fc9ccb7b22..cd3d0d54dfddc 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5766,6 +5766,12 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
if (ret)
goto enomem;
+
+ if (wq->flags & __WQ_PERCPU_POOLS) {
+ for_each_possible_cpu(cpu)
+ unbound_wq_update_pwq(wq, cpu);
+ }
+
return 0;
enomem:
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread