rv/reactors: fix lockdep warning and add KUnit tests
Subsystems (2)
v3 cover rv/reactors: fix lockdep warning and add KUnit tests
From: Wen Yang <[email protected]>
We occasionally hit a lockdep "Invalid wait context" warning in production when a reactor callback is preempted by a timer interrupt. On interrupt exit the scheduler takes rq->__lock (LD_WAIT_SPIN) while rv_react() still holds its wait-type-override map, which declared LD_WAIT_FREE. On any kernel where the task context has preemption enabled (not just CONFIG_PREEMPT_RT) this triggers a spurious lockdep report:
[ BUG: Invalid wait context ]
1 lock held by kunit_try_catch/209:
#0: (rv_react_map-wait-type-override){+.+.}-{1:1}
kunit_try_catch/209 is trying to lock:
ffff8a743ed3e8a0 (&rq->__lock){-...}-{2:2}
Changes in v3:
Patch 1:
- Rewrite commit message to clarify that the bug is not
PREEMPT_RT-specific: any preemptible task context can be interrupted by a timer, and the scheduler acquires rq->__lock (LD_WAIT_SPIN) on interrupt exit while rv_react() still holds its LD_WAIT_FREE override map.
- Rewrite the inline comment in rv_react() to make clear.
Patch 3:
- Fix commit message: missing EXPORT_SYMBOL_GPL() is caught by modpost at link time, not at load time.
Patch 4:
- Drop test_reactor_registered bool and the unregister_test_reactor() wrapper entirely.
- Remove test_register_unregister(); the teardown already covers the unregister path, and test_double_register() exercises the full register->verify->unregister-via-teardown flow.
- Replace the runtime KUNIT_ASSERT_EQ(strlen(...)) guard in test_name_too_long() with a _Static_assert on the name array size.
- In test_react_no_callback(): reuse the existing react_call_count atomic to assert that the callback was never invoked.
This catches a broader class of regressions — e.g.
an inverted or missing NULL guard that does not cause a NULL dereference.
No functional change to patches 2-3; the series structure is unchanged.
Tested with CONFIG_PROVE_LOCKING=y and CONFIG_KUNIT=y.
v2: https://lore.kernel.org/lkml/[email protected]/
v1: https://lore.kernel.org/lkml/[email protected]/
Wen Yang (4):
rv/reactors: use context-sensitive lockdep wait type in rv_react() rv/reactors: propagate rv_register_reactor() error from reactor init rv/reactors: export rv_register_reactor() and rv_unregister_reactor() rv/reactors: add KUnit tests for reactor registration and dispatch
kernel/trace/rv/Kconfig | 12 +++ kernel/trace/rv/Makefile | 1 + kernel/trace/rv/reactor_panic.c | 3 +- kernel/trace/rv/reactor_printk.c | 3 +- kernel/trace/rv/rv_reactors.c | 22 +++-- kernel/trace/rv/rv_reactors_kunit.c | 119 ++++++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 kernel/trace/rv/rv_reactors_kunit.c
base-commit: 785095112f4198de49760552374f364043c8dbdf
Signature · 1 line
2.25.1
PATCH v3 1/4 rv/reactors: use context-sensitive lockdep wait type in rv_react()
From: Wen Yang <[email protected]>
Reactors must not explicitly take locks, so they should comply with LD_WAIT_FREE. However, reactor callbacks can run with preemption enabled on any kernel (not just PREEMPT_RT). If a timer interrupt fires during the callback, the interrupt exit path schedules and acquires rq->__lock (LD_WAIT_SPIN) while the lockdep override map that declared LD_WAIT_FREE is still held, triggering a spurious "Invalid wait context" warning:
[ BUG: Invalid wait context ] context-{5:5}
1 lock held by kunit_try_catch/209:
#0: (rv_react_map-wait-type-override){+.+.}-{1:1}
kunit_try_catch/209 is trying to lock:
ffff8a743ed3e8a0 (&rq->__lock){-...}-{2:2}
--- kernel/trace/rv/rv_reactors.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-)
kernel/trace/rv/rv_reactors.c +15 -5
@@ -465,18 +465,28 @@ int init_rv_reactors(struct dentry *root_dir)void rv_react (struct rv_monitor *monitor,const char *msg, ...) { - static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE); +/* +* Reactors must not explicitly take locks, so they should be +* LD_WAIT_FREE. However, reactor callbacks can run with preemption +* enabled, meaning the preempting code (e.g. the scheduler taking +* rq->__lock at LD_WAIT_SPIN) may violate that constraint. Use +* LD_WAIT_SPIN in preemptible contexts to avoid false-positive lockdep +* reports; keep LD_WAIT_FREE in NMI/hardirq where preemption is +* disabled and the scheduler cannot run. +*/ + static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_SPIN); + static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map_atomic, LD_WAIT_FREE); +struct lockdep_map *__maybe_unused map;va_list args;if (!rv_reacting_on () || !monitor->react)return ; + map = (in_nmi () ||in_hardirq ()) ? &rv_react_map_atomic : &rv_react_map;va_start (args, msg); - -lock_map_acquire_try (&rv_react_map); +lock_map_acquire_try (map); monitor->react (msg, args); -lock_map_release (&rv_react_map); - +lock_map_release (map);va_end (args); }EXPORT_SYMBOL_GPL (rv_react);
Signature · 1 line
2.25.1
PATCH v3 2/4 rv/reactors: propagate rv_register_reactor() error from reactor init
In reply to [email protected]From: Wen Yang <[email protected]>
Both register_react_printk() and register_react_panic() ignore the return value of rv_register_reactor() and always return 0. If the registration fails (e.g. a duplicate reactor name), the init functions silently report success even though the reactor was not registered.
Propagate the error from rv_register_reactor() so a failed registration is reported instead of being silently ignored.
--- kernel/trace/rv/reactor_panic.c | 3 +-- kernel/trace/rv/reactor_printk.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-)
kernel/trace/rv/reactor_panic.c +1 -2
@@ -26,8 +26,7 @@ static struct rv_reactor rv_panic = {static int __init register_react_panic (void ) { -rv_register_reactor (&rv_panic); -return 0 ; +return rv_register_reactor (&rv_panic); } static void __exit unregister_react_panic(void)
kernel/trace/rv/reactor_printk.c +1 -2
@@ -25,8 +25,7 @@ static struct rv_reactor rv_printk = {static int __init register_react_printk (void ) { -rv_register_reactor (&rv_printk); -return 0 ; +return rv_register_reactor (&rv_printk); } static void __exit unregister_react_printk(void)
Signature · 1 line
2.25.1
PATCH v3 3/4 rv/reactors: export rv_register_reactor() and rv_unregister_reactor()
In reply to [email protected]From: Wen Yang <[email protected]>
rv_react() is exported to modules, but the reactor registration helpers are not. Export them with EXPORT_SYMBOL_GPL() so reactor modules and the tristate KUnit test module can register and unregister reactors without hitting undefined symbol errors at link time(modpost).
--- kernel/trace/rv/rv_reactors.c | 2 ++ 1 file changed, 2 insertions(+)
kernel/trace/rv/rv_reactors.c +2 -0
@@ -314,6 +314,7 @@ int rv_register_reactor(struct rv_reactor *reactor)guard (mutex)(&rv_interface_lock);return __rv_register_reactor (reactor); } +EXPORT_SYMBOL_GPL (rv_register_reactor);/** * rv_unregister_reactor - unregister a rv reactor.
@@ -327,6 +328,7 @@ int rv_unregister_reactor(struct rv_reactor *reactor)list_del (&reactor->list);return 0 ; } +EXPORT_SYMBOL_GPL (rv_unregister_reactor);/* * reacting_on interface.
Signature · 1 line
2.25.1
PATCH v3 4/4 rv/reactors: add KUnit tests for reactor registration and dispatch
In reply to [email protected]From: Wen Yang <[email protected]>
Add KUnit tests covering the reactor register/unregister lifecycle (including duplicate and name-length rejection) and rv_react() dispatch (a no-op without a callback, exactly one invocation with one; the mdelay callback keeps the CPU busy so a timer interrupt exercises the LD_WAIT_SPIN lockdep context). The Kconfig entry is tristate so the tests can be built as a module when CONFIG_KUNIT=m; only RV_REACTORS is required.
--- kernel/trace/rv/Kconfig | 12 +++ kernel/trace/rv/Makefile | 1 + kernel/trace/rv/rv_reactors_kunit.c | 119 ++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 kernel/trace/rv/rv_reactors_kunit.c
kernel/trace/rv/Kconfig +12 -0
@@ -113,6 +113,18 @@ config RV_REACT_PANIC Enables the panic reactor. The panic reactor emits a printk() message if an exception is found and panic()s the system. +config RV_REACTORS_KUNIT + tristate "KUnit tests for RV reactors" if !KUNIT_ALL_TESTS + depends on KUNIT + depends on RV_REACTORS + default KUNIT_ALL_TESTS + help + Enable KUnit tests for RV reactor registration and dispatch. + These tests verify the register/unregister lifecycle, duplicate + rejection, and that rv_react() correctly invokes callbacks. + + If unsure, say N. + config RV_MONITORS_KUNIT_TEST tristate "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS depends on KUNIT && RV && RV_REACTORS
kernel/trace/rv/Makefile +1 -0
@@ -25,4 +25,5 @@ obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o obj-$(CONFIG_RV_REACTORS) += rv_reactors.o obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o +obj-$(CONFIG_RV_REACTORS_KUNIT) += rv_reactors_kunit.o obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
kernel/trace/rv/rv_reactors_kunit.c new file +119 -0
119 lines not on this page · raw message
Signature · 1 line
2.25.1
Re: [PATCH v3 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
In reply to [email protected]Quoted 17 lines · On Mon, 2026-08-10 at 01:10 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
Reactors must not explicitly take locks, so they should comply with LD_WAIT_FREE. However, reactor callbacks can run with preemption enabled on any kernel (not just PREEMPT_RT). If a timer interrupt fires during the callback, the interrupt exit path schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the lockdep override map that declaredLD_WAIT_FREEis still held, triggering a spurious "Invalid wait context" warning:[ BUG: Invalid wait context ] context-{5:5}
1 lock held by kunit_try_catch/209:
#0: (rv_react_map-wait-type-override){+.+.}-{1:1} kunit_try_catch/209 is trying to lock:
ffff8a743ed3e8a0(&rq->__lock){-...}-{2:2}
Quoted 2 lines
Fixes 69d8895cb9a9 ("rv: Add explicit lockdep context for reactors")Suggested-by Gabriele Monaco <[email protected]>
You came up with the idea, I at most suggested how I'd like it worded in the comment.
Anyway, I'd appreciate comments/acks from the other folks in the loop, but from my side this looks good.
Thanks,
Gabriele
Quoted 46 lines
Signed-off-by Wen Yang <[email protected]>Cc Thomas Weißschuh <[email protected]>---
kernel/trace/rv/rv_reactors.c | 20 +++++++++++++++-----1 file changed, 15 insertions(+), 5 deletions(-)kernel/trace/rv/rv_reactors.c +0
-0@@ -465,18 +465,28 @@ int init_rv_reactors(struct dentry *root_dir)
voidrv_react(struct rv_monitor *monitor, const char *msg, ...){kernel/trace/rv/rv_reactors.c +6
-1- static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE); +/* +* Reactors must not explicitly take locks, so they should be +* LD_WAIT_FREE. However, reactor callbacks can run with preemption +* enabled, meaning the preempting code (e.g. the scheduler taking +* rq->__lock at LD_WAIT_SPIN) may violate that constraint. Use +* LD_WAIT_SPIN in preemptible contexts to avoid false-positive lockdep
kernel/trace/rv/rv_reactors.c +6
-0+* reports; keep LD_WAIT_FREE in NMI/hardirq where preemption is +* disabled and the scheduler cannot run. +*/ + static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_SPIN); + static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map_atomic, LD_WAIT_FREE); +struct lockdep_map *__maybe_unused map;
va_listargs;
if (!rv_reacting_on() || !monitor->react)
return;kernel/trace/rv/rv_reactors.c +1
-0+ map = (in_nmi() || in_hardirq()) ? &rv_react_map_atomic :
&rv_react_map;
va_start(args, msg);kernel/trace/rv/rv_reactors.c +1
-2- -lock_map_acquire_try (&rv_react_map); +lock_map_acquire_try (map);
monitor->react(msg, args);kernel/trace/rv/rv_reactors.c +1
-2-lock_map_release (&rv_react_map); - +lock_map_release (map);
va_end(args);
}
EXPORT_SYMBOL_GPL(rv_react);
Re: [PATCH v3 4/4] rv/reactors: add KUnit tests for reactor registration and dispatch
In reply to [email protected]Quoted 9 lines · On Mon, 2026-08-10 at 01:10 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
Add KUnit tests covering the reactor register/unregister lifecycle (including duplicate and name-length rejection) and
rv_react()dispatch (a no-op without a callback, exactly one invocation with one; the mdelay callback keeps the CPU busy so a timer interrupt exercises theLD_WAIT_SPINlockdep context). TheKconfigentry is tristate so the tests can be built as a module whenCONFIG_KUNIT=m; onlyRV_REACTORSis required.
Looks simple and good now.
Quoted 1 line
Suggested-by Gabriele Monaco <[email protected]>
Same as before, you can leave only
Thanks
Gabriele
Quoted 169 lines
Signed-off-by Wen Yang <[email protected]>---
kernel/trace/rv/Kconfig | 12 +++ kernel/trace/rv/Makefile | 1 +kernel/trace/rv/rv_reactors_kunit.c| 119 ++++++++++++++++++++++++++++3 files changed, 132 insertions(+)create mode 100644
kernel/trace/rv/rv_reactors_kunit.ckernel/trace/rv/Kconfig +0
-0@@ -113,6 +113,18 @@ config RV_REACT_PANIC Enables the panic reactor. The panic reactor emits a printk()message if an exception is found and panic()s the system.
kernel/trace/rv/Kconfig +12
-0+config RV_REACTORS_KUNIT + tristate "KUnit tests for RV reactors" if !KUNIT_ALL_TESTS + depends on KUNIT + depends on RV_REACTORS + default KUNIT_ALL_TESTS + help + Enable KUnit tests for RV reactor registration and dispatch. + These tests verify the register/unregister lifecycle, duplicate + rejection, and that rv_react() correctly invokes callbacks. + + If unsure, say N. +config
RV_MONITORS_KUNIT_TEST
tristate "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS depends on KUNIT && RV &&RV_REACTORSkernel/trace/rv/Makefile +0
-0@@ -25,4 +25,5 @@ obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.oobj-$(CONFIG_RV_REACTORS) += rv_reactors.o obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o +obj-$(CONFIG_RV_REACTORS_KUNIT) += rv_reactors_kunit.o obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
kernel/trace/rv/rv_reactors_kunit.c new file +26
-0@@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* KUnit tests for RV reactor registration and dispatch. +*/ + +#include <kunit/test.h> +#include <linux/rv.h> +#include <linux/delay.h> +#include <linux/atomic.h> +#include "rv.h" + +static struct rv_reactor test_reactor = { + .name ="kunit_test_reactor" , + .description ="KUnit test reactor" , +}; + +static void reactor_teardown (void *arg) +{ +rv_unregister_reactor (&test_reactor); +} + +static void register_test_reactor (struct kunit *test) +{ +KUNIT_ASSERT_EQ (test,rv_register_reactor (&test_reactor),0 ); + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, reactor_teardown,NULL), 0);
kernel/trace/rv/rv_reactors_kunit.c new file +15
-0+} + +static void test_double_register (struct kunit *test) +{ +register_test_reactor (test); +KUNIT_EXPECT_EQ (test,rv_register_reactor (&test_reactor), -EINVAL ); +} + +/* +* Use a fixed-size array so sizeof() gives the exact byte count at +* compile time. +*/ +static const char long_reactor_name[] ="kunit_reactor_name_too_long_xxx_" ; +_Static_assert(sizeof(long_reactor_name) - 1 >= MAX_RV_REACTOR_NAME_SIZE, + "long_reactor_name must be at least MAX_RV_REACTOR_NAME_SIZEchars");
kernel/trace/rv/rv_reactors_kunit.c new file +78
-0+ +static void test_name_too_long (struct kunit *test) +{ +static struct rv_reactor long_reactor = { + .name = long_reactor_name, + }; + +KUNIT_EXPECT_EQ (test,rv_register_reactor (&long_reactor), -EINVAL ); +} + +static struct kunit_case rv_reactor_registration_cases[] = { +KUNIT_CASE (test_double_register), +KUNIT_CASE (test_name_too_long), + {} +}; + +static struct kunit_suite rv_reactor_registration_suite = { + .name ="rv_reactor_registration" , + .test_cases = rv_reactor_registration_cases, +}; + +static atomic_t react_call_count; + +__printf(1, 0) static void mock_react(const char *msg, va_list args) +{ +atomic_inc (&react_call_count); +/* +* Hold the CPU for 5 ms so a timer interrupt is likely to fire +* inside rv_react()'s lockdep context, exercising the LD_WAIT_SPIN +* constraint. mdelay() is a calibrated busy-wait with no scheduler +* interaction. +*/ +mdelay (5 ); +} + +static void test_react_no_callback (struct kunit *test) +{ +struct rv_monitor monitor = { + .name ="kunit_null_react" , + }; + +atomic_set (&react_call_count,0 ); +rv_react (&monitor,"no callback" ); + +/* +* The only possible failure in this test case is a kernel panic. +* NULL react guard: callback must NOT have been invoked +*/ +KUNIT_EXPECT_EQ (test,atomic_read (&react_call_count),0 ); +} + +static void test_react_callback_invoked (struct kunit *test) +{ +struct rv_monitor monitor = { + .name ="kunit_dispatch_monitor" , + .react = mock_react, + }; + +atomic_set (&react_call_count,0 ); +rv_react (&monitor,"callback invocation test" ); +KUNIT_EXPECT_EQ (test,atomic_read (&react_call_count),1 ); +} + +static struct kunit_case rv_react_dispatch_cases[] = { +KUNIT_CASE (test_react_no_callback), +KUNIT_CASE (test_react_callback_invoked), + {} +}; + +static struct kunit_suite rv_react_dispatch_suite = { + .name ="rv_react_dispatch" , + .test_cases = rv_react_dispatch_cases, +}; + +kunit_test_suites (&rv_reactor_registration_suite, &rv_react_dispatch_suite); + +MODULE_LICENSE ("GPL" ); +MODULE_DESCRIPTION ("KUnit tests for RV reactor registration and dispatch" );
Re: [PATCH v3 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
In reply to Gabriele MonacoQuoted 10 lines · Gabriele Monaco
Quoted 9 lines · On Mon, 2026-08-10 at 01:10 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
Reactors must not explicitly take locks, so they should comply with LD_WAIT_FREE. However, reactor callbacks can run with preemption enabled on any kernel (not just PREEMPT_RT). If a timer interrupt fires during the callback, the interrupt exit path schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the lockdep override map that declaredLD_WAIT_FREEis still held, triggering a spurious "Invalid wait context" warning:
...
Quoted 1 line
Anyway, I'd appreciate comments/acks from the other folks in the loop
Sorry, I do not know enough about lockdep to comment on this.
FWIW, I would rather just use LD_WAIT_SPIN and keep things simple. Context-sensitive code paths "feels wrong" to me. Spinning should either be allowed or forbidden. Making it dynamic "feels like" it will bring further complications down the road.
But that's just my intuition.
Nam
Re: [PATCH v3 4/4] rv/reactors: add KUnit tests for reactor registration and dispatch
In reply to [email protected]Quoted 4 lines · [email protected] writes:
+/*
+ * Use a fixed-size array sosizeof()gives the exact byte count at
+ * compile time.
+ */
There is no need for a comment. This is obvious from the code itself.
Quoted 8 lines
kernel/trace/rv/rv_reactors_kunit.c new file +8
-0+static const char long_reactor_name[] ="kunit_reactor_name_too_long_xxx_" ; +_Static_assert (sizeof (long_reactor_name) -1 >=MAX_RV_REACTOR_NAME_SIZE , +"long_reactor_name must be at least MAX_RV_REACTOR_NAME_SIZE chars" ); + +static void test_name_too_long (struct kunit *test) +{ +static struct rv_reactor long_reactor = { + .name = long_reactor_name,
kernel/trace/rv/rv_reactors_kunit.c line 56
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ }; + +KUNIT_EXPECT_EQ (test,rv_register_reactor (&long_reactor), -EINVAL ); +} + +static struct kunit_case rv_reactor_registration_cases[] = { +KUNIT_CASE (test_double_register), +KUNIT_CASE (test_name_too_long), + {} +};
I am not sure about the usefulness of these test cases. rv_register_reactor() is not an user API that we have to prepare for corner case usage. We can expect its users to be sane since the users are us.
But well, if Gabriele wants to keep them..
kernel/trace/rv/rv_reactors_kunit.c line 63
Quoted 7 lines
kernel/trace/rv/rv_reactors_kunit.c new file +7
-0+ +static struct kunit_suite rv_reactor_registration_suite = { + .name ="rv_reactor_registration" , + .test_cases = rv_reactor_registration_cases, +}; + +static atomic_t react_call_count;
Do we really need atomic_t? Does int work?
kernel/trace/rv/rv_reactors_kunit.c line 73
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ +__printf(1, 0) static void mock_react(const char *msg, va_list args) +{ +atomic_inc (&react_call_count); +/* +* Hold the CPU for 5 ms so a timer interrupt is likely to fire +* inside rv_react()'s lockdep context, exercising the LD_WAIT_SPIN +* constraint. mdelay() is a calibrated busy-wait with no scheduler +* interaction. +*/
The comment above mdelay()'s definition already explains what it does.
Quoted 6 lines
kernel/trace/rv/rv_reactors_kunit.c new file +6
-0+mdelay (5 ); +} + +static void test_react_no_callback (struct kunit *test) +{ +struct rv_monitor monitor = {
kernel/trace/rv/rv_reactors_kunit.c line 89
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ .name = "kunit_null_react", + }; + +atomic_set (&react_call_count,0 ); +rv_react (&monitor,"no callback" ); + +/* +* The only possible failure in this test case is a kernel panic. +* NULL react guard: callback must NOT have been invoked +*/
Obvious comment.
Quoted 4 lines
kernel/trace/rv/rv_reactors_kunit.c new file +4
-0+KUNIT_EXPECT_EQ (test,atomic_read (&react_call_count),0 ); +} + +static void test_react_callback_invoked (struct kunit *test)
kernel/trace/rv/rv_reactors_kunit.c line 103
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+{ +struct rv_monitor monitor = { + .name ="kunit_dispatch_monitor" , + .react = mock_react, + }; + +atomic_set (&react_call_count,0 ); +rv_react (&monitor,"callback invocation test" ); +KUNIT_EXPECT_EQ (test,atomic_read (&react_call_count),1 ); +}
So the test calls rv_react(), and validates that the reactor is called? Honestly I am not sure how useful that is. Especially since Gabriele already made the selftest which validates that the reactor is invoked.
Nam
Re: [PATCH v3 2/4] rv/reactors: propagate rv_register_reactor() error from reactor init
In reply to [email protected]Quoted 7 lines · [email protected] writes:
From: Wen Yang <[email protected]>
Both
register_react_printk()andregister_react_panic()ignore the return value ofrv_register_reactor()and always return 0. If the registration fails (e.g. a duplicate reactor name), the init functions silently report success even though the reactor was not registered.
Quoted 6 lines
Propagate the error from
rv_register_reactor()so a failed registration is reported instead of being silently ignored.Suggested-by Gabriele Monaco <[email protected]>Reviewed-by Gabriele Monaco <[email protected]>Signed-off-by Wen Yang <[email protected]>
Gabriele Monaco
maintainerRe: [PATCH v3 4/4] rv/reactors: add KUnit tests for reactor registration and dispatch
In reply to Nam CaoQuoted 33 lines · Nam Cao
Quoted 4 lines · [email protected] writes:
+/*
+ * Use a fixed-size array sosizeof()gives the exact byte count at
+ * compile time.
+ */There is no need for a comment. This is obvious from the code itself.
Quoted 9 lines
kernel/trace/rv/rv_reactors_kunit.c new file +3
-0+static const char long_reactor_name[] ="kunit_reactor_name_too_long_xxx_" ; +_Static_assert(sizeof(long_reactor_name) - 1 >= MAX_RV_REACTOR_NAME_SIZE, + "long_reactor_name must be at least MAX_RV_REACTOR_NAME_SIZEchars");
kernel/trace/rv/rv_reactors_kunit.c new file +5
-0+ +static void test_name_too_long (struct kunit *test) +{ +static struct rv_reactor long_reactor = { + .name = long_reactor_name,kernel/trace/rv/rv_reactors_kunit.c line 56
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ }; + +KUNIT_EXPECT_EQ (test,rv_register_reactor (&long_reactor), -EINVAL ); +} + +static struct kunit_case rv_reactor_registration_cases[] = { +KUNIT_CASE (test_double_register), +KUNIT_CASE (test_name_too_long), + {} +};I am not sure about the usefulness of these test cases.
rv_register_reactor()is not an user API that we have to prepare for corner case usage. We can expect its users to be sane since the users are us.
Quoted 2 lines
But well, if Gabriele wants to keep them..
That's right, it can be a bit of an overkill, but I'm thinking one day reactors could come from (out-of-tree) kernel modules, it wouldn't hurt to have this path tested, especially if it doesn't require much effort.
Quoted 9 lines
kernel/trace/rv/rv_reactors_kunit.c line 63
Quoted 7 lines
kernel/trace/rv/rv_reactors_kunit.c new file +7
-0+ +static struct kunit_suite rv_reactor_registration_suite = { + .name ="rv_reactor_registration" , + .test_cases = rv_reactor_registration_cases, +}; + +static atomic_t react_call_count;Do we really need
atomic_t? Does int work?
Missed that, yes, it should work just fine since KUnit runs in a single thread.
Quoted 44 lines
Review
Quoted 12 lines
kernel/trace/rv/rv_reactors_kunit.c new file +8
-0+ +__printf(1, 0) static void mock_react(const char *msg, va_list args) +{ +atomic_inc (&react_call_count); +/* +* Hold the CPU for 5 ms so a timer interrupt is likely to fire +* inside rv_react()'s lockdep context, exercising the LD_WAIT_SPIN +* constraint. mdelay() is a calibrated busy-wait with no scheduler
(quoted diff) +2
-0+ * interaction. + */The comment above mdelay()'s definition already explains what it does.
Quoted 6 lines
kernel/trace/rv/rv_reactors_kunit.c new file +6
-0+mdelay (5 ); +} + +static void test_react_no_callback (struct kunit *test) +{ +struct rv_monitor monitor = {kernel/trace/rv/rv_reactors_kunit.c line 89
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ .name = "kunit_null_react", + }; + +atomic_set (&react_call_count,0 ); +rv_react (&monitor,"no callback" ); + +/* +* The only possible failure in this test case is a kernel panic. +* NULL react guard: callback must NOT have been invoked +*/ Obvious comment.
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+KUNIT_EXPECT_EQ (test,atomic_read (&react_call_count),0 ); +} + +static void test_react_callback_invoked (struct kunit *test) +{ +struct rv_monitor monitor = { + .name ="kunit_dispatch_monitor" , + .react = mock_react, + }; +
Quoted 8 lines
kernel/trace/rv/rv_reactors_kunit.c line 103
Quoted 4 lines
kernel/trace/rv/rv_reactors_kunit.c new file +4
-0+atomic_set (&react_call_count,0 ); +rv_react (&monitor,"callback invocation test" ); +KUNIT_EXPECT_EQ (test,atomic_read (&react_call_count),1 ); +}So the test calls
rv_react(), and validates that the reactor is called? Honestly I am not sure how useful that is. Especially since Gabriele already made the selftest which validates that the reactor is invoked.
From what I understood, the value in this test suite isn't really about the assertions (especially in test_react_no_callback). It triggers cases that should not produce a splat (panic/lockdep) and a pass should consider that too. This makes them not quite pure KUnit, but again, I think it doesn't hurt to have this kind of test since KUnit output is in dmesg anyway, so a splat would hardly slip.
What do you think?
Gabriele
Gabriele Monaco
maintainerRe: [PATCH v3 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
In reply to Nam CaoQuoted 15 lines · Nam Cao
Quoted 10 lines · Gabriele Monaco
Quoted 9 lines · On Mon, 2026-08-10 at 01:10 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
Reactors must not explicitly take locks, so they should comply with LD_WAIT_FREE. However, reactor callbacks can run with preemption enabled on any kernel (not just PREEMPT_RT). If a timer interrupt fires during the callback, the interrupt exit path schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the lockdep override map that declaredLD_WAIT_FREEis still held, triggering a spurious "Invalid wait context" warning:...
Quoted 1 line
Anyway, I'd appreciate comments/acks from the other folks in the loop
Quoted 8 lines
Sorry, I do not know enough about lockdep to comment on this.
FWIW, I would rather just use
LD_WAIT_SPINand keep things simple. Context-sensitive code paths "feels wrong" to me. Spinning should either be allowed or forbidden. Making it dynamic "feels like" it will bring further complications down the road.But that's just my intuition.
I don't have a strong opinion on this, but since there's no one in the kernel using LD_WAIT_FREE as inner type, that feels like a hint to go down the simple route too and allow LD_WAIT_SPIN.
If a reactor ever uses spinlocks, lockdep would already complain on its own if that ends up being an issue, wouldn't it?
Gabriele
Re: [PATCH v3 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
Quoted 14 lines · Gabriele Monaco
Quoted 13 lines · Nam Cao
Quoted 10 lines · Gabriele Monaco
Quoted 9 lines · On Mon, 2026-08-10 at 01:10 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
Reactors must not explicitly take locks, so they should comply with LD_WAIT_FREE. However, reactor callbacks can run with preemption enabled on any kernel (not just PREEMPT_RT). If a timer interrupt fires during the callback, the interrupt exit path schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the lockdep override map that declaredLD_WAIT_FREEis still held, triggering a spurious "Invalid wait context" warning:...
Quoted 8 lines
Quoted 8 lines
Quoted 1 line
Anyway, I'd appreciate comments/acks from the other folks in the loop
Sorry, I do not know enough about lockdep to comment on this.
FWIW, I would rather just use
LD_WAIT_SPINand keep things simple. Context-sensitive code paths "feels wrong" to me. Spinning should either be allowed or forbidden. Making it dynamic "feels like" it will bring further complications down the road.
To me the dynamic logic also feels quite complicated.
We could also disable preemption before overriding the lockdep context when lockdep is enabled to avoid the observed issue.
Quoted 8 lines
Quoted 1 line
But that's just my intuition.
I don't have a strong opinion on this, but since there's no one in the kernel using
LD_WAIT_FREEas inner type, that feels like a hint to go down the simple route too and allowLD_WAIT_SPIN.If a reactor ever uses spinlocks, lockdep would already complain on its own if that ends up being an issue, wouldn't it?
Only if that reactor is actually triggered by a tracepoint in the wrong context. This might not happen during testing. This happened to me in my signal reactor patch, which is why I added the lockdep override.
Thomas
Re: [PATCH v3 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
Quoted 19 lines · Thomas Weißschuh
Quoted 18 lines · Gabriele Monaco
Quoted 17 lines · Nam Cao
Quoted 10 lines · Gabriele Monaco
Quoted 9 lines · On Mon, 2026-08-10 at 01:10 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
Reactors must not explicitly take locks, so they should comply with LD_WAIT_FREE. However, reactor callbacks can run with preemption enabled on any kernel (not just PREEMPT_RT). If a timer interrupt fires during the callback, the interrupt exit path schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the lockdep override map that declaredLD_WAIT_FREEis still held, triggering a spurious "Invalid wait context" warning:...
Quoted 1 line
Anyway, I'd appreciate comments/acks from the other folks in the loop
Sorry, I do not know enough about lockdep to comment on this.
Quoted 9 lines
Quoted 4 lines
Quoted 4 lines
FWIW, I would rather just use
LD_WAIT_SPINand keep things simple. Context-sensitive code paths "feels wrong" to me. Spinning should either be allowed or forbidden. Making it dynamic "feels like" it will bring further complications down the road.To me the dynamic logic also feels quite complicated.
We could also disable preemption before overriding the lockdep context when lockdep is enabled to avoid the observed issue.
Thanks,
rv_react() already has two callers in this tree that land in very different places.
nrp attaches to local_timer_entry, which fires from inside the hardirq handler itself:
DEFINE_IDTENTRY_SYSVEC(sysvec_apic_timer_interrupt)
irq_enter_rcu() ->
preempt_count_add(HARDIRQ_OFFSET)
trace_local_timer_entry()
handle_vector_irq_entry() -> da_handle_event() -> rv_react()in_hardirq() is true for the whole of that rv_react() call.
While pagefault, on the other hand, attaches to page_fault_user, which only fires when the interrupted context was user mode -- i.e. preempt_count is guaranteed 0:
exc_page_fault()
trace_page_fault_user()
handle_page_fault() -> ltl_atom_pulse() -> ... -> rv_react()A timer tick can land inside that second one and there's nothing wrong with it doing so.
So I don't think LD_WAIT_SPIN for everything is safe, it's not just "less strict on paper".
If a reactor ever does raw_spin_lock() by mistake while running from nrp's path, check_wait_context() takes the wait type straight from the override map:
if (unlikely(class->lock_type == LD_LOCK_WAIT_OVERRIDE))
curr_inner = prev_inner; /* SPIN */
if (next_outer > curr_inner)
return print_lock_invalid_wait_context(...);
SPIN nested in SPIN is 2 > 2, which is false, so it just passes.
We'd be silently giving up the one case (hardirq/NMI) where the "no locks" rule for reactors actually matters, which is the same thing that bit you with the signal reactor.
And for what it's worth, checking context to decide the wait type isn't something we'd be introducing -- lockdep does the same thing to get the baseline before any override is applied, in the exact function that produced this splat:
static inline short task_wait_context(struct task_struct *curr)
{
if (lockdep_hardirq_context()) {
if (curr->hardirq_threaded || curr->irq_config)
return LD_WAIT_CONFIG;
return LD_WAIT_SPIN;
} else if (curr->softirq_context) {
return LD_WAIT_CONFIG;
}
return LD_WAIT_MAX;
}That's four cases. Our in_nmi() || in_hardirq() is a simplification of what's already there, not a new habit.
Thomas, on disabling preemption instead: it does fix the pagefault
case, and it's a no-op for nrp since preempt_count is already elevated there. But it changes what a reactor is allowed to do, for every reactor, not just the two above.cspin_lock() on RT checks might_resched() before it even looks at thevlock:
static __always_inline void __rt_spin_lock(spinlock_t *lock)
{
rtlock_might_resched(); /* unconditional */
rtlock_lock(&lock->lock);
}so any future reactor using a plain spinlock would hit that every single time, lock contended or not, on top of whatever it's already called for. That seems worse than the thing we're trying to fix.
Since neither struct rv_reactor nor rv_react() actually documents what context a callback may run in, maybe that's worth spelling out separately regardless of what we do here -- something close to what printk already does for the same reason (reactor_printk's vprintk_deferred() leans on this internally: is_printk_legacy_deferred() checks in_nmi() to decide whether it's safe to take console_lock or whether it has to go through the lock-free irq_work path instead).
A reactor that wants to do more than printk/panic would follow the same shape:
static void some_reactor(const char *msg, va_list args)
{
if (atomic_cmpxchg(&pending, 0, 1) == 0)
irq_work_queue(&my_irq_work); /* NMI-safe */
}
static void some_irq_work_fn(struct irq_work *work)
{
schedule_work(&my_work); /* spin_lock()/mutex_lock() now
fine */
}Please let me know if there are any concerns or if further changes are needed.
--
Best wishes,
Wen
Quoted 15 lines
Quoted 8 lines
Quoted 1 line
But that's just my intuition.
I don't have a strong opinion on this, but since there's no one in the kernel using
LD_WAIT_FREEas inner type, that feels like a hint to go down the simple route too and allowLD_WAIT_SPIN.If a reactor ever uses spinlocks, lockdep would already complain on its own if that ends up being an issue, wouldn't it?
Only if that reactor is actually triggered by a tracepoint in the wrong context. This might not happen during testing. This happened to me in my signal reactor patch, which is why I added the lockdep override.
Thomas
Gabriele Monaco
maintainerRe: [PATCH v3 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
Quoted 2 lines · Wen Yang
So I don't think
LD_WAIT_SPINfor everything is safe, it's not just "less strict on paper".
Yes it won't be safe, we could have wrong reactors not reported by lockdep, but so would your conditional check. We'd be catching everything in interrupts, but if a model never runs in interrupt context, we'd be demoted to LD_WAIT_SPIN nonetheless.
Your condition is kind of a best-effort to do a bit better than just LD_WAIT_SPIN. Again, I'm not completely against it, but it isn't a final solution (not that I can think of a better one) and if most people find it confusing, we're probably better off without it.
Or am I missing something?
Quoted 9 lines
If a reactor ever does
raw_spin_lock()by mistake while running from nrp's path,check_wait_context()takes the wait type straight from the override map:if (
unlikely(class->lock_type == LD_LOCK_WAIT_OVERRIDE))curr_inner= prev_inner; /* SPIN */ if (next_outer>curr_inner)
returnprint_lock_invalid_wait_context(...);
Quoted 4 lines
SPIN nested in SPIN is 2 > 2, which is false, so it just passes. We'd be silently giving up the one case (hardirq/NMI) where the "no locks" rule for reactors actually matters, which is the same thing that bit you with the signal reactor.
True, that is one, but it isn't the only case where using locks can be an issue, namely sched_switch is particularly hard to please (interrupts are disabled for a reason).
Quoted 17 lines
And for what it's worth, checking context to decide the wait type isn't something we'd be introducing -- lockdep does the same thing to get the baseline before any override is applied, in the exact function that produced this splat:
static inline short
task_wait_context(struct task_struct *curr){
if (lockdep_hardirq_context()) {
if (curr->hardirq_threaded||curr->irq_config) returnLD_WAIT_CONFIG; returnLD_WAIT_SPIN;
} else if (curr->softirq_context) {
returnLD_WAIT_CONFIG;
}
returnLD_WAIT_MAX;
}
Quoted 2 lines
That's four cases. Our
in_nmi()||in_hardirq()is a simplification of what's already there, not a new habit.
I'm not familiar with the lockdep code, so I may be missing something here, but isn't this function getting the current context? It isn't used to tune the lockdep logic for some specific function, it /is/ the lockdep logic itself.
Is this really comparable?
Quoted 12 lines
Thomas, on disabling preemption instead: it does fix the pagefault case, and it's a no-op for nrp since
preempt_countis already elevated there. But it changes what a reactor is allowed to do, for every reactor, not just the twoabove.cspin_lock()on RT checksmight_resched()before it even looks at thevlock:static
__always_inlinevoid__rt_spin_lock(spinlock_t *lock){
rtlock_might_resched(); /* unconditional */rtlock_lock(&lock->lock);
}
Quoted 3 lines
so any future reactor using a plain spinlock would hit that every single time, lock contended or not, on top of whatever it's already called for. That seems worse than the thing we're trying to fix.
I'm also against disabling preemption, we may even be able to avoid scheduling by also disabling interrupts in a convenient order, but in principle I'd avoid that like plague. RV was born as a tool for real-time and disabling preemption only to get better lockdep reporting doesn't look right to me.
Sure we could do it only with lockdep and assume lockdep's overhead is already enough, but we're again complicating things.
Quoted 7 lines
Since neither struct
rv_reactornorrv_react()actually documents what context a callback may run in, maybe that's worth spelling out separately regardless of what we do here -- something close to what printk already does for the same reason (reactor_printk'svprintk_deferred()leans on this internally:is_printk_legacy_deferred()checksin_nmi()to decide whether it's safe to takeconsole_lockor whether it has to go through the lock-freeirq_workpath instead).
You raise a fair point though, we should document all this somewhere. I believe this is the best thing we can do at this point.
The peculiarity of reactors and RV in general is that we run from tracepoints, so we could run virtually from anywhere, that's why there's no one size fits all..
Overall I'm now leaning towards using only LD_WAIT_SPIN and carefully document why we had to do this.
What do you all think?
Thanks,
Gabriele