[PATCH v5 1/2] workqueue: Make alloc_workqueue() unbound by default

From: Marco Crivellari

Date: Mon Nov 02 2026 - 06:01:23 EST


Currently alloc_workqueue() ensures that one among WQ_PERCPU or WQ_UNBOUND
is present. Now every user has one among these two flags and if nothing
is specified the default is WQ_PERCPU.

This default is suboptimal: most workloads benefit from unbound queues,
allowing the scheduler to place worker threads where they're needed and
reducing noise when CPUs are isolated.

In order to achieve the future goal of removing WQ_UNBOUND from the code,
start changing the default behavior making alloc_workqueue() act this way:

- if WQ_PERCPU and WQ_UNBOUND are present: remove WQ_PERCPU (as before)
- if neither are present: add WQ_UNBOUND

BH workqueues (WQ_BH) are always per-CPU and can't be unbound, so
WQ_PERCPU is now unconditionally forced for them before the above
defaulting logic runs.

The WARN_ONCE() when neither flag is present has been removed: from now
on, every alloc_workqueue() user without WQ_PERCPU will be considered
unbound.

The defaulting now happens before the workqueue is allocated, since
unbound workqueues need the node_nr_active[] array. Doing it before the
WQ_POWER_EFFICIENT override also makes sure that callers passing both
WQ_PERCPU and WQ_UNBOUND get the warning regardless of
wq_power_efficient.

Update the WQ_POWER_EFFICIENT comment accordingly.

Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@xxxxxxxxxxxxx/
Suggested-by: Tejun Heo <tj@xxxxxxxxxx>
Signed-off-by: Marco Crivellari <marco.crivellari@xxxxxxxx>
Reviewed-by: Breno Leitao <leitao@xxxxxxxxxx>
---
include/linux/workqueue.h | 4 ++--
kernel/workqueue.c | 32 ++++++++++++++++----------------
2 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h
index c8a36423cb34..da18debf424d 100644
--- a/include/linux/workqueue.h
+++ b/include/linux/workqueue.h
@@ -392,8 +392,8 @@ enum wq_flags {
* turn may lead to more scheduling choices which are sub-optimal
* in terms of power consumption.
*
- * Workqueues marked with WQ_POWER_EFFICIENT are per-cpu by default
- * but become unbound if workqueue.power_efficient kernel param is
+ * Workqueues marked with WQ_POWER_EFFICIENT and WQ_PERCPU become
+ * unbound if workqueue.power_efficient kernel param is
* specified. Per-cpu workqueues which are identified to
* contribute significantly to power-consumption are identified and
* marked with this flag and enabling the power_efficient mode
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index c1cb328bce3c..747c48582ceb 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5892,6 +5892,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
unsigned int flags,
int max_active, va_list args)
{
+ bool wq_dropped_percpu = false;
struct workqueue_struct *wq;
size_t wq_size;
int name_len;
@@ -5901,6 +5902,18 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
return NULL;
if (WARN_ON_ONCE(max_active))
return NULL;
+ flags |= WQ_PERCPU;
+ }
+
+ /*
+ * Do this before computing wq_size and before WQ_POWER_EFFICIENT
+ * can drop WQ_PERCPU.
+ */
+ if (!(flags & (WQ_UNBOUND | WQ_PERCPU))) {
+ flags |= WQ_UNBOUND;
+ } else if (unlikely((flags & WQ_PERCPU) && (flags & WQ_UNBOUND))) {
+ flags &= ~WQ_PERCPU;
+ wq_dropped_percpu = true;
}

/* see the comment above the definition of WQ_POWER_EFFICIENT */
@@ -5927,22 +5940,9 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt,
pr_warn_once("workqueue: name exceeds WQ_NAME_LEN. Truncating to: %s\n",
wq->name);

- /*
- * One among WQ_PERCPU and WQ_UNBOUND must be set, but not both.
- * - If neither is set, default to WQ_PERCPU
- * - If both are set, default to WQ_UNBOUND
- *
- * This code can be removed after workqueue are unbound by default
- */
- if (unlikely(!(flags & (WQ_UNBOUND | WQ_PERCPU)))) {
- WARN_ONCE(1, "workqueue: %s is using neither WQ_PERCPU or WQ_UNBOUND. "
- "Setting WQ_PERCPU.\n", wq->name);
- flags |= WQ_PERCPU;
- } else if (unlikely((flags & WQ_PERCPU) && (flags & WQ_UNBOUND))) {
- WARN_ONCE(1, "workqueue: %s uses both WQ_PERCPU and WQ_UNBOUND. "
- "Dropped WQ_PERCPU, keeping WQ_UNBOUND.\n", wq->name);
- flags &= ~WQ_PERCPU;
- }
+ WARN_ONCE(wq_dropped_percpu,
+ "workqueue: %s uses both WQ_PERCPU and WQ_UNBOUND. "
+ "Dropped WQ_PERCPU, keeping WQ_UNBOUND.\n", wq->name);

if (flags & WQ_BH) {
/*
--
2.56.0