[PATCH v8 05/14] rv: Add a KUnit test for the storage free path

From
<hidden>
Date
2026-11-05 17:50:04
Also in
lkml
Reply to
unknown sender, 2026-11-05
From: Wen Yang <redacted>

A per-object HA monitor whose timer is armed through a pointer resolved
before da_destroy_storage() outlives the storage and reports against a
gone entity.  Real handlers resolve and use a storage under one
guard(rcu)(), so the fixture issues the two steps separately to reach
that window.

Add rv_test_rvtest_arm_after_destroy() to rv_ha_kunit.c to verify that
the free hook properly disarms the timer before the storage is freed.

Signed-off-by: Wen Yang <redacted>
---
 kernel/trace/rv/rv_ha_kunit.c | 51 ++++++++++++++++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/rv/rv_ha_kunit.c b/kernel/trace/rv/rv_ha_kunit.c
index e8aaee110fae..29ee8a739739 100644
--- a/kernel/trace/rv/rv_ha_kunit.c
+++ b/kernel/trace/rv/rv_ha_kunit.c
@@ -3,12 +3,16 @@
  * KUnit tests for RV hybrid automata (HA) framework
  */
 #include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/delay.h>
 #include <linux/rv.h>
 
 #if IS_ENABLED(CONFIG_RV_HA_MONITOR)
 
 #include <rv_trace.h>
 
+static atomic_t rvtest_expired;
+
 /* rvtest: a minimal per-object HA monitor driven directly by the tests. */
 #define MONITOR_NAME rvtest
 #define RV_MON_TYPE RV_MON_PER_OBJ
@@ -19,7 +23,7 @@ typedef void *monitor_target;
 #define trace_rv_retries_error(...)	do { } while (0)
 #define trace_event_rvtest(...)		do { } while (0)
 #define trace_error_rvtest(...)		do { } while (0)
-#define trace_error_env_rvtest(...)	do { } while (0)
+#define trace_error_env_rvtest(...)	atomic_inc(&rvtest_expired)
 
 enum states_rvtest {
 	idle_rvtest,
@@ -134,8 +138,53 @@ static void rv_test_rvtest_underflow(struct kunit *test)
 	rv_this.enabled = 0;
 }
 
+/*
+ * rv_test_rvtest_arm_after_destroy - a storage outlived by its own timer
+ *
+ * da_destroy_storage() cancels the timer, then unlinks:
+ *
+ *	da_monitor_reset_hook()		cancels
+ *	hash_del_rcu()
+ *	call_rcu()
+ *
+ * A reader that resolved the storage before the unlink can arm past that
+ * cancel.  Handlers resolve and use under one guard(rcu)(), so the two steps
+ * are issued separately here.
+ *
+ * The timer is still queued after the destroy, and it fires -- reporting a
+ * violation for an entity that is gone.
+ */
+static void rv_test_rvtest_arm_after_destroy(struct kunit *test)
+{
+	const int id = 4242;
+	struct da_monitor *stale;
+
+	atomic_set(&rvtest_expired, 0);
+	da_monitor_init();
+	rv_this.enabled = 1;
+
+	da_handle_start_run_event(id, NULL, start_rvtest);
+
+	scoped_guard(rcu)			/* resolve before the unlink */
+		stale = da_get_monitor(id, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, stale);
+
+	da_destroy_storage(id);			/* cancel, unlink, call_rcu */
+	ha_start_timer_ns(to_ha_monitor(stale), clk_rvtest,
+			  RVTEST_BUDGET_NS, ha_get_ns());
+
+	rcu_barrier();
+
+	/* A timer the free path missed expires here. */
+	msleep(600);
+	KUNIT_EXPECT_EQ(test, atomic_read(&rvtest_expired), 0);
+	da_monitor_destroy();
+	rv_this.enabled = 0;
+}
+
 static struct kunit_case rv_ha_kunit_cases[] = {
 	KUNIT_CASE(rv_test_rvtest_underflow),
+	KUNIT_CASE(rv_test_rvtest_arm_after_destroy),
 	{}
 };
 
-- 
2.25.1
Thread (15 messages) 15 messages, 1 author, last reply 6d ago
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help