public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/5] workqueue: Debugging improvements
@ 2023-03-07 12:53 Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 1/5] workqueue: Fix hung time report of worker pools Petr Mladek
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Petr Mladek @ 2023-03-07 12:53 UTC (permalink / raw)
  To: Tejun Heo; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel, Petr Mladek

The workqueue watchdog provides a lot of information when a stall is
detected. The report says a lot about what workqueues and worker pools
are active and what is being blocked. Unfortunately, it does not provide
much information about what caused the stall.

In particular, it did not help me to get root of the following problems:

    + New workers were not created because the system reached PID limit.
      Admins limited it too much in a cloud.

    + A networking driver was not loaded because systemd killed modprobe
      when switching the root from initrd to the booted system.

      It was surprisingly quite reproducible. Interrupts are not handled
      immediately in kernel code. The wait in kthread_create_on_node()
      was one of few locations. So the race window evidently was not
      trivial.


1st patch fixes a misleading "hung" time report.

2nd, 3rd, and 4rd patches add warnings into create_worker() and
create_rescuer().

5th patch adds printing bracktraces of CPU-bound workers that might
block CPU-bound workqueues. The candidate is well defined to keep
the number of backtraces small. It always printed only the right one
during my testing.


The first 4 patches would have helped me to debug the real problems
that I met.

The 5th patch is theoretical. I did not see this case in practice.
But it looks realistic enough. And it worked very well when I
simulated the problem. IMHO, it should be pretty useful.


Changes against v1:

  + Used pr_err_once() instead of the complicated code synchronizing
    the error messages with the watchdog interval.

    I tried also the standard ratelimit API was not really usable.
    The synchronization with the watchdog was bad and the error
    messages touched/restarted the watchdog timestamp a non-reliable
    way. In fact, we wanted something like reset-able pr_once().
    

  + Added "cpu_stall" into struct worker_pool.

  + Renamed the functions for printing backtraces of hogging CPU-bound
    workers and cleaned up the code.


Petr Mladek (5):
  workqueue: Fix hung time report of worker pools
  workqueue: Warn when a new worker could not be created
  workqueue: Interrupted create_worker() is not a repeated event
  workqueue: Warn when a rescuer could not be created
  workqueue: Print backtraces from CPUs with hung CPU bound workqueues

 kernel/workqueue.c | 102 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 95 insertions(+), 7 deletions(-)

-- 
2.35.3


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v2 1/5] workqueue: Fix hung time report of worker pools
  2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
@ 2023-03-07 12:53 ` Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 2/5] workqueue: Warn when a new worker could not be created Petr Mladek
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Petr Mladek @ 2023-03-07 12:53 UTC (permalink / raw)
  To: Tejun Heo; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel, Petr Mladek

The workqueue watchdog prints a warning when there is no progress in
a worker pool. Where the progress means that the pool started processing
a pending work item.

Note that it is perfectly fine to process work items much longer.
The progress should be guaranteed by waking up or creating idle
workers.

show_one_worker_pool() prints state of non-idle worker pool. It shows
a delay since the last pool->watchdog_ts.

The timestamp is updated when a first pending work is queued in
__queue_work(). Also it is updated when a work is dequeued for
processing in worker_thread() and rescuer_thread().

The delay is misleading when there is no pending work item. In this
case it shows how long the last work item is being proceed. Show
zero instead. There is no stall if there is no pending work.

Fixes: 82607adcf9cdf40fb7b ("workqueue: implement lockup detector")
Signed-off-by: Petr Mladek <pmladek@suse.com>
---
 kernel/workqueue.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index b8b541caed48..2be9b0ecf22c 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -5002,10 +5002,16 @@ static void show_one_worker_pool(struct worker_pool *pool)
 	struct worker *worker;
 	bool first = true;
 	unsigned long flags;
+	unsigned long hung = 0;
 
 	raw_spin_lock_irqsave(&pool->lock, flags);
 	if (pool->nr_workers == pool->nr_idle)
 		goto next_pool;
+
+	/* How long the first pending work is waiting for a worker. */
+	if (!list_empty(&pool->worklist))
+		hung = jiffies_to_msecs(jiffies - pool->watchdog_ts) / 1000;
+
 	/*
 	 * Defer printing to avoid deadlocks in console drivers that
 	 * queue work while holding locks also taken in their write
@@ -5014,9 +5020,7 @@ static void show_one_worker_pool(struct worker_pool *pool)
 	printk_deferred_enter();
 	pr_info("pool %d:", pool->id);
 	pr_cont_pool_info(pool);
-	pr_cont(" hung=%us workers=%d",
-		jiffies_to_msecs(jiffies - pool->watchdog_ts) / 1000,
-		pool->nr_workers);
+	pr_cont(" hung=%lus workers=%d", hung, pool->nr_workers);
 	if (pool->manager)
 		pr_cont(" manager: %d",
 			task_pid_nr(pool->manager->task));
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 2/5] workqueue: Warn when a new worker could not be created
  2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 1/5] workqueue: Fix hung time report of worker pools Petr Mladek
@ 2023-03-07 12:53 ` Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 3/5] workqueue: Interrupted create_worker() is not a repeated event Petr Mladek
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Petr Mladek @ 2023-03-07 12:53 UTC (permalink / raw)
  To: Tejun Heo; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel, Petr Mladek

