[PATCH wq/for-7.4 v3 0/3] workqueue: Keep max_active and percpu_max_active in sync

From: Breno Leitao

Date: Fri Oct 09 2026 - 08:43:17 EST


Commit 27db9dd7f84f3a ("workqueue: Give percpu workqueues their own
max_active") gave the percpu backend a limit of its own, so a workqueue
now carries both percpu_max_active and max_active.

__alloc_workqueue() only ever sets the pair matching the backend the
workqueue is created on, so the other stays at zero for its lifetime.

Nothing reads "the other scope" max_active today, so this is groundwork
rather than a fix.

Three patches. The first moves the initial limit setup out of
__alloc_workqueue() into wq_init_max_active().

The second sets both domains rather than one, according to tejun's
recommendation:

U = unbound max_active, the system-wide target
L = unbound min_active, the floor on each node
P = percpu_max_active, the limit on each CPU
N = online CPUs in the effective unbound mask, at least 1

Setting P:
U = min(P * N, WQ_MAX_ACTIVE)
L = P

Setting U:
L = min(L, U)
P = max(DIV_ROUND_UP(U, N), L)

Setting L:
L = clamp(L, 0, U)
P = max(DIV_ROUND_UP(U, N), L)

The third rederives both domains once CPU topology is known. Workqueues
created before smp_init() brings up the secondary CPUs compute N as 1,
and nothing revisits that afterwards.

This shouldn't change the behaviour of workqueue for now, but keeps the
groundwork correct for the PERCPU affinity scope work it's meant to
enable.

Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
Changes in v3:
- Dropped the silly percpu_concurrency_managed attribute patch.
- Fixed N to come from the workqueue's own unbound_effective_cpumask()
- Added a patch rederiving both domains once workqueue_init_topology()
knows the real CPU count.
- Handled BH in one place: wq_init_max_active() sets the limits of a BH
workqueue through wq_init_bh_max_active() and returns, instead of
giving BH its own branch further down (Tejun)
- Set min_active to percpu_max_active when deriving from a percpu limit
instead of min(percpu_max_active, max_active), as max_active can never
be lower (Tejun)
- Changelog of patch 2 now refers to the new PERCPU affinity scope
rather than WQ_AFFN_CPU (Tejun)
- Link to v2: https://patch.msgid.link/20260925-wq_final-v2-0-860ee052169e@xxxxxxxxxx

Changes in v2:
- Cut the series down to the three patches that change nothing
observable. The runtime scaling, the affinity scopes, the backend
rework, the concurrency management attribute and the sysfs files are
all held back.
- Set both max_active domains instead of holding them equal, deriving
one from the other rather than duplicating the value (Tejun)
- Link to v1:
https://patch.msgid.link/20260918-wq_final-v1-0-5c43c08a26bc@xxxxxxxxxx

---
Breno Leitao (3):
workqueue: Move the initial max_active setup into a helper
workqueue: Keep the limits of both max_active domains current
workqueue: Rescale max_active domains once CPU topology is known

kernel/workqueue.c | 168 ++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 129 insertions(+), 39 deletions(-)
---
base-commit: 9d4815c14f7faf789aeaa63515024168daf0390c
change-id: 20260914-wq_final-bcfbcd3b0f79

Best regards,
--
Breno Leitao <leitao@xxxxxxxxxx>