summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-06 14:54:05 +0100
committerMark Brown <broonie@kernel.org>2026-07-06 14:54:05 +0100
commit76a07b89197f06d50eb61d9e9cdc093e5b0abbd8 (patch)
tree061f7a8ca6234faeffd8dfe03911c739b0a231fe
parent571450de9f941ec7511b3d2fa0b0fdb4877ba9df (diff)
parentecf5aad9a4417fece80890f27a9899db90c9c457 (diff)
downloadlinux-next-76a07b89197f06d50eb61d9e9cdc093e5b0abbd8.tar.gz
linux-next-76a07b89197f06d50eb61d9e9cdc093e5b0abbd8.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git
-rw-r--r--kernel/workqueue.c134
-rw-r--r--tools/workqueue/wq_dump.py10
-rw-r--r--tools/workqueue/wq_monitor.py6
3 files changed, 129 insertions, 21 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 78f25afb4a9d..784aeea768b6 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -226,6 +226,8 @@ struct worker_pool {
/* L: hash of busy workers */
struct worker *manager; /* L: purely informational */
+ /* L: last worker woken by kick_pool() */
+ struct worker *last_woken_worker;
struct list_head workers; /* A: attached workers */
struct ida worker_ida; /* worker IDs for task name */
@@ -1258,19 +1260,27 @@ static void kick_bh_pool(struct worker_pool *pool)
}
/**
- * kick_pool - wake up an idle worker if necessary
+ * kick_pool_pick - select an idle worker to kick, deferring the wakeup
* @pool: pool to kick
+ * @wakep: out-param, set to the task to wake after pool->lock is dropped
*
- * @pool may have pending work items. Wake up worker if necessary. Returns
- * whether a worker was woken up.
+ * Like kick_pool() but, for a regular (non-BH) pool, returns the picked
+ * worker's task via @wakep instead of waking it, so the caller can issue the
+ * wakeup after dropping pool->lock (the wakeup takes rq->lock). Worker
+ * selection, wake_cpu setup and the BH kick still happen under the lock.
+ * Returns whether a worker was selected or kicked.
+ *
+ * Must be called with @pool->lock held.
*/
-static bool kick_pool(struct worker_pool *pool)
+static bool kick_pool_pick(struct worker_pool *pool, struct task_struct **wakep)
{
struct worker *worker = first_idle_worker(pool);
struct task_struct *p;
lockdep_assert_held(&pool->lock);
+ *wakep = NULL;
+
if (!need_more_worker(pool) || !worker)
return false;
@@ -1310,10 +1320,30 @@ static bool kick_pool(struct worker_pool *pool)
}
}
#endif
- wake_up_process(p);
+ /* Track the last idle worker woken, used for stall diagnostics. */
+ pool->last_woken_worker = worker;
+
+ *wakep = p;
return true;
}
+/**
+ * kick_pool - wake up an idle worker if necessary
+ * @pool: pool to kick
+ *
+ * @pool may have pending work items. Wake up worker if necessary. Returns
+ * whether a worker was woken up.
+ */
+static bool kick_pool(struct worker_pool *pool)
+{
+ struct task_struct *p;
+ bool kicked = kick_pool_pick(pool, &p);
+
+ if (p)
+ wake_up_process(p);
+ return kicked;
+}
+
#ifdef CONFIG_WQ_CPU_INTENSIVE_REPORT
/*
@@ -1505,7 +1535,11 @@ void wq_worker_tick(struct task_struct *task)
if (!pwq)
return;
- pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
+ /*
+ * @pwq is shared across CPUs for unbound wqs and this advisory stat is
+ * bumped outside pool->lock, so the update is intentionally racy.
+ */
+ data_race(pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC);
if (!wq_cpu_intensive_thresh_us)
return;
@@ -2277,6 +2311,7 @@ static void __queue_work(int cpu, struct workqueue_struct *wq,
{
struct pool_workqueue *pwq;
struct worker_pool *last_pool, *pool;
+ struct task_struct *wake_task = NULL;
unsigned int work_flags;
unsigned int req_cpu = cpu;
@@ -2399,7 +2434,7 @@ retry:
trace_workqueue_activate_work(work);
insert_work(pwq, work, &pool->worklist, work_flags);
- kick_pool(pool);
+ kick_pool_pick(pool, &wake_task);
} else {
work_flags |= WORK_STRUCT_INACTIVE;
insert_work(pwq, work, &pwq->inactive_works, work_flags);
@@ -2407,6 +2442,8 @@ retry:
out:
raw_spin_unlock(&pool->lock);
+ if (wake_task)
+ wake_up_process(wake_task);
rcu_read_unlock();
}
@@ -2948,6 +2985,13 @@ static void set_worker_dying(struct worker *worker, struct list_head *list)
pool->nr_workers--;
pool->nr_idle--;
+ /*
+ * Clear last_woken_worker if it points to this worker, so that
+ * show_cpu_pool_busy_workers() cannot dereference a freed worker.
+ */
+ if (pool->last_woken_worker == worker)
+ pool->last_woken_worker = NULL;
+
worker->flags |= WORKER_DIE;
list_move(&worker->entry, list);
@@ -3227,6 +3271,7 @@ __acquires(&pool->lock)
{
struct pool_workqueue *pwq = get_work_pwq(work);
struct worker_pool *pool = worker->pool;
+ struct task_struct *wake_task = NULL;
unsigned long work_data;
int lockdep_start_depth, rcu_start_depth;
bool bh_draining = pool->flags & POOL_BH_DRAINING;
@@ -3280,8 +3325,11 @@ __acquires(&pool->lock)
* since nr_running would always be >= 1 at this point. This is used to
* chain execution of the pending work items for WORKER_NOT_RUNNING
* workers such as the UNBOUND and CPU_INTENSIVE ones.
+ *
+ * Select the worker under pool->lock; the wakeup is deferred until
+ * after the lock is dropped, guarded by the rcu_read_lock() below.
*/
- kick_pool(pool);
+ kick_pool_pick(pool, &wake_task);
/*
* Record the last pool and clear PENDING which should be the last
@@ -3292,7 +3340,12 @@ __acquires(&pool->lock)
set_work_pool_and_clear_pending(work, pool->id, pool_offq_flags(pool));
pwq->stats[PWQ_STAT_STARTED]++;
+
+ rcu_read_lock();
raw_spin_unlock_irq(&pool->lock);
+ if (wake_task)
+ wake_up_process(wake_task);
+ rcu_read_unlock();
rcu_start_depth = rcu_preempt_depth();
lockdep_start_depth = lockdep_depth(current);
@@ -7693,20 +7746,58 @@ module_param_named(panic_on_stall_time, wq_panic_on_stall_time, uint, 0644);
MODULE_PARM_DESC(panic_on_stall_time, "Panic if stall exceeds this many seconds (0=disabled)");
/*
- * Show workers that might prevent the processing of pending work items.
- * A busy worker that is not running on the CPU (e.g. sleeping in
- * wait_event_idle() with PF_WQ_WORKER cleared) can stall the pool just as
- * effectively as a CPU-bound one, so dump every in-flight worker.
+ * Report that a pool has no worker in running state, which is a sign that the
+ * pool may be stuck. Print pool info. Must be called with pool->lock held and
+ * inside a printk_deferred_enter/exit region.
+ */
+static void show_pool_no_running_worker(struct worker_pool *pool)
+{
+ lockdep_assert_held(&pool->lock);
+
+ printk_deferred_enter();
+ pr_info("pool %d: no worker in running state, cpu=%d is %s (nr_workers=%d nr_idle=%d)\n",
+ pool->id, pool->cpu,
+ idle_cpu(pool->cpu) ? "idle" : "busy",
+ pool->nr_workers, pool->nr_idle);
+ pr_info("The pool might have trouble waking an idle worker.\n");
+ /*
+ * last_woken_worker and its task are valid here: set_worker_dying()
+ * clears it under pool->lock before setting WORKER_DIE, so if
+ * last_woken_worker is non-NULL the kthread has not yet exited and
+ * worker->task is still alive.
+ */
+ if (pool->last_woken_worker) {
+ pr_info("Backtrace of last woken worker:\n");
+ sched_show_task(pool->last_woken_worker->task);
+ } else {
+ pr_info("Last woken worker empty\n");
+ }
+ printk_deferred_exit();
+}
+
+/*
+ * Show running workers that might prevent the processing of pending work items.
+ * If no running worker is found, the pool may be stuck waiting for an idle
+ * worker to be woken, so report the pool state and the last woken worker.
*/
static void show_cpu_pool_busy_workers(struct worker_pool *pool)
{
+ bool found_running = false;
struct worker *worker;
unsigned long irq_flags;
- int bkt;
+ int cpu, bkt;
raw_spin_lock_irqsave(&pool->lock, irq_flags);
+ /* Snapshot cpu inside the lock to safely use it after unlock. */
+ cpu = pool->cpu;
+
hash_for_each(pool->busy_hash, bkt, worker, hentry) {
+ /* Skip workers that are not actively running on the CPU. */
+ if (!task_is_running(worker->task))
+ continue;
+
+ found_running = true;
/*
* Defer printing to avoid deadlocks in console
* drivers that queue work while holding locks
@@ -7720,7 +7811,24 @@ static void show_cpu_pool_busy_workers(struct worker_pool *pool)
printk_deferred_exit();
}
+ /*
+ * If no running worker was found, the pool is likely stuck. Print pool
+ * state and the backtrace of the last woken worker, which is the prime
+ * suspect for the stall.
+ */
+ if (!found_running)
+ show_pool_no_running_worker(pool);
+
raw_spin_unlock_irqrestore(&pool->lock, irq_flags);
+
+ /*
+ * Trigger a backtrace on the stalled CPU to capture what it is
+ * currently executing. Skip an offline CPU, whose NMI is never acked
+ * and would make the backtrace busy-wait until it times out. Done
+ * after releasing the lock to avoid issues with NMI delivery.
+ */
+ if (!found_running && cpu_online(cpu))
+ trigger_single_cpu_backtrace(cpu);
}
static void show_cpu_pools_busy_workers(void)
diff --git a/tools/workqueue/wq_dump.py b/tools/workqueue/wq_dump.py
index ce4161f52f2f..a0c72237531f 100644
--- a/tools/workqueue/wq_dump.py
+++ b/tools/workqueue/wq_dump.py
@@ -46,6 +46,11 @@ each workqueue:
import sys
+import argparse
+parser = argparse.ArgumentParser(description=desc,
+ formatter_class=argparse.RawTextHelpFormatter)
+args = parser.parse_args()
+
import drgn
from drgn.helpers.linux.list import list_for_each_entry,list_empty
from drgn.helpers.linux.percpu import per_cpu_ptr
@@ -53,11 +58,6 @@ from drgn.helpers.linux.cpumask import for_each_cpu,for_each_possible_cpu
from drgn.helpers.linux.nodemask import for_each_node
from drgn.helpers.linux.idr import idr_for_each
-import argparse
-parser = argparse.ArgumentParser(description=desc,
- formatter_class=argparse.RawTextHelpFormatter)
-args = parser.parse_args()
-
def err(s):
print(s, file=sys.stderr, flush=True)
sys.exit(1)
diff --git a/tools/workqueue/wq_monitor.py b/tools/workqueue/wq_monitor.py
index 9e964c5be40c..7f47fa398e3c 100644
--- a/tools/workqueue/wq_monitor.py
+++ b/tools/workqueue/wq_monitor.py
@@ -37,9 +37,6 @@ import re
import time
import json
-import drgn
-from drgn.helpers.linux.list import list_for_each_entry
-
import argparse
parser = argparse.ArgumentParser(description=desc,
formatter_class=argparse.RawTextHelpFormatter)
@@ -51,6 +48,9 @@ parser.add_argument('-j', '--json', action='store_true',
help='Output in json')
args = parser.parse_args()
+import drgn
+from drgn.helpers.linux.list import list_for_each_entry
+
workqueues = prog['workqueues']
WQ_UNBOUND = prog['WQ_UNBOUND']