The workqueue watchdog reports a lockup when there was not any progress
in the worker pool for a long time. The progress means that a pending
work item starts being proceed.

The progress is guaranteed by using idle workers or creating new workers
for pending work items.

There are several reasons why a new worker could not be created:

   + there is not enough memory

   + there is no free pool ID (IDR API)

   + the system reached PID limit

   + the process creating the new worker was interrupted

   + the last idle worker (manager) has not been scheduled for a long
     time. It was not able to even start creating the kthread.

None of these failures is reported at the moment. The only clue is that
show_one_worker_pool() prints that there is a manager. It is the last
idle worker that is responsible for creating a new one. But it is not
clear if create_worker() is failing and why.

Make the debugging easier by printing errors in create_worker().

The error code is important, especially from kthread_create_on_node().
It helps to distinguish the various reasons. For example, reaching
memory limit (-ENOMEM), other system limits (-EAGAIN), or process
interrupted (-EINTR).

Use pr_once() to avoid repeating the same error every CREATE_COOLDOWN
for each stuck worker pool.

Ratelimited printk() might be better. It would help to know if the problem
remains. It would be more clear if the create_worker() errors and workqueue
stalls are related. Also old messages might get lost when the internal log
buffer is full. The problem is that printk() might touch the watchdog.
For example, see touch_nmi_watchdog() in serial8250_console_write().
It would require synchronization of the begin and length of the ratelimit
interval with the workqueue watchdog. Otherwise, the error messages
might break the watchdog. This does not look worth the complexity.

Signed-off-by: Petr Mladek <pmladek@suse.com>
---
 kernel/workqueue.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 2be9b0ecf22c..36ad9a4d65e4 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -1938,12 +1938,17 @@ static struct worker *create_worker(struct worker_pool *pool)
 
 	/* ID is needed to determine kthread name */
 	id = ida_alloc(&pool->worker_ida, GFP_KERNEL);
-	if (id < 0)
+	if (id < 0) {
+		pr_err_once("workqueue: Failed to allocate a worker ID: %pe\n",
+			    ERR_PTR(id));
 		return NULL;
+	}
 
 	worker = alloc_worker(pool->node);
-	if (!worker)
+	if (!worker) {
+		pr_err_once("workqueue: Failed to allocate a worker\n");
 		goto fail;
+	}
 
 	worker->id = id;
 
@@ -1955,8 +1960,11 @@ static struct worker *create_worker(struct worker_pool *pool)
 
 	worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
 					      "kworker/%s", id_buf);
