mirror of
https://github.com/torvalds/linux
synced 2024-09-06 18:01:05 +00:00
workqueue changes for v6.4-rc1
Mostly changes from Petr to improve warning and error reporting. Workqueue now reports more of the relevant failures with better context which should help debugging. -----BEGIN PGP SIGNATURE----- iIQEABYIACwWIQTfIjM1kS57o3GsC/uxYfJx3gVYGQUCZErbOg4cdGpAa2VybmVs Lm9yZwAKCRCxYfJx3gVYGfipAP9BXrUBI99uZL7XjSEe6rLWa0STODyWh67ikR+0 nUO22QEA+ttt5ecLZyopYB18Le06VBBW7J5cs0Cg83bCBTGb4wE= =gHbc -----END PGP SIGNATURE----- Merge tag 'wq-for-6.4' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq Pull workqueue updates from Tejun Heo: "Mostly changes from Petr to improve warning and error reporting. Workqueue now reports more of the relevant failures with better context which should help debugging" * tag 'wq-for-6.4' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq: workqueue: Introduce show_freezable_workqueues workqueue: Print backtraces from CPUs with hung CPU bound workqueues workqueue: Warn when a rescuer could not be created workqueue: Interrupted create_worker() is not a repeated event workqueue: Warn when a new worker could not be created workqueue: Fix hung time report of worker pools workqueue: Simplify a pr_warn() call in wq_select_unbound_cpu() MAINTAINERS: Add workqueue_internal.h to the WORKQUEUE entry
This commit is contained in:
commit
cd546fa325
|
@ -22743,6 +22743,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git
|
|||
F: Documentation/core-api/workqueue.rst
|
||||
F: include/linux/workqueue.h
|
||||
F: kernel/workqueue.c
|
||||
F: kernel/workqueue_internal.h
|
||||
|
||||
WWAN DRIVERS
|
||||
M: Loic Poulain <loic.poulain@linaro.org>
|
||||
|
|
|
@ -472,6 +472,7 @@ extern unsigned int work_busy(struct work_struct *work);
|
|||
extern __printf(1, 2) void set_worker_desc(const char *fmt, ...);
|
||||
extern void print_worker_info(const char *log_lvl, struct task_struct *task);
|
||||
extern void show_all_workqueues(void);
|
||||
extern void show_freezable_workqueues(void);
|
||||
extern void show_one_workqueue(struct workqueue_struct *wq);
|
||||
extern void wq_worker_comm(char *buf, size_t size, struct task_struct *task);
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ static int try_to_freeze_tasks(bool user_only)
|
|||
todo - wq_busy, wq_busy);
|
||||
|
||||
if (wq_busy)
|
||||
show_all_workqueues();
|
||||
show_freezable_workqueues();
|
||||
|
||||
if (!wakeup || pm_debug_messages_on) {
|
||||
read_lock(&tasklist_lock);
|
||||
|
|
|
@ -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
|
||||
|
@ -1392,15 +1396,13 @@ static bool is_chained_work(struct workqueue_struct *wq)
|
|||
*/
|
||||
static int wq_select_unbound_cpu(int cpu)
|
||||
{
|
||||
static bool printed_dbg_warning;
|
||||
int new_cpu;
|
||||
|
||||
if (likely(!wq_debug_force_rr_cpu)) {
|
||||
if (cpumask_test_cpu(cpu, wq_unbound_cpumask))
|
||||
return cpu;
|
||||
} else if (!printed_dbg_warning) {
|
||||
pr_warn("workqueue: round-robin CPU selection forced, expect performance impact\n");
|
||||
printed_dbg_warning = true;
|
||||
} else {
|
||||
pr_warn_once("workqueue: round-robin CPU selection forced, expect performance impact\n");
|
||||
}
|
||||
|
||||
if (cpumask_empty(wq_unbound_cpumask))
|
||||
|
@ -1938,12 +1940,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 +1962,16 @@ 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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
set_user_nice(worker->task, pool->attrs->nice);
|
||||
kthread_bind_mask(worker->task, pool->attrs->cpumask);
|
||||
|
@ -4380,13 +4395,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;
|
||||
}
|
||||
|
@ -5002,10 +5022,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 +5040,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));
|
||||
|
@ -5041,8 +5065,7 @@ static void show_one_worker_pool(struct worker_pool *pool)
|
|||
/**
|
||||
* show_all_workqueues - dump workqueue state
|
||||
*
|
||||
* Called from a sysrq handler or try_to_freeze_tasks() and prints out
|
||||
* all busy workqueues and pools.
|
||||
* Called from a sysrq handler and prints out all busy workqueues and pools.
|
||||
*/
|
||||
void show_all_workqueues(void)
|
||||
{
|
||||
|
@ -5063,6 +5086,29 @@ void show_all_workqueues(void)
|
|||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* show_freezable_workqueues - dump freezable workqueue state
|
||||
*
|
||||
* Called from try_to_freeze_tasks() and prints out all freezable workqueues
|
||||
* still busy.
|
||||
*/
|
||||
void show_freezable_workqueues(void)
|
||||
{
|
||||
struct workqueue_struct *wq;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
pr_info("Showing freezable workqueues that are still busy:\n");
|
||||
|
||||
list_for_each_entry_rcu(wq, &workqueues, list) {
|
||||
if (!(wq->flags & WQ_FREEZABLE))
|
||||
continue;
|
||||
show_one_workqueue(wq);
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/* used to show worker information through /proc/PID/{comm,stat,status} */
|
||||
void wq_worker_comm(char *buf, size_t size, struct task_struct *task)
|
||||
{
|
||||
|
@ -5962,6 +6008,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;
|
||||
|
@ -5975,6 +6072,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;
|
||||
|
@ -5987,6 +6085,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;
|
||||
|
||||
|
@ -6011,11 +6110,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();
|
||||
|
@ -6023,6 +6128,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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue