summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-07 14:28:11 +0100
committerMark Brown <broonie@kernel.org>2026-09-07 14:28:11 +0100
commit2b098773cba848d123ea585455980abcf981b7f3 (patch)
treea8fbade89a0c72eb9b8ce4b362271b68a43abc9e
parentf32faa86e75dcba12b00a82a99028fe9ad4b7f4b (diff)
parent8bbd858c4710ff90491365c09209d9d6f70ced77 (diff)
downloadlinux-next-2b098773cba848d123ea585455980abcf981b7f3.tar.gz
linux-next-2b098773cba848d123ea585455980abcf981b7f3.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git
-rw-r--r--include/trace/events/workqueue.h146
-rw-r--r--kernel/workqueue.c192
2 files changed, 251 insertions, 87 deletions
diff --git a/include/trace/events/workqueue.h b/include/trace/events/workqueue.h
index b0de2bc9ed52..5b820ff8f81a 100644
--- a/include/trace/events/workqueue.h
+++ b/include/trace/events/workqueue.h
@@ -9,6 +9,7 @@
#include <linux/workqueue.h>
struct pool_workqueue;
+struct worker_pool;
/**
* workqueue_queue_work - called when a work gets queued
@@ -126,7 +127,152 @@ TRACE_EVENT(workqueue_execute_end,
TP_printk("work struct %p: function %ps", __entry->work, __entry->function)
);
+/**
+ * workqueue_cpu_intensive - called when a work item exceeds cpu_intensive threshold
+ * @pwq: pointer to struct pool_workqueue
+ * @work: pointer to struct work_struct
+ * @function: pointer to worker function
+ * @duration_us: CPU time consumed in microseconds
+ *
+ * This event occurs when a concurrency-managed work item runs for longer
+ * than wq_cpu_intensive_thresh_us without sleeping and is excluded from
+ * concurrency management to prevent stalling other work items.
+ */
+TRACE_EVENT(workqueue_cpu_intensive,
+
+ TP_PROTO(struct pool_workqueue *pwq, struct work_struct *work,
+ work_func_t function, u64 duration_us),
+
+ TP_ARGS(pwq, work, function, duration_us),
+
+ TP_STRUCT__entry(
+ __field( void *, work )
+ __field( void *, function )
+ __string( workqueue, pwq->wq->name )
+ __field( int, cpu )
+ __field( u64, duration_us )
+ ),
+
+ TP_fast_assign(
+ __entry->work = work;
+ __entry->function = function;
+ __assign_str(workqueue);
+ __entry->cpu = pwq->pool->cpu;
+ __entry->duration_us = duration_us;
+ ),
+
+ TP_printk("work struct=%p function=%ps workqueue=%s cpu=%d duration_us=%llu",
+ __entry->work, __entry->function, __get_str(workqueue),
+ __entry->cpu, __entry->duration_us)
+);
+
+/**
+ * workqueue_mayday - called when a pool_workqueue sends mayday to rescuer
+ * @pwq: pointer to struct pool_workqueue
+ *
+ * This event occurs when a worker pool fails to create a new worker and
+ * requests the workqueue's rescuer thread to process pending works.
+ */
+TRACE_EVENT(workqueue_mayday,
+
+ TP_PROTO(struct pool_workqueue *pwq),
+
+ TP_ARGS(pwq),
+
+ TP_STRUCT__entry(
+ __string( workqueue, pwq->wq->name )
+ __field( int, pool_id )
+ __field( int, cpu )
+ __field( int, nr_active )
+ ),
+
+ TP_fast_assign(
+ __assign_str(workqueue);
+ __entry->pool_id = pwq->pool->id;
+ __entry->cpu = pwq->pool->cpu;
+ __entry->nr_active = pwq->nr_active;
+ ),
+
+ TP_printk("workqueue=%s pool_id=%d cpu=%d nr_active=%d",
+ __get_str(workqueue), __entry->pool_id, __entry->cpu,
+ __entry->nr_active)
+);
+
+/**
+ * workqueue_rescued - called when a work item is assigned to a rescuer
+ * @pwq: pointer to struct pool_workqueue
+ * @work: pointer to struct work_struct
+ * @function: pointer to worker function
+ *
+ * This event occurs when a work item is claimed by a rescuer thread
+ * to guarantee forward progress.
+ */
+TRACE_EVENT(workqueue_rescued,
+
+ TP_PROTO(struct pool_workqueue *pwq, struct work_struct *work,
+ work_func_t function),
+
+ TP_ARGS(pwq, work, function),
+
+ TP_STRUCT__entry(
+ __field( void *, work )
+ __field( void *, function )
+ __string( workqueue, pwq->wq->name )
+ __field( int, cpu )
+ ),
+
+ TP_fast_assign(
+ __entry->work = work;
+ __entry->function = function;
+ __assign_str(workqueue);
+ __entry->cpu = pwq->pool->cpu;
+ ),
+
+ TP_printk("work struct=%p function=%ps workqueue=%s cpu=%d",
+ __entry->work, __entry->function, __get_str(workqueue),
+ __entry->cpu)
+);
+
+/**
+ * workqueue_bh_budget_yield - called when a BH worker yields due to budget exhaustion
+ * @pool: pointer to struct worker_pool
+ * @restarts: number of restarts executed
+ * @timeout: whether execution hit the time limit (BH_WORKER_JIFFIES)
+ * @highpri: whether this is a high-priority BH pool
+ *
+ * This event occurs when a bottom-half (BH) worker pool running in softirq
+ * context exhausts its execution time slice or restart limit and must yield
+ * execution.
+ */
+TRACE_EVENT(workqueue_bh_budget_yield,
+
+ TP_PROTO(struct worker_pool *pool, int restarts, bool timeout, bool highpri),
+
+ TP_ARGS(pool, restarts, timeout, highpri),
+
+ TP_STRUCT__entry(
+ __field( int, pool_id )
+ __field( int, cpu )
+ __field( int, restarts )
+ __field( bool, timeout )
+ __field( bool, highpri )
+ ),
+
+ TP_fast_assign(
+ __entry->pool_id = pool->id;
+ __entry->cpu = pool->cpu;
+ __entry->restarts = restarts;
+ __entry->timeout = timeout;
+ __entry->highpri = highpri;
+ ),
+
+ TP_printk("pool_id=%d cpu=%d restarts=%d timeout=%d highpri=%d",
+ __entry->pool_id, __entry->cpu, __entry->restarts,
+ __entry->timeout, __entry->highpri)
+);
+
#endif /* _TRACE_WORKQUEUE_H */
/* This part must be outside protection */
#include <trace/define_trace.h>
+
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 1ae3732a2c51..cc434b0b201d 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -56,6 +56,7 @@
#include <linux/kvm_para.h>
#include <linux/delay.h>
#include <linux/irq_work.h>
+#include <linux/math64.h>
#include "workqueue_internal.h"
@@ -516,6 +517,9 @@ static struct workqueue_attrs *unbound_std_wq_attrs[NR_STD_WORKER_POOLS];
/* I: attributes used when instantiating ordered pools on demand */
static struct workqueue_attrs *ordered_wq_attrs[NR_STD_WORKER_POOLS];
+/* I: attributes of percpu workqueues, which are backed by the static pools */
+static struct workqueue_attrs *percpu_std_wq_attrs[NR_STD_WORKER_POOLS];
+
/*
* I: kthread_worker to release pwq's. pwq release needs to be bounced to a
* process context while holding a pool lock. Bounce to a dedicated kthread
@@ -739,7 +743,7 @@ static int worker_pool_assign_id(struct worker_pool *pool)
}
static struct pool_workqueue __rcu **
-unbound_pwq_slot(struct workqueue_struct *wq, int cpu)
+pwq_slot(struct workqueue_struct *wq, int cpu)
{
if (cpu >= 0)
return per_cpu_ptr(wq->cpu_pwq, cpu);
@@ -748,9 +752,9 @@ unbound_pwq_slot(struct workqueue_struct *wq, int cpu)
}
/* @cpu < 0 for dfl_pwq */
-static struct pool_workqueue *unbound_pwq(struct workqueue_struct *wq, int cpu)
+static struct pool_workqueue *installed_pwq(struct workqueue_struct *wq, int cpu)
{
- return rcu_dereference_check(*unbound_pwq_slot(wq, cpu),
+ return rcu_dereference_check(*pwq_slot(wq, cpu),
lockdep_is_held(&wq_pool_mutex) ||
lockdep_is_held(&wq->mutex));
}
@@ -765,7 +769,7 @@ static struct pool_workqueue *unbound_pwq(struct workqueue_struct *wq, int cpu)
*/
static struct cpumask *unbound_effective_cpumask(struct workqueue_struct *wq)
{
- return unbound_pwq(wq, -1)->pool->attrs->__pod_cpumask;
+ return installed_pwq(wq, -1)->pool->attrs->__pod_cpumask;
}
static unsigned int work_color_to_flags(int color)
@@ -1532,6 +1536,7 @@ void wq_worker_tick(struct task_struct *task)
struct worker *worker = kthread_data(task);
struct pool_workqueue *pwq = worker->current_pwq;
struct worker_pool *pool = worker->pool;
+ u64 delta;
if (!pwq)
return;
@@ -1572,6 +1577,11 @@ void wq_worker_tick(struct task_struct *task)
pwq->stats[PWQ_STAT_CM_WAKEUP]++;
raw_spin_unlock(&pool->lock);
+
+ delta = READ_ONCE(worker->task->se.sum_exec_runtime) - worker->current_at;
+ trace_workqueue_cpu_intensive(pwq, worker->current_work,
+ worker->current_func,
+ div_u64(delta, NSEC_PER_USEC));
}
/**
@@ -3117,6 +3127,7 @@ static void send_mayday(struct pool_workqueue *pwq)
list_add_tail(&pwq->mayday_node, &wq->maydays);
wake_up_process(wq->rescuer->task);
pwq->stats[PWQ_STAT_MAYDAY]++;
+ trace_workqueue_mayday(pwq);
}
}
@@ -3623,6 +3634,7 @@ static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescu
list_for_each_entry_safe_from(work, n, &pool->worklist, entry) {
if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) {
pwq->stats[PWQ_STAT_RESCUED]++;
+ trace_workqueue_rescued(pwq, work, work->func);
/* put the cursor for next search */
list_move_tail(&cursor->entry, &n->entry);
return true;
@@ -3758,6 +3770,9 @@ static void bh_worker(struct worker *worker)
struct worker_pool *pool = worker->pool;
int nr_restarts = BH_WORKER_RESTARTS;
unsigned long end = jiffies + BH_WORKER_JIFFIES;
+ bool budget_exhausted = false;
+ bool timeout = false;
+ int restarts = 0;
worker_lock_callback(pool);
raw_spin_lock_irq(&pool->lock);
@@ -3780,8 +3795,23 @@ static void bh_worker(struct worker *worker)
if (assign_work(work, worker, NULL))
process_scheduled_works(worker);
- } while (keep_working(pool) &&
- --nr_restarts && time_before(jiffies, end));
+
+ if (!keep_working(pool))
+ break;
+
+ if (!--nr_restarts) {
+ budget_exhausted = true;
+ break;
+ }
+
+ if (!time_before(jiffies, end)) {
+ budget_exhausted = true;
+ timeout = true;
+ break;
+ }
+
+ restarts++;
+ } while (1);
worker_set_flags(worker, WORKER_PREP);
done:
@@ -3789,6 +3819,10 @@ done:
kick_pool(pool);
raw_spin_unlock_irq(&pool->lock);
worker_unlock_callback(pool);
+
+ if (budget_exhausted)
+ trace_workqueue_bh_budget_yield(pool, restarts, timeout,
+ pool->attrs->nice == HIGHPRI_NICE_LEVEL);
}
/*
@@ -5385,22 +5419,34 @@ static struct worker_pool *get_percpu_pool(struct workqueue_struct *wq, int cpu)
return &per_cpu_ptr(pools, cpu)[highpri];
}
-/* obtain a pool matching @attr and create a pwq associating the pool and @wq */
+/*
+ * Obtain the pool backing @wq on @cpu and create a pwq associating the two.
+ * A WQ_PERCPU workqueue is backed by the static per-cpu pool of @cpu,
+ * everything else by a pool matching @attrs. @cpu < 0 is always unbound.
+ */
static struct pool_workqueue *alloc_pwq(struct workqueue_struct *wq,
- const struct workqueue_attrs *attrs)
+ const struct workqueue_attrs *attrs,
+ int cpu)
{
struct worker_pool *pool;
struct pool_workqueue *pwq;
lockdep_assert_held(&wq_pool_mutex);
- pool = get_unbound_pool(attrs);
- if (!pool)
- return NULL;
+ WARN_ON_ONCE((wq->flags & WQ_PERCPU) && cpu < 0);
+
+ if (cpu >= 0 && (wq->flags & WQ_PERCPU)) {
+ pool = get_percpu_pool(wq, cpu);
+ } else {
+ pool = get_unbound_pool(attrs);
+ if (!pool)
+ return NULL;
+ }
pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
if (!pwq) {
- put_unbound_pool(pool);
+ if (!is_percpu_pool(pool))
+ put_unbound_pool(pool);
return NULL;
}
@@ -5408,6 +5454,13 @@ static struct pool_workqueue *alloc_pwq(struct workqueue_struct *wq,
return pwq;
}
+/* create a pwq backed by an unbound pool matching @attrs */
+static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
+ const struct workqueue_attrs *attrs)
+{
+ return alloc_pwq(wq, attrs, -1);
+}
+
/**
* wq_calc_pod_cpumask - calculate a wq_attrs' cpumask for a pod
* @attrs: the wq_attrs of the default pwq of the target workqueue
@@ -5437,10 +5490,10 @@ static void wq_calc_pod_cpumask(struct workqueue_attrs *attrs, int cpu)
}
/* install @pwq into @wq and return the old pwq, @cpu < 0 for dfl_pwq */
-static struct pool_workqueue *install_unbound_pwq(struct workqueue_struct *wq,
- int cpu, struct pool_workqueue *pwq)
+static struct pool_workqueue *install_pwq(struct workqueue_struct *wq,
+ int cpu, struct pool_workqueue *pwq)
{
- struct pool_workqueue __rcu **slot = unbound_pwq_slot(wq, cpu);
+ struct pool_workqueue __rcu **slot = pwq_slot(wq, cpu);
struct pool_workqueue *old_pwq;
lockdep_assert_held(&wq_pool_mutex);
@@ -5503,15 +5556,18 @@ apply_wqattrs_prepare(struct workqueue_struct *wq,
/*
* If something goes wrong during CPU up/down, we'll fall back to
- * the default pwq covering whole @attrs->cpumask. Always create
- * it even if we don't use it immediately.
+ * the default pwq covering whole @attrs->cpumask. Create it even
+ * if we don't use it immediately. A percpu workqueue has a pwq on
+ * every possible CPU and never falls back, so it has no default.
*/
copy_workqueue_attrs(new_attrs, attrs);
wqattrs_actualize_cpumask(new_attrs, unbound_cpumask);
cpumask_copy(new_attrs->__pod_cpumask, new_attrs->cpumask);
- ctx->dfl_pwq = alloc_pwq(wq, new_attrs);
- if (!ctx->dfl_pwq)
- goto out_free;
+ if (!(wq->flags & WQ_PERCPU)) {
+ ctx->dfl_pwq = alloc_unbound_pwq(wq, new_attrs);
+ if (!ctx->dfl_pwq)
+ goto out_free;
+ }
for_each_possible_cpu(cpu) {
if (new_attrs->ordered) {
@@ -5519,7 +5575,7 @@ apply_wqattrs_prepare(struct workqueue_struct *wq,
ctx->pwq_tbl[cpu] = ctx->dfl_pwq;
} else {
wq_calc_pod_cpumask(new_attrs, cpu);
- ctx->pwq_tbl[cpu] = alloc_pwq(wq, new_attrs);
+ ctx->pwq_tbl[cpu] = alloc_pwq(wq, new_attrs, cpu);
if (!ctx->pwq_tbl[cpu])
goto out_free;
}
@@ -5561,9 +5617,10 @@ static void apply_wqattrs_commit(struct apply_wqattrs_ctx *ctx)
/* save the previous pwqs and install the new ones */
for_each_possible_cpu(cpu)
- ctx->pwq_tbl[cpu] = install_unbound_pwq(ctx->wq, cpu,
- ctx->pwq_tbl[cpu]);
- ctx->dfl_pwq = install_unbound_pwq(ctx->wq, -1, ctx->dfl_pwq);
+ ctx->pwq_tbl[cpu] = install_pwq(ctx->wq, cpu,
+ ctx->pwq_tbl[cpu]);
+ if (ctx->dfl_pwq)
+ ctx->dfl_pwq = install_pwq(ctx->wq, -1, ctx->dfl_pwq);
/* update node_nr_active->max, which only unbound workqueues have */
if (ctx->wq->flags & WQ_UNBOUND)
@@ -5577,10 +5634,6 @@ static int apply_workqueue_attrs_locked(struct workqueue_struct *wq,
{
struct apply_wqattrs_ctx *ctx;
- /* only unbound workqueues can change attributes */
- if (WARN_ON(!(wq->flags & WQ_UNBOUND)))
- return -EINVAL;
-
ctx = apply_wqattrs_prepare(wq, attrs, wq_unbound_cpumask);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
@@ -5612,6 +5665,10 @@ int apply_workqueue_attrs(struct workqueue_struct *wq,
{
int ret;
+ /* only unbound workqueues can change attributes */
+ if (WARN_ON(!(wq->flags & WQ_UNBOUND)))
+ return -EINVAL;
+
mutex_lock(&wq_pool_mutex);
ret = apply_workqueue_attrs_locked(wq, attrs);
mutex_unlock(&wq_pool_mutex);
@@ -5660,11 +5717,11 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
/* nothing to do if the target cpumask matches the current pwq */
wq_calc_pod_cpumask(target_attrs, cpu);
- if (wqattrs_equal(target_attrs, unbound_pwq(wq, cpu)->pool->attrs))
+ if (wqattrs_equal(target_attrs, installed_pwq(wq, cpu)->pool->attrs))
return;
/* create a new pwq */
- pwq = alloc_pwq(wq, target_attrs);
+ pwq = alloc_unbound_pwq(wq, target_attrs);
if (!pwq) {
pr_warn("workqueue: allocation failed while updating CPU pod affinity of \"%s\"\n",
wq->name);
@@ -5673,49 +5730,25 @@ static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
/* Install the new pwq. */
mutex_lock(&wq->mutex);
- old_pwq = install_unbound_pwq(wq, cpu, pwq);
+ old_pwq = install_pwq(wq, cpu, pwq);
goto out_unlock;
use_dfl_pwq:
mutex_lock(&wq->mutex);
- pwq = unbound_pwq(wq, -1);
+ pwq = installed_pwq(wq, -1);
raw_spin_lock_irq(&pwq->pool->lock);
get_pwq(pwq);
raw_spin_unlock_irq(&pwq->pool->lock);
- old_pwq = install_unbound_pwq(wq, cpu, pwq);
+ old_pwq = install_pwq(wq, cpu, pwq);
out_unlock:
mutex_unlock(&wq->mutex);
put_pwq_unlocked(old_pwq);
}
-static int alloc_and_link_percpu_pwqs(struct workqueue_struct *wq)
-{
- struct pool_workqueue *pwq;
- int cpu;
-
- for_each_possible_cpu(cpu) {
- struct worker_pool *pool = get_percpu_pool(wq, cpu);
-
- pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
- if (!pwq)
- return -ENOMEM;
-
- init_pwq(pwq, wq, pool);
-
- mutex_lock(&wq->mutex);
- link_pwq(pwq);
- mutex_unlock(&wq->mutex);
-
- rcu_assign_pointer(*per_cpu_ptr(wq->cpu_pwq, cpu), pwq);
- }
-
- return 0;
-}
-
static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
bool highpri = wq->flags & WQ_HIGHPRI;
- int cpu, ret;
+ int ret;
lockdep_assert_held(&wq_pool_mutex);
@@ -5724,7 +5757,7 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
goto enomem;
if (!(wq->flags & WQ_UNBOUND)) {
- ret = alloc_and_link_percpu_pwqs(wq);
+ ret = apply_workqueue_attrs_locked(wq, percpu_std_wq_attrs[highpri]);
} else if (wq->flags & __WQ_ORDERED) {
struct pool_workqueue *dfl_pwq;
@@ -5743,27 +5776,8 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
return 0;
enomem:
- if (wq->cpu_pwq) {
- for_each_possible_cpu(cpu) {
- struct pool_workqueue __rcu **slot;
- struct pool_workqueue *pwq;
-
- slot = per_cpu_ptr(wq->cpu_pwq, cpu);
- pwq = rcu_access_pointer(*slot);
- if (pwq) {
- /*
- * Unlink pwq from wq->pwqs since link_pwq()
- * may have already added it. wq->mutex is not
- * needed as the wq has not been published yet.
- */
- if (!list_empty(&pwq->pwqs_node))
- list_del_rcu(&pwq->pwqs_node);
- kmem_cache_free(pwq_cache, pwq);
- }
- }
- free_percpu(wq->cpu_pwq);
- wq->cpu_pwq = NULL;
- }
+ free_percpu(wq->cpu_pwq);
+ wq->cpu_pwq = NULL;
return -ENOMEM;
}
@@ -6007,10 +6021,10 @@ err_unlock_free_node_nr_active:
* flushing the pwq_release_worker ensures that the pwq_release_workfn()
* completes before calling kfree(wq).
*/
- if (wq->flags & WQ_UNBOUND) {
+ if (pwq_release_worker)
kthread_flush_worker(pwq_release_worker);
+ if (wq->flags & WQ_UNBOUND)
free_node_nr_active(wq->node_nr_active);
- }
err_free_wq:
free_workqueue_attrs(wq->attrs);
kfree(wq);
@@ -6199,12 +6213,12 @@ void destroy_workqueue(struct workqueue_struct *wq)
rcu_read_lock();
for_each_possible_cpu(cpu) {
- put_pwq_unlocked(unbound_pwq(wq, cpu));
- RCU_INIT_POINTER(*unbound_pwq_slot(wq, cpu), NULL);
+ put_pwq_unlocked(installed_pwq(wq, cpu));
+ RCU_INIT_POINTER(*pwq_slot(wq, cpu), NULL);
}
- put_pwq_unlocked(unbound_pwq(wq, -1));
- RCU_INIT_POINTER(*unbound_pwq_slot(wq, -1), NULL);
+ put_pwq_unlocked(installed_pwq(wq, -1));
+ RCU_INIT_POINTER(*pwq_slot(wq, -1), NULL);
rcu_read_unlock();
}
@@ -8203,7 +8217,7 @@ void __init workqueue_init_early(void)
init_cpu_worker_pool(pool, cpu, std_nice[i++]);
}
- /* create default unbound and ordered wq attrs */
+ /* create default unbound, ordered and percpu wq attrs */
for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
struct workqueue_attrs *attrs;
@@ -8219,6 +8233,10 @@ void __init workqueue_init_early(void)
attrs->nice = std_nice[i];
attrs->ordered = true;
ordered_wq_attrs[i] = attrs;
+
+ BUG_ON(!(attrs = alloc_workqueue_attrs()));
+ attrs->nice = std_nice[i];
+ percpu_std_wq_attrs[i] = attrs;
}
system_wq = alloc_workqueue("events", WQ_PERCPU | __WQ_DEPRECATED, 0);