-	if (IS_ERR(worker->task))
+	if (IS_ERR(worker->task)) {
+		pr_err_once("workqueue: Failed to create a worker thread: %pe",
+			    worker->task);
 		goto fail;
+	}
 
 	set_user_nice(worker->task, pool->attrs->nice);
 	kthread_bind_mask(worker->task, pool->attrs->cpumask);
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 3/5] workqueue: Interrupted create_worker() is not a repeated event
  2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 1/5] workqueue: Fix hung time report of worker pools Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 2/5] workqueue: Warn when a new worker could not be created Petr Mladek
@ 2023-03-07 12:53 ` Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 4/5] workqueue: Warn when a rescuer could not be created Petr Mladek
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Petr Mladek @ 2023-03-07 12:53 UTC (permalink / raw)
  To: Tejun Heo; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel, Petr Mladek

kthread_create_on_node() might get interrupted(). It is rare but realistic.
For example, when an unbound workqueue is allocated in module_init()
callback. It is done in the context of the "modprobe" process. And,
for example, systemd might kill pending processes when switching root
from initrd to the booted system.

The interrupt is a one-off event and the race might be hard to reproduce.
It is always worth printing.

Signed-off-by: Petr Mladek <pmladek@suse.com>
---
 kernel/workqueue.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 36ad9a4d65e4..16439d79d164 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -1961,8 +1961,13 @@ static struct worker *create_worker(struct worker_pool *pool)
 	worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
 					      "kworker/%s", id_buf);
 	if (IS_ERR(worker->task)) {
-		pr_err_once("workqueue: Failed to create a worker thread: %pe",
-			    worker->task);
+		if (PTR_ERR(worker->task) == -EINTR) {
+			pr_err("workqueue: Interrupted when creating a worker thread \"kworker/%s\"\n",
+			       id_buf);
+		} else {
+			pr_err_once("workqueue: Failed to create a worker thread: %pe",
+				    worker->task);
+		}
 		goto fail;
 	}
 
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 4/5] workqueue: Warn when a rescuer could not be created
  2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
                   ` (2 preceding siblings ...)
  2023-03-07 12:53 ` [PATCH v2 3/5] workqueue: Interrupted create_worker() is not a repeated event Petr Mladek
@ 2023-03-07 12:53 ` Petr Mladek
  2023-03-07 12:53 ` [PATCH v2 5/5] workqueue: Print backtraces from CPUs with hung CPU bound workqueues Petr Mladek
  2023-03-17 22:04 ` [PATCH v2 0/5] workqueue: Debugging improvements Tejun Heo
  5 siblings, 0 replies; 7+ messages in thread
From: Petr Mladek @ 2023-03-07 12:53 UTC (permalink / raw)
  To: Tejun Heo; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel, Petr Mladek

Rescuers are created when a workqueue with WQ_MEM_RECLAIM is allocated.
It typically happens during the system boot.

systemd switches the root filesystem from initrd to the booted system
during boot. It kills processes that block the switch for too long.
One of the process might be modprobe that tries to create a workqueue.

These problems are hard to reproduce. Also alloc_workqueue() does not
pass the error code. Make the debugging easier by printing an error,
similar to create_worker().

Signed-off-by: Petr Mladek <pmladek@suse.com>
---
 kernel/workqueue.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 16439d79d164..cce342defc69 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -4393,13 +4393,18 @@ static int init_rescuer(struct workqueue_struct *wq)
 		return 0;
 
 	rescuer = alloc_worker(NUMA_NO_NODE);
-	if (!rescuer)
+	if (!rescuer) {
+		pr_err("workqueue: Failed to allocate a rescuer for wq \"%s\"\n",
+		       wq->name);
 		return -ENOMEM;
+	}
 
 	rescuer->rescue_wq = wq;
 	rescuer->task = kthread_create(rescuer_thread, rescuer, "%s", wq->name);
 	if (IS_ERR(rescuer->task)) {
 		ret = PTR_ERR(rescuer->task);
+		pr_err("workqueue: Failed to create a rescuer kthread for wq \"%s\": %pe",
+		       wq->name, ERR_PTR(ret));
 		kfree(rescuer);
 		return ret;
 	}
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 5/5] workqueue: Print backtraces from CPUs with hung CPU bound workqueues
  2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
                   ` (3 preceding siblings ...)
  2023-03-07 12:53 ` [PATCH v2 4/5] workqueue: Warn when a rescuer could not be created Petr Mladek
