rv/reactors: fix lockdep warning and add KUnit tests
Subsystems (2)
v2 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 a preemptible kernel (e.g. 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 v2:
- Use a context-sensitive override map: LD_WAIT_SPIN in preemptible contexts, LD_WAIT_FREE in NMI/hardirq where the scheduler cannot run (patch 1).
- Merge the v1 printk/panic test files into one generic rv_reactors_kunit covering registration and dispatch, as the v1 tests were mostly not reactor-specific (patches 2-4).
- Export rv_register_reactor()/rv_unregister_reactor() so the tristate KUnit module can link against them (patch 3).
- Propagate rv_register_reactor() errors from the built-in reactor init functions instead of silently ignoring them (patch 2).
- KUnit tests are tristate (usable with CONFIG_KUNIT=m) and verify the callback is actually invoked (atomic counter), the exact -EINVAL on duplicate registration, and use mdelay(5) to exercise the lockdep context under load. A teardown action unregisters the test reactor even if a test aborts.
Patch 1 fixes the lockdep warning. Patches 2-3 make the registration API usable by the test module and propagate its errors. Patch 4 adds the KUnit tests.
Tested with CONFIG_PROVE_LOCKING=y and CONFIG_KUNIT=y.
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 | 19 +++- kernel/trace/rv/rv_reactors_kunit.c | 143 ++++++++++++++++++++++++++++ 6 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 kernel/trace/rv/rv_reactors_kunit.c
base-commit: 984b5a36fd12d1849511a45fda32036fe2b0d004
Signature · 1 line
2.25.1
PATCH v2 1/4 rv/reactors: use context-sensitive lockdep wait type in rv_react()
From: Wen Yang <[email protected]>
The single DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE) in rv_react() declares wait_type_inner = LD_WAIT_FREE for every execution context. In a preemptible context (e.g. CONFIG_PREEMPT_RT or a KUnit test running on a task), a timer interrupt can fire during a reactor callback; the interrupt exit path then schedules and acquires rq->__lock (LD_WAIT_SPIN) while the override map is still held. Since the map declares the context to be wait-free, lockdep reports 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}
Use two lockdep override maps, selected by execution context:
- Preemptible context (task, softirq, PREEMPT_RT irq thread): the scheduler may preempt, so use LD_WAIT_SPIN, the tightest wait type the scheduler itself uses, to suppress the spurious warning.
- NMI/hardirq context: preemption is disabled and the scheduler cannot run, so the false positive cannot arise. Keep LD_WAIT_FREE here to preserve the original constraint that reactors must not take raw spinlocks in atomic context.
--- kernel/trace/rv/rv_reactors.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
kernel/trace/rv/rv_reactors.c +12 -5
@@ -465,18 +465,25 @@ 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); +/* +* A reactor callback can be preempted; the scheduler then takes +* rq->__lock (LD_WAIT_SPIN). Advertise that in preemptible contexts +* to avoid a spurious lockdep report, and keep LD_WAIT_FREE in atomic +* ones where 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 v2 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 v2 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 load time.
--- 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 v2 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 | 143 ++++++++++++++++++++++++++++ 3 files changed, 156 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 +143 -0
143 lines not on this page · raw message
Signature · 1 line
2.25.1
Re: [PATCH v2 3/4] rv/reactors: export rv_register_reactor() and rv_unregister_reactor()
In reply to [email protected]Quoted 6 lines · On Mon, 2026-08-03 at 02:43 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
rv_react()is exported to modules, but the reactor registration helpers are not. Export them withEXPORT_SYMBOL_GPL()so reactor modules and the tristate KUnit test module can register and unregister reactors without hitting undefined symbol errors at load time.
Nit, but I believe you meant /link/ time (modpost). Linking phase would fail at the end of the build and you wouldn't have anything (.ko) to load.
Anyway patch looks good:
Thanks,
Gabriele
Quoted 26 lines
Signed-off-by Wen Yang <[email protected]>---
kernel/trace/rv/rv_reactors.c | 2 ++1 file changed, 2 insertions(+)kernel/trace/rv/rv_reactors.c +1
-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.kernel/trace/rv/rv_reactors.c +1
-0@@ -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_oninterface.
Re: [PATCH v2 2/4] rv/reactors: propagate rv_register_reactor() error from reactor init
In reply to [email protected]Quoted 9 lines · On Mon, 2026-08-03 at 02:43 +0800, [email protected] wrote:
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.Propagate the error from
rv_register_reactor()so a failed registration is reported instead of being silently ignored.
Quoted 37 lines
Suggested-by Gabriele Monaco <[email protected]>Signed-off-by Wen Yang <[email protected]>---
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 +0
-0@@ -26,8 +26,7 @@ static struct rv_reactor rv_panic = {
static int__initregister_react_panic(void)
{kernel/trace/rv/reactor_panic.c +1
-2-rv_register_reactor (&rv_panic); -return 0 ; +return rv_register_reactor (&rv_panic);}
static void__exitunregister_react_panic(void)kernel/trace/rv/reactor_printk.c +0
-0@@ -25,8 +25,7 @@ static struct rv_reactor rv_printk = {
static int__initregister_react_printk(void)
{kernel/trace/rv/reactor_printk.c +1
-2-rv_register_reactor (&rv_printk); -return 0 ; +return rv_register_reactor (&rv_printk);}
static void__exitunregister_react_printk(void)
Gabriele Monaco
maintainerRe: [PATCH v2 4/4] rv/reactors: add KUnit tests for reactor registration and dispatch
In reply to [email protected]Quoted 69 lines · On Mon, 2026-08-03 at 02:43 +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.Signed-off-by Wen Yang <[email protected]>---
kernel/trace/rv/Kconfig | 12 +++ kernel/trace/rv/Makefile | 1 +kernel/trace/rv/rv_reactors_kunit.c| 143 ++++++++++++++++++++++++++++3 files changed, 156 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 +12
-0@@ -0,0 +1,143 @@ +// 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 = {
kernel/trace/rv/rv_reactors_kunit.c line 22
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ .name = "kunit_test_reactor", + .description = "KUnit test reactor", +}; + +/* +* rv_unregister_reactor() on a reactor that was never registered would +* list_del() an uninitialized list_head, so track registration and only +* unregister in the teardown if it is still on the list. +*/ +static bool test_reactor_registered;
I'm not too fond of adding more logic than necessary in the test, but if you really want to keep the dynamic teardown cannot this variable stay somehow in test-specific memory (test->priv)?
That feels to me a bit cleaner, although the reactor and obviously the reactors list are still static, so we probably wouldn't gain a huge deal.
What do you think?
kernel/trace/rv/rv_reactors_kunit.c line 30
Quoted 8 lines
kernel/trace/rv/rv_reactors_kunit.c new file +8
-0+ +static int unregister_test_reactor (void ) +{ +int ret =0 ; + +if (test_reactor_registered) { + ret =rv_unregister_reactor (&test_reactor); + test_reactor_registered = false;
rv_unregister_reactor() cannot fail and probably never will (I'm not quite sure it should return int).
But setting test_reactor_registered to false here is assuming it didn't fail while the rest of the test doesn't make this assumption.
Maybe let's make it void and stop tracking a result that can never change (and that real reactors already have to ignore).
If we do that you may even stop unregistering manually and rely only on the teardown, and perhaps get rid of test_reactor_registered. Even test_register_unregister() would be mostly already covered by the next one. Just throwing in the idea.
Quoted 38 lines
kernel/trace/rv/rv_reactors_kunit.c new file +20
-0+ } + +return ret; +} + +/* +* The teardown action guarantees the reactor is unregistered even if a +* test fails mid-way, so a leftover entry cannot corrupt later tests. +*/ +static void reactor_teardown (void *arg) +{ +unregister_test_reactor (); +} + +static void register_test_reactor (struct kunit *test) +{ +KUNIT_ASSERT_EQ (test,rv_register_reactor (&test_reactor),0 ); + test_reactor_registered = true; + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, reactor_teardown,NULL), 0);
kernel/trace/rv/rv_reactors_kunit.c new file +17
-0+} + +static void test_register_unregister (struct kunit *test) +{ +register_test_reactor (test); + +KUNIT_EXPECT_EQ (test,unregister_test_reactor (),0 ); +} + +static void test_double_register (struct kunit *test) +{ +register_test_reactor (test); + +KUNIT_EXPECT_EQ (test,rv_register_reactor (&test_reactor), -EINVAL ); + +KUNIT_EXPECT_EQ (test,unregister_test_reactor (),0 ); +}
kernel/trace/rv/rv_reactors_kunit.c line 77
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ +static void test_name_too_long (struct kunit *test) +{ +/* Name length of MAX_RV_REACTOR_NAME_SIZE (32) must be rejected. */ +static struct rv_reactor long_reactor = { + .name ="kunit_reactor_name_too_long_xxx_" , + }; + +KUNIT_ASSERT_EQ (test, (int )strlen (long_reactor.name), +MAX_RV_REACTOR_NAME_SIZE );
You probably want KUNIT_ASSERT_GE, or even better _Static_assert(sizeof(long_name) - 1 >= MAX_RV_REACTOR_NAME_SIZE)
(you'd need to save the name to a static array first for sizeof() to work as expected)
Quoted 27 lines
kernel/trace/rv/rv_reactors_kunit.c new file +27
-0+KUNIT_EXPECT_EQ (test,rv_register_reactor (&long_reactor), -EINVAL ); +} + +static struct kunit_case rv_reactor_registration_cases[] = { +KUNIT_CASE (test_register_unregister), +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 );
kernel/trace/rv/rv_reactors_kunit.c line 114
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+} + +static void test_react_no_callback (struct kunit *test) +{ +struct rv_monitor monitor = { + .name ="kunit_null_react" , + }; + +/* rv_react() must silently return when monitor->react is NULL. */ +rv_react (&monitor,"no callback" );
So what are we testing here? That the kernel doesn't panic? It's fair to expect a silent return, but we cannot really validate that.
If that's your intent, maybe specify it better, since, in fact, KUnit cannot validate this. Something like:
"The only possible failure in this test case is a kernel panic"
Thanks,
Gabriele
Quoted 29 lines
kernel/trace/rv/rv_reactors_kunit.c new file +29
-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" );
Gabriele Monaco
maintainerRe: [PATCH v2 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
In reply to [email protected]Quoted 6 lines · On Mon, 2026-08-03 at 02:43 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
The single
DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE)inrv_react()declareswait_type_inner=LD_WAIT_FREEfor every execution context. In a preemptible context (e.g.CONFIG_PREEMPT_RTor a KUnit test running on a task), a timer interrupt can fire during a reactor
We are obviously not doing this for KUnit tests, but aren't tracepoint handlers also running with preemption enabled on non-PREEMPT_RT kernels now? So technically this is a problem with any configuration if events don't run with preemption disabled for other reasons.
Or is the issue with spinlocks only popping out on PREEMPT_RT because they become sleeping locks?
Is lockdep really happy to allow an interrupt/schedule taking spinlocks under LD_WAIT_FREE on non-PREEMPT_RT?
Quoted 14 lines
callback; the interrupt exit path then schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the override map is still held. Since the map declares the context to be wait-free, lockdep reports 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}Use two lockdep override maps, selected by execution context:
Quoted 8 lines
- Preemptible context (task, softirq,
PREEMPT_RTirq thread): the scheduler may preempt, so useLD_WAIT_SPIN, the tightest wait type the scheduler itself uses, to suppress the spurious warning.- NMI/hardirq context: preemption is disabled and the scheduler cannot run, so the false positive cannot arise. Keep
LD_WAIT_FREEhere to preserve the original constraint that reactors must not take raw spinlocks in atomic context.
So here you're describing at length the solution but not really why you're doing that. A reader that didn't follow the discussion might think the requirement is indeed context-dependant, it isn't.
I'd write very bluntly something like:
"Reactors are not supposed to explicitly take locks, reactor code must comply
with LD_WAIT_FREE. However reactors may run with interrupts and preemption enabled, so the interrupting code may not satisfy this constraint. Relax it if we are running from a context that cannot be interrupted to avoid false positives."
I would write something like that also in the comment, to make clear that reactors really should be LD_WAIT_FREE, but we are asserting that as best effort.
What do you think?
Thanks,
Gabriele
Quoted 44 lines
Fixes 69d8895cb9a9 ("rv: Add explicit lockdep context for reactors")Signed-off-by Wen Yang <[email protected]>Cc Thomas Weißschuh <[email protected]>---
kernel/trace/rv/rv_reactors.c | 17 ++++++++++++-----1 file changed, 12 insertions(+), 5 deletions(-)kernel/trace/rv/rv_reactors.c +0
-0@@ -465,18 +465,25 @@ int init_rv_reactors(struct dentry *root_dir)
voidrv_react(struct rv_monitor *monitor, const char *msg, ...){kernel/trace/rv/rv_reactors.c +4
-1- static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE); +/* +* A reactor callback can be preempted; the scheduler then takes +* rq->__lock (LD_WAIT_SPIN). Advertise that in preemptible contexts +* to avoid a spurious lockdep report, and keep LD_WAIT_FREE in atomic
kernel/trace/rv/rv_reactors.c +5
-0+* ones where 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 v2 1/4] rv/reactors: use context-sensitive lockdep wait type in rv_react()
Quoted 53 lines · Gabriele Monaco
Quoted 6 lines · On Mon, 2026-08-03 at 02:43 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
The single
DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE)inrv_react()declareswait_type_inner=LD_WAIT_FREEfor every execution context. In a preemptible context (e.g.CONFIG_PREEMPT_RTor a KUnit test running on a task), a timer interrupt can fire during a reactorWe are obviously not doing this for KUnit tests, but aren't tracepoint handlers also running with preemption enabled on non-PREEMPT_RT kernels now? So technically this is a problem with any configuration if events don't run with preemption disabled for other reasons.
Or is the issue with spinlocks only popping out on
PREEMPT_RTbecause they become sleeping locks?
Is lockdep really happy to allow an interrupt/schedule taking spinlocks underLD_WAIT_FREEon non-PREEMPT_RT?Quoted 14 lines
callback; the interrupt exit path then schedules and acquires
rq->__lock(LD_WAIT_SPIN) while the override map is still held. Since the map declares the context to be wait-free, lockdep reports 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}Use two lockdep override maps, selected by execution context:
Quoted 8 lines
- Preemptible context (task, softirq,
PREEMPT_RTirq thread): the scheduler may preempt, so useLD_WAIT_SPIN, the tightest wait type the scheduler itself uses, to suppress the spurious warning.- NMI/hardirq context: preemption is disabled and the scheduler cannot run, so the false positive cannot arise. Keep
LD_WAIT_FREEhere to preserve the original constraint that reactors must not take raw spinlocks in atomic context.So here you're describing at length the solution but not really why you're doing that. A reader that didn't follow the discussion might think the requirement is indeed context-dependant, it isn't.
I'd write very bluntly something like:
"Reactors are not supposed to explicitly take locks, reactor code must comply
withLD_WAIT_FREE. However reactors may run with interrupts and preemption enabled, so the interrupting code may not satisfy this constraint. Relax it if we are running from a context that cannot be interrupted to avoid false positives."
Quoted 5 lines
I would write something like that also in the comment, to make clear that reactors really should be
LD_WAIT_FREE, but we are asserting that as best effort.What do you think?
Good point, thank you.
We've made the changes in v3 as you suggested.
--
Best wishes,
Wen
Quoted 46 lines
Quoted 45 lines
Fixes 69d8895cb9a9 ("rv: Add explicit lockdep context for reactors")Signed-off-by Wen Yang <[email protected]>Cc Thomas Weißschuh <[email protected]>--- kernel/trace/rv/rv_reactors.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)kernel/trace/rv/rv_reactors.c +4
-1@@ -465,18 +465,25 @@ 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); +/* +* A reactor callback can be preempted; the scheduler then takes +* rq->__lock (LD_WAIT_SPIN). Advertise that in preemptible contexts +* to avoid a spurious lockdep report, and keep LD_WAIT_FREE in atomic
kernel/trace/rv/rv_reactors.c +6
-0+* ones where 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);kernel/trace/rv/rv_reactors.c +2
-4- -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);
Re: [PATCH v2 3/4] rv/reactors: export rv_register_reactor() and rv_unregister_reactor()
In reply to Gabriele MonacoQuoted 8 lines · Gabriele Monaco
Quoted 6 lines · On Mon, 2026-08-03 at 02:43 +0800, [email protected] wrote:
From: Wen Yang <[email protected]>
rv_react()is exported to modules, but the reactor registration helpers are not. Export them withEXPORT_SYMBOL_GPL()so reactor modules and the tristate KUnit test module can register and unregister reactors without hitting undefined symbol errors at load time.
Quoted 7 lines
Nit, but I believe you meant /link/ time (modpost). Linking phase would fail at the end of the build and you wouldn't have anything (.ko) to load.
Anyway patch looks good:
Reviewed-by Gabriele Monaco <[email protected]>
You are correct, thank you for the nit. Missing EXPORT_SYMBOL_GPL() is caught by modpost at link time; no .ko is produced, so "load time" was wrong. Fixed in v3:
"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)."
--
Best wishes,
Wen
Quoted 28 lines
Quoted 27 lines
Signed-off-by Wen Yang <[email protected]>--- 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.
Re: [PATCH v2 4/4] rv/reactors: add KUnit tests for reactor registration and dispatch
In reply to Gabriele MonacoQuoted 85 lines · Gabriele Monaco
Quoted 69 lines · On Mon, 2026-08-03 at 02:43 +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.Signed-off-by Wen Yang <[email protected]>--- kernel/trace/rv/Kconfig | 12 +++ kernel/trace/rv/Makefile | 1 + kernel/trace/rv/rv_reactors_kunit.c | 143 ++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+)create mode 100644
kernel/trace/rv/rv_reactors_kunit.ckernel/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_REACTORSkernel/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.okernel/trace/rv/rv_reactors_kunit.c new file +12
-0@@ -0,0 +1,143 @@ +// 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 = {kernel/trace/rv/rv_reactors_kunit.c line 22
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ .name = "kunit_test_reactor", + .description = "KUnit test reactor", +}; + +/* +* rv_unregister_reactor() on a reactor that was never registered would +* list_del() an uninitialized list_head, so track registration and only +* unregister in the teardown if it is still on the list. +*/ +static bool test_reactor_registered;I'm not too fond of adding more logic than necessary in the test, but if you really want to keep the dynamic teardown cannot this variable stay somehow in test-specific memory (
test->priv)?
Quoted 5 lines
That feels to me a bit cleaner, although the reactor and obviously the reactors list are still static, so we probably wouldn't gain a huge deal.
What do you think?
Thansk, your suggestion in the next comment -- relying purely on theteardown action. Since reactor_teardown() is only registered after rv_register_reactor() succeeds, calling rv_unregister_reactor() unconditionally inside it is always safe. This lets us drop test_reactor_registered entirely, along with the unregister_test_reactor() wrapper. See the updated code below.
Quoted 14 lines
kernel/trace/rv/rv_reactors_kunit.c line 30
Quoted 8 lines
kernel/trace/rv/rv_reactors_kunit.c new file +8
-0+ +static int unregister_test_reactor (void ) +{ +int ret =0 ; + +if (test_reactor_registered) { + ret =rv_unregister_reactor (&test_reactor); + test_reactor_registered = false;
rv_unregister_reactor()cannot fail and probably never will (I'm not quite sure it should return int).
But settingtest_reactor_registeredto false here is assuming it didn't fail while the rest of the test doesn't make this assumption.
Quoted 8 lines
Maybe let's make it void and stop tracking a result that can never change (and that real reactors already have to ignore).
If we do that you may even stop unregistering manually and rely only on the teardown, and perhaps get rid of
test_reactor_registered. Eventest_register_unregister()would be mostly already covered by the next one. Just throwing in the idea.
Agreed on all points. In v3:
- unregister_test_reactor() is gone; reactor_teardown() calls rv_unregister_reactor() directly.
- test_reactor_registered is gone.
- test_register_unregister() is removed; the teardown covers the unregister path, and test_double_register() already exercises the register→verify→unregister (via teardown) flow.
The updated teardown is:
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);}
Quoted 52 lines
Quoted 38 lines
kernel/trace/rv/rv_reactors_kunit.c new file +20
-0+ } + +return ret; +} + +/* +* The teardown action guarantees the reactor is unregistered even if a +* test fails mid-way, so a leftover entry cannot corrupt later tests. +*/ +static void reactor_teardown (void *arg) +{ +unregister_test_reactor (); +} + +static void register_test_reactor (struct kunit *test) +{ +KUNIT_ASSERT_EQ (test,rv_register_reactor (&test_reactor),0 ); + test_reactor_registered = true; + KUNIT_ASSERT_EQ(test, + kunit_add_action_or_reset(test, reactor_teardown,NULL), 0);
kernel/trace/rv/rv_reactors_kunit.c new file +17
-0+} + +static void test_register_unregister (struct kunit *test) +{ +register_test_reactor (test); + +KUNIT_EXPECT_EQ (test,unregister_test_reactor (),0 ); +} + +static void test_double_register (struct kunit *test) +{ +register_test_reactor (test); + +KUNIT_EXPECT_EQ (test,rv_register_reactor (&test_reactor), -EINVAL ); + +KUNIT_EXPECT_EQ (test,unregister_test_reactor (),0 ); +}kernel/trace/rv/rv_reactors_kunit.c line 77
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+ +static void test_name_too_long (struct kunit *test) +{ +/* Name length of MAX_RV_REACTOR_NAME_SIZE (32) must be rejected. */ +static struct rv_reactor long_reactor = { + .name ="kunit_reactor_name_too_long_xxx_" , + }; + +KUNIT_ASSERT_EQ (test, (int )strlen (long_reactor.name), +MAX_RV_REACTOR_NAME_SIZE );You probably want
KUNIT_ASSERT_GE, or even better_Static_assert(sizeof(long_name) - 1 >= MAX_RV_REACTOR_NAME_SIZE)
Quoted 3 lines
(you'd need to save the name to a static array first for
sizeof()to work as expected)
Good idea. The _Static_assert approach is strictly better: it catches the mistake at build time rather than test run time, and the assertion is always evaluated regardless of whether the test runs at all.
In v3 the name is stored in a fixed-size array:
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 leastMAX_RV_REACTOR_NAME_SIZE chars");
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);
}The KUNIT_ASSERT_EQ(strlen(...)) runtime check is removed.
Quoted 41 lines
Quoted 27 lines
kernel/trace/rv/rv_reactors_kunit.c new file +27
-0+KUNIT_EXPECT_EQ (test,rv_register_reactor (&long_reactor), -EINVAL ); +} + +static struct kunit_case rv_reactor_registration_cases[] = { +KUNIT_CASE (test_register_unregister), +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 );kernel/trace/rv/rv_reactors_kunit.c line 114
Quoted 10 lines
kernel/trace/rv/rv_reactors_kunit.c new file +10
-0+} + +static void test_react_no_callback (struct kunit *test) +{ +struct rv_monitor monitor = { + .name ="kunit_null_react" , + }; + +/* rv_react() must silently return when monitor->react is NULL. */ +rv_react (&monitor,"no callback" );So what are we testing here? That the kernel doesn't panic? It's fair to expect a silent return, but we cannot really validate that.
Quoted 5 lines
If that's your intent, maybe specify it better, since, in fact, KUnit cannot validate this. Something like:
"The only possible failure in this test case is a kernel panic"
Good point. And thinking further about it: we actually can do better than a pure crash guard here. The existing mock_react() already increments react_call_count, so we can reuse that counter to assert that the callback was never invoked. That turns the test from "does not panic" into "callback count stays zero", which catches a broader class of bugs.
In v3 the test becomes:
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);
}Thanks,
--
Best wishes,
Wen
Quoted 31 lines
Quoted 30 lines
kernel/trace/rv/rv_reactors_kunit.c new file +29
-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" );