mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Breno Leitao <leitao@debian.org>
To: Tejun Heo <tj@kernel.org>, Lai Jiangshan <jiangshanlai@gmail.com>,
	 Andrew Morton <akpm@linux-foundation.org>
Cc: linux-kernel@vger.kernel.org, Omar Sandoval <osandov@osandov.com>,
	 kernel-team@meta.com, Breno Leitao <leitao@debian.org>
Subject: [PATCH 4/4] workqueue: Detect stalled in-flight work items with empty worklist
Date: Wed, 11 Feb 2026 04:29:18 -0800	[thread overview]
Message-ID: <20260211-wqstall_start-at-v1-4-bd9499a18c19@debian.org> (raw)
In-Reply-To: <20260211-wqstall_start-at-v1-0-bd9499a18c19@debian.org>

The workqueue watchdog skips pools with an empty worklist, assuming no
work is pending. However, a single work item that was dequeued and is
now executing on a worker will leave the worklist empty while the worker
is stuck. This means a pool with one hogged worker and no pending work
is invisible to the watchdog.

An example is something like:

  static void stall_work_fn(struct work_struct *work)
  {
        for (;;) {
                mdelay(1000);
                cond_resched();
        }
  }

Fix this by scanning the pool's busy_hash for workers whose
current_start timestamp exceeds the watchdog threshold, independent of
worklist state. The new report_stalled_workers() function iterates all
in-flight workers in a pool and reports each one that has exceeded the
threshold, running as a separate detection path alongside the existing
pool-level last_progress_ts check.

This is an example of the report:

  BUG: workqueue lockup - worker 365:stall_work_fn [wq_stall] stuck in pool cpus=9 node=0 flags=0x0 nice=0 for 33s!
   Showing busy workqueues and worker pools:
   ...

The feature is gated behind a new CONFIG_WQ_WATCHDOG_WORKERS option
(disabled by default) under CONFIG_WQ_WATCHDOG.

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 kernel/workqueue.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 lib/Kconfig.debug  | 12 ++++++++++++
 2 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index e527e763162e6..719e14aa4ac56 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -7659,6 +7659,49 @@ static void wq_watchdog_reset_touched(void)
 		per_cpu(wq_watchdog_touched_cpu, cpu) = jiffies;
 }
 
+#ifdef CONFIG_WQ_WATCHDOG_WORKERS
+/*
+ * Scan all in-flight workers in @pool for stalls. A worker is considered
+ * stalled if its current work item has been executing for longer than @thresh
+ * based on its current_start timestamp. This catches workers that are stuck
+ * regardless of the pool's worklist state or last_progress_ts.
+ */
+static bool report_stalled_workers(struct worker_pool *pool,
+				   unsigned long now,
+				   unsigned long thresh)
+{
+	struct worker *worker;
+	bool stall = false;
+	int bkt;
+
+	/*
+	 * Iterate busy_hash without pool->lock. This is intentionally
+	 * lockless to avoid contention in the watchdog timer path.
+	 * Workers that have been stalled for thresh (typically 30s) are
+	 * unlikely to be transitioning in/out of busy_hash concurrently.
+	 */
+	hash_for_each(pool->busy_hash, bkt, worker, hentry) {
+		if (time_after(now, worker->current_start + thresh)) {
+			pr_emerg("BUG: workqueue lockup - worker ");
+			pr_cont_worker_id(worker);
+			pr_cont(":%ps stuck in pool",
+				worker->current_func);
+			pr_cont_pool_info(pool);
+			pr_cont(" for %us!\n",
+				jiffies_to_msecs(now - worker->current_start) / 1000);
+			stall = true;
+		}
+	}
+	return stall;
+}
+#else
+static bool report_stalled_workers(struct worker_pool *pool,
+				   unsigned long now, unsigned long thresh)
+{
+	return false;
+}
+#endif /* CONFIG_WQ_WATCHDOG_WORKERS */
+
 static void wq_watchdog_timer_fn(struct timer_list *unused)
 {
 	unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ;
@@ -7677,8 +7720,6 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
 		unsigned long pool_ts, touched, ts;
 
 		pool->cpu_stall = false;
-		if (list_empty(&pool->worklist))
-			continue;
 
 		/*
 		 * If a virtual machine is stopped by the host it can look to
@@ -7686,6 +7727,13 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
 		 */
 		kvm_check_and_clear_guest_paused();
 
+		/* Check for individual stalled workers in this pool. */
+		if (report_stalled_workers(pool, now, thresh))
+			lockup_detected = true;
+
+		if (list_empty(&pool->worklist))
+			continue;
+
 		/* get the latest of pool and touched timestamps */
 		if (pool->cpu >= 0)
 			touched = READ_ONCE(per_cpu(wq_watchdog_touched_cpu, pool->cpu));
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ce25a8faf6e9e..dc4bb546b2033 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1320,6 +1320,18 @@ config BOOTPARAM_WQ_STALL_PANIC
 	  This setting can be overridden at runtime via the
 	  workqueue.panic_on_stall kernel parameter.
 
+config WQ_WATCHDOG_WORKERS
+	bool "Detect individual stalled workqueue workers"
+	depends on WQ_WATCHDOG
+	default n
+	help
+	  Say Y here to enable per-worker stall detection. When enabled,
+	  the workqueue watchdog scans all in-flight workers in each pool
+	  and reports any whose current work item has been executing for
+	  longer than the watchdog threshold. This catches stalled workers
+	  even when the pool's worklist is empty or the pool has recently
+	  made forward progress on other work items.
+
 config WQ_CPU_INTENSIVE_REPORT
 	bool "Report per-cpu work items which hog CPU for too long"
 	depends on DEBUG_KERNEL

-- 
2.47.3


  parent reply	other threads:[~2026-02-11 12:32 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-11 12:29 [PATCH 0/4] workqueue: Detect stalled in-flight workers Breno Leitao
2026-02-11 12:29 ` [PATCH 1/4] workqueue: Use POOL_BH instead of WQ_BH when checking pool flags Breno Leitao
2026-02-11 12:29 ` [PATCH 2/4] workqueue: Rename pool->watchdog_ts to pool->last_progress_ts Breno Leitao
2026-02-11 12:29 ` [PATCH 3/4] workqueue: Show in-flight work item duration in stall diagnostics Breno Leitao
2026-02-11 12:29 ` Breno Leitao [this message]
2026-02-11 18:56 ` [PATCH 0/4] workqueue: Detect stalled in-flight workers Tejun Heo
2026-03-04 15:40   ` Breno Leitao
2026-03-04 16:40     ` Tejun Heo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260211-wqstall_start-at-v1-4-bd9499a18c19@debian.org \
    --to=leitao@debian.org \
    --cc=akpm@linux-foundation.org \
    --cc=jiangshanlai@gmail.com \
    --cc=kernel-team@meta.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=osandov@osandov.com \
    --cc=tj@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®