@ 2023-03-07 12:53 ` Petr Mladek
  2023-03-17 22:04 ` [PATCH v2 0/5] workqueue: Debugging improvements Tejun Heo
  5 siblings, 0 replies; 7+ messages in thread
From: Petr Mladek @ 2023-03-07 12:53 UTC (permalink / raw)
  To: Tejun Heo; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel, Petr Mladek

The workqueue watchdog reports a lockup when there was not any progress
in the worker pool for a long time. The progress means that a pending
work item starts being proceed.

Worker pools for unbound workqueues always wake up an idle worker and
try to process the work immediately. The last idle worker has to create
new worker first. The stall might happen only when a new worker could
not be created in which case an error should get printed. Another problem
might be too high load. In this case, workers are victims of a global
system problem.

Worker pools for CPU bound workqueues are designed for lightweight
work items that do not need much CPU time. They are proceed one by
one on a single worker. New worker is used only when a work is sleeping.
It creates one additional scenario. The stall might happen when
the CPU-bound workqueue is used for CPU-intensive work.

More precisely, the stall is detected when a CPU-bound worker is in
the TASK_RUNNING state for too long. In this case, it might be useful
to see the backtrace from the problematic worker.

The information how long a worker is in the running state is not available.
But the CPU-bound worker pools do not have many workers in the running
state by definition. And only few pools are typically blocked.

It should be acceptable to print backtraces from all workers in
TASK_RUNNING state in the stalled worker pools. The number of false
positives should be very low.

Signed-off-by: Petr Mladek <pmladek@suse.com>
---
 kernel/workqueue.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index cce342defc69..db6be8df5ddc 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -49,6 +49,7 @@
 #include <linux/moduleparam.h>
 #include <linux/uaccess.h>
 #include <linux/sched/isolation.h>
+#include <linux/sched/debug.h>
 #include <linux/nmi.h>
 #include <linux/kvm_para.h>
 
