From: Rik van Riel <riel@surriel.com>
To: linux-kernel@vger.kernel.org
Cc: kernel-team@meta.com, mingo@redhat.com, peterz@infradead.org,
juri.lelli@redhat.com, vincent.guittot@linaro.org,
dietmar.eggemann@arm.com, vschneid@redhat.com,
Rik van Riel <riel@surriel.com>
Subject: [PATCH 1/9] sched: add SCHED_WARN_ON()/SCHED_WARN_ON_ONCE()/SCHED_WARN()/SCHED_WARN_ONCE()
Date: Wed, 10 Jun 2026 22:14:08 -0400 [thread overview]
Message-ID: <20260611021416.910555-2-riel@surriel.com> (raw)
In-Reply-To: <20260611021416.910555-1-riel@surriel.com>
WARN_ON(), WARN_ON_ONCE(), WARN() and WARN_ONCE() emit at KERN_WARNING.
With a legacy (or boot) console registered, vprintk_emit() takes the
synchronous "legacy_direct" path: console_trylock_spinning() +
console_unlock() -> up(&console_sem) -> wake_up_process() ->
try_to_wake_up(), which acquires the woken task's ->pi_lock and its
rq->lock.
If the WARN fires while the current CPU already holds an rq->lock or a
->pi_lock -- the case for almost every WARN in the scheduling-class hot
paths -- this re-enters the scheduler and can deadlock (recursively on
rq->lock, or via the pi_lock/rq->lock ordering). The nbcon and klogd
wakeups are deferred via irq_work and are safe; only the legacy console
path is synchronous. Plain WARN*() emit at KERN_WARNING rather than
LOGLEVEL_SCHED, so they do not get the scheduler-safe deferral that
printk_deferred() does.
Add SCHED_WARN_ON()/SCHED_WARN_ON_ONCE() (and the SCHED_WARN()/
SCHED_WARN_ONCE() forms that carry a format message), which behave
exactly like their WARN*() counterparts but bracket the report in a
printk_deferred section, so the console output is handed to irq_work
instead of being emitted synchronously. The bracket is entered only on
the (cold) firing path, so the hot path cost is unchanged: just the
condition test.
printk_deferred_enter()/exit() toggle a per-CPU counter and must be
balanced on one CPU, so the caller must have preemption disabled. That
is always true while an rq->lock or ->pi_lock (both raw_spinlock_t,
which disable preemption) is held. A lockdep_assert_preemption_disabled()
guards against misuse from preemptible context and compiles away without
CONFIG_PROVE_LOCKING.
WARN_ON()/WARN_ON_ONCE() do not stringify their condition, so passing
the already-evaluated result as a constant produces identical console
output; the condition is evaluated exactly once and its boolean result
is returned, preserving the semantics for callers that test it.
No conversions are done here; this only adds the macros.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/sched.h | 52 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index c7c2dea65edd..60739ccfc32f 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -44,6 +44,7 @@
#include <linux/lockdep_api.h>
#include <linux/lockdep.h>
#include <linux/memblock.h>
+#include <linux/printk.h>
#include <linux/memcontrol.h>
#include <linux/minmax.h>
#include <linux/mm.h>
@@ -99,6 +100,57 @@ struct cpuidle_state;
#define TASK_ON_RQ_QUEUED 1
#define TASK_ON_RQ_MIGRATING 2
+/*
+ * SCHED_WARN_ON() / SCHED_WARN_ON_ONCE() / SCHED_WARN() / SCHED_WARN_ONCE():
+ * WARN_ON() / WARN_ON_ONCE() / WARN() / WARN_ONCE() variants that are safe to
+ * call while holding an rq->lock or a task's ->pi_lock.
+ *
+ * A plain WARN emits at KERN_WARNING. With a legacy console registered, the
+ * printk takes the synchronous path console_unlock() -> up(&console_sem) ->
+ * wake_up_process() -> try_to_wake_up(), which grabs ->pi_lock and rq->lock --
+ * and so deadlocks if such a lock is already held by the WARNing context.
+ *
+ * Bracket the report in a printk_deferred section so the console output is
+ * handed to irq_work instead. This is done only on the (cold) firing path, so
+ * the hot path keeps just the condition test. printk_deferred_enter()/exit()
+ * toggle a per-CPU counter and must be balanced on one CPU; the caller must
+ * therefore have preemption disabled, which is always true while an rq/pi
+ * raw_spinlock is held. The lockdep assert catches misuse from preemptible
+ * context and compiles away without CONFIG_PROVE_LOCKING.
+ */
+#define __SCHED_WARN_DEFERRED(__warn, x) \
+({ \
+ int __ret = !!(x); \
+ \
+ lockdep_assert_preemption_disabled(); \
+ if (unlikely(__ret)) { \
+ printk_deferred_enter(); \
+ __warn(1); \
+ printk_deferred_exit(); \
+ } \
+ __ret; \
+})
+
+#define SCHED_WARN_ON(x) __SCHED_WARN_DEFERRED(WARN_ON, x)
+#define SCHED_WARN_ON_ONCE(x) __SCHED_WARN_DEFERRED(WARN_ON_ONCE, x)
+
+/* As above, for the WARN()/WARN_ONCE() forms that carry a format message. */
+#define __SCHED_WARN_FMT_DEFERRED(__warn, x, fmt...) \
+({ \
+ int __ret = !!(x); \
+ \
+ lockdep_assert_preemption_disabled(); \
+ if (unlikely(__ret)) { \
+ printk_deferred_enter(); \
+ __warn(1, fmt); \
+ printk_deferred_exit(); \
+ } \
+ __ret; \
+})
+
+#define SCHED_WARN(x, fmt...) __SCHED_WARN_FMT_DEFERRED(WARN, x, fmt)
+#define SCHED_WARN_ONCE(x, fmt...) __SCHED_WARN_FMT_DEFERRED(WARN_ONCE, x, fmt)
+
extern __read_mostly int scheduler_running;
extern unsigned long calc_load_update;
--
2.53.0-Meta
next prev parent reply other threads:[~2026-06-11 2:14 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
2026-06-11 2:14 ` Rik van Riel [this message]
2026-06-11 2:14 ` [PATCH 2/9] sched/core: defer WARN console output under rq->lock Rik van Riel
2026-06-11 2:14 ` [PATCH 3/9] sched/fair: " Rik van Riel
2026-06-11 2:14 ` [PATCH 4/9] sched/deadline: " Rik van Riel
2026-06-11 2:14 ` [PATCH 5/9] sched/rt: " Rik van Riel
2026-06-11 2:14 ` [PATCH 6/9] sched_ext: " Rik van Riel
2026-06-11 2:14 ` [PATCH 7/9] sched/core_sched: " Rik van Riel
2026-06-11 2:14 ` [PATCH 8/9] sched/deadline: " Rik van Riel
2026-06-11 2:14 ` [PATCH 9/9] sched/rt: " Rik van Riel
2026-06-11 7:43 ` [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Peter Zijlstra
2026-06-11 16:20 ` Rik van Riel
2026-06-11 19:19 ` Peter Zijlstra
2026-06-12 1:53 ` Rik van Riel
2026-06-12 6:52 ` Peter Zijlstra
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=20260611021416.910555-2-riel@surriel.com \
--to=riel@surriel.com \
--cc=dietmar.eggemann@arm.com \
--cc=juri.lelli@redhat.com \
--cc=kernel-team@meta.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=peterz@infradead.org \
--cc=vincent.guittot@linaro.org \
--cc=vschneid@redhat.com \
/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