diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-06 14:54:05 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-06 14:54:05 +0100 |
| commit | 76a07b89197f06d50eb61d9e9cdc093e5b0abbd8 (patch) | |
| tree | 061f7a8ca6234faeffd8dfe03911c739b0a231fe | |
| parent | 571450de9f941ec7511b3d2fa0b0fdb4877ba9df (diff) | |
| parent | ecf5aad9a4417fece80890f27a9899db90c9c457 (diff) | |
| download | linux-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.c | 134 | ||||
| -rw-r--r-- | tools/workqueue/wq_dump.py | 10 | ||||
| -rw-r--r-- | tools/workqueue/wq_monitor.py | 6 |
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'] |