@@ -141,6 +142,8 @@ enum {
  * WR: wq->mutex protected for writes.  RCU protected for reads.
  *
  * MD: wq_mayday_lock protected.
+ *
+ * WD: Used internally by the watchdog.
  */
 
 /* struct worker is defined in workqueue_internal.h */
@@ -153,6 +156,7 @@ struct worker_pool {
 	unsigned int		flags;		/* X: flags */
 
 	unsigned long		watchdog_ts;	/* L: watchdog timestamp */
+	bool			cpu_stall;	/* WD: stalled cpu bound pool */
 
 	/*
 	 * The counter is incremented in a process context on the associated CPU
@@ -5978,6 +5982,57 @@ static struct timer_list wq_watchdog_timer;
 static unsigned long wq_watchdog_touched = INITIAL_JIFFIES;
 static DEFINE_PER_CPU(unsigned long, wq_watchdog_touched_cpu) = INITIAL_JIFFIES;
 
+/*
+ * Show workers that might prevent the processing of pending work items.
+ * The only candidates are CPU-bound workers in the running state.
+ * Pending work items should be handled by another idle worker
+ * in all other situations.
+ */
+static void show_cpu_pool_hog(struct worker_pool *pool)
+{
+	struct worker *worker;
+	unsigned long flags;
+	int bkt;
+
+	raw_spin_lock_irqsave(&pool->lock, flags);
+
+	hash_for_each(pool->busy_hash, bkt, worker, hentry) {
+		if (task_is_running(worker->task)) {
+			/*
+			 * Defer printing to avoid deadlocks in console
+			 * drivers that queue work while holding locks
+			 * also taken in their write paths.
+			 */
+			printk_deferred_enter();
+
+			pr_info("pool %d:\n", pool->id);
+			sched_show_task(worker->task);
+
+			printk_deferred_exit();
+		}
+	}
+
+	raw_spin_unlock_irqrestore(&pool->lock, flags);
+}
+
+static void show_cpu_pools_hogs(void)
+{
+	struct worker_pool *pool;
+	int pi;
+
+	pr_info("Showing backtraces of running workers in stalled CPU-bound worker pools:\n");
+
+	rcu_read_lock();
+
+	for_each_pool(pool, pi) {
+		if (pool->cpu_stall)
+			show_cpu_pool_hog(pool);
+
+	}
+
+	rcu_read_unlock();
+}
+
 static void wq_watchdog_reset_touched(void)
 {
 	int cpu;
@@ -5991,6 +6046,7 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
 {
 	unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ;
 	bool lockup_detected = false;
+	bool cpu_pool_stall = false;
 	unsigned long now = jiffies;
 	struct worker_pool *pool;
 	int pi;
@@ -6003,6 +6059,7 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
 	for_each_pool(pool, pi) {
 		unsigned long pool_ts, touched, ts;
 
+		pool->cpu_stall = false;
 		if (list_empty(&pool->worklist))
 			continue;
 
@@ -6027,11 +6084,17 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
 		/* did we stall? */
 		if (time_after(now, ts + thresh)) {
 			lockup_detected = true;
+			if (pool->cpu >= 0) {
+				pool->cpu_stall = true;
+				cpu_pool_stall = true;
+			}
 			pr_emerg("BUG: workqueue lockup - pool");
 			pr_cont_pool_info(pool);
 			pr_cont(" stuck for %us!\n",
 				jiffies_to_msecs(now - pool_ts) / 1000);
 		}
+
+
 	}
 
 	rcu_read_unlock();
@@ -6039,6 +6102,9 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
 	if (lockup_detected)
 		show_all_workqueues();
 
+	if (cpu_pool_stall)
+		show_cpu_pools_hogs();
+
 	wq_watchdog_reset_touched();
 	mod_timer(&wq_watchdog_timer, jiffies + thresh);
 }
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v2 0/5] workqueue: Debugging improvements
  2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
                   ` (4 preceding siblings ...)
  2023-03-07 12:53 ` [PATCH v2 5/5] workqueue: Print backtraces from CPUs with hung CPU bound workqueues Petr Mladek
@ 2023-03-17 22:04 ` Tejun Heo
  5 siblings, 0 replies; 7+ messages in thread
From: Tejun Heo @ 2023-03-17 22:04 UTC (permalink / raw)
  To: Petr Mladek; +Cc: Lai Jiangshan, Michal Koutny, linux-kernel

On Tue, Mar 07, 2023 at 01:53:30PM +0100, Petr Mladek wrote:
> Petr Mladek (5):
>   workqueue: Fix hung time report of worker pools
>   workqueue: Warn when a new worker could not be created
>   workqueue: Interrupted create_worker() is not a repeated event
>   workqueue: Warn when a rescuer could not be created
>   workqueue: Print backtraces from CPUs with hung CPU bound workqueues

Applied to wq/for-6.4.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2023-03-17 22:04 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-03-07 12:53 [PATCH v2 0/5] workqueue: Debugging improvements Petr Mladek
2023-03-07 12:53 ` [PATCH v2 1/5] workqueue: Fix hung time report of worker pools Petr Mladek
2023-03-07 12:53 ` [PATCH v2 2/5] workqueue: Warn when a new worker could not be created Petr Mladek
2023-03-07 12:53 ` [PATCH v2 3/5] workqueue: Interrupted create_worker() is not a repeated event Petr Mladek
2023-03-07 12:53 ` [PATCH v2 4/5] workqueue: Warn when a rescuer could not be created Petr Mladek
2023-03-07 12:53 ` [PATCH v2 5/5] workqueue: Print backtraces from CPUs with hung CPU bound workqueues Petr Mladek
2023-03-17 22:04 ` [PATCH v2 0/5] workqueue: Debugging improvements Tejun Heo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox