* [PATCH -rcu/dev 0/4] hazptrtorture: Fix shutdown hang and catch slot owner race
@ 2026-10-27 16:38 Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 1/4] hazptrtorture: Release all deferred hazard pointers before stopping Andrea Parri
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Andrea Parri @ 2026-10-27 16:38 UTC (permalink / raw)
To: Paul E. McKenney, Mathieu Desnoyers
Cc: Andrea Parri, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Lai Jiangshan, Zqiang, Gary Guo, Bradley Morgan,
Kunwu Chan, rcu, lkmm, linux-kernel
Patch 1 fixes a shutdown hang in hazptrtorture with deferral enabled, as
in the PREEMPT scenario: hazptr_torture_cleanup() stops the kthread that
releases deferred hazard pointers before the writer, which can then
wait in ->sync() forever. It now stops the readers before that
kthread, which releases what is left once the readers have stopped, and
that kthread before the writer.
Patches 2 and 4 make hazptrtorture catch the race fixed by "hazptr:
Record per-CPU slot owner after claiming the slot" (now on -rcu/dev)
should it come back, as Paul asked [1] and agreed to [2]. Patch 2
adds two CONFIG_HAZPTR_DEBUG checks of the recorded slot owner, in
hazptr_release_debug() and hazptr_note_context_switch(). Patch 4 adds
the nest_acquire module parameter, which makes readers take a second,
nested hazard pointer that goes through __hazptr_acquire() while the
first is in slot 0.
Patch 3 fixes a false positive of an existing CONFIG_HAZPTR_DEBUG
check, which the nesting of patch 4 exposes with CPU hotplug: a hazard
pointer that __hazptr_acquire() puts directly in the backup slot warns
if the task migrates before releasing it. It comes before patch 4,
which would otherwise make runs with CPU hotplug warn.
The series applies on -rcu/dev. 30-minute hazptrtorture runs of the
PREEMPT and NOPREEMPT scenarios pass on x86_64 with 8 CPUs, as do a
30-minute NOPREEMPT run with torture_type=hazptr and three 10-minute
PREEMPT runs: one with irq_acquire=100 and nest_acquire=1, one with
CONFIG_HAZPTR_DEBUG=n, and one with torture_type=hazptr-stack. The
30-minute PREEMPT run ends with as many hazard pointers released from
the pending lists as were deferred (reldefer: 650385 relundefer
650385). With kthread_do_pending_ms=10000, two 3-minute PREEMPT runs
shut down in 100 and 130ms, well within one period.
Without patch 4, hazptrtorture passes even without the slot-owner fix:
its readers hold one hazard pointer each, so they reach the slow path
only when the ptr_eq() check fails or the pointer is NULL. With
patches 2 and 4 but without the fix, PREEMPT and NOPREEMPT report the
race 6.3 and 6.0 seconds after boot, and with nest_acquire=0 both run
10 minutes without a report.
The nesting costs about a third of the outer reader iterations at the
default nest_acquire=2. If that is too much for every run, it could
default to zero and be enabled from the scenario files instead.
Patches 2 and 4 stand in for the KUnit reproducer mentioned in the RFC
notes of the fix, which needs a test-only hook in
__hazptr_try_acquire(). The checks can miss one case that the KUnit
test covers: an interrupt handler that acquires and releases the slot
instead of detaching it, then a context switch before the task's
release, when no other hazard pointer claims the slot in between.
hazptrtorture does not produce that case either, as
hazptr_torture_acquire() always detaches.
Patch 2's release check also warns about a second hazptr_release() of
a ctx whose per-CPU slot another hazard pointer has claimed since. The
hazptr_release() kernel-doc permits that second release, but it clears
the other hazard pointer, and a second release of a backup slot oopses
even without this series. Nothing in-tree releases a hazard pointer
twice. I will follow up separately on the kernel-doc.
An LLM found the hang fixed by patch 1, the crash shown in patch 2 and
the false positive fixed by patch 3. It also helped write all four
patches and their changelogs, and ran the tests above.
---
Changes since RFC:
- Patch 1: stop the readers, then do_pending, then the writer, and
release all deferred hazard pointers after torture_kthread_stopping(),
instead of keeping do_pending in its loop until it is stopped (Paul).
Paul suggested changing the "while (!torture_must_stop())" condition
to "while (!kthread_should_stop())"; this keeps it, for the reason in
[3]. The changelog also says when kthread_stop() can still delay the
exit of do_pending by a full kthread_do_pending_ms sleep (Paul).
Bradley's Acked-by is not carried over, as the patch changed.
- Post patch 1 in a series (Bradley), now with patches 2 and 4, so
that hazptrtorture catches the race fixed by the slot-owner RFC patch
(Paul).
- Patch 3: new, for the false positive that patch 4 exposes.
- Do not repost the slot-owner RFC patch, which Paul has queued.
- Drop the Assisted-by tags, following mainline commit 1e035297ffda
("docs: remove Assisted-by references"), and describe the LLM use
in the changelogs and above instead.
[1] https://lore.kernel.org/r/fa16d2b1-594e-494b-8cfc-9b8c13f2c223@paulmck-laptop/
[2] https://lore.kernel.org/r/e0145c14-9d6a-4c91-816c-57dabd98879b@paulmck-laptop/
[3] https://lore.kernel.org/r/20261023120755.2463384-1-parri.andrea@gmail.com/
RFC patch 1: https://lore.kernel.org/r/20261019162005.12888-1-parri.andrea@gmail.com/
RFC slot-owner fix: https://lore.kernel.org/r/20261019161852.12783-1-parri.andrea@gmail.com/
Andrea Parri (4):
hazptrtorture: Release all deferred hazard pointers before stopping
hazptr: Check per-CPU slot owner with CONFIG_HAZPTR_DEBUG
hazptr: Do not warn when a backup slot is released on another CPU
hazptrtorture: Nest a second hazard pointer in readers
.../admin-guide/kernel-parameters.txt | 6 +++
include/linux/hazptr.h | 13 ++++++
kernel/hazptr.c | 4 ++
kernel/rcu/hazptrtorture.c | 40 ++++++++++++++++++-
4 files changed, 61 insertions(+), 2 deletions(-)
base-commit: 633f91e3408cc65a5237752a3265b031c389e43e
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH -rcu/dev 1/4] hazptrtorture: Release all deferred hazard pointers before stopping
2026-10-27 16:38 [PATCH -rcu/dev 0/4] hazptrtorture: Fix shutdown hang and catch slot owner race Andrea Parri
@ 2026-10-27 16:38 ` Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 2/4] hazptr: Check per-CPU slot owner with CONFIG_HAZPTR_DEBUG Andrea Parri
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Andrea Parri @ 2026-10-27 16:38 UTC (permalink / raw)
To: Paul E. McKenney, Mathieu Desnoyers
Cc: Andrea Parri, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Lai Jiangshan, Zqiang, Gary Guo, Bradley Morgan,
Kunwu Chan, rcu, lkmm, linux-kernel
hazptrtorture can hang at shutdown when deferral is enabled, as in the
PREEMPT scenario: the writer stays in ->sync() and kthread_stop() on it
never returns, until kvm.sh kills QEMU:
[ 1800.899278] hazptr-torture: Stopping hazptr_torture_do_pending task
[ 1800.899447] hazptr-torture: Stopping hazptr_torture_writer task
[ 1812.713870] hazptr-torture: rtc: 00000000225df70a ver: 12436 HTWS_SYNC tfle: 0 rta: 12437 rtaf: 0 rtf: 12427 onoff: 48783/48783:48786/48786 3,254:3,280 611671:731494 (HZ=1000) acq: 5720353149 rel: 5720210186 acqirq: 571927 relirq: 714778 reldefer: 714139 relundefer 714114
[ 1812.713971] hazptr-torture: hazptr_torture_stats is stopping
Summary: Potential hang (QEMU killed)
!!! PID 1393523 hung at 1980 vs. 1800 seconds
hazptr_torture_cleanup() stops the do_pending kthread first, then the
writer, then the readers. Once torture_must_stop(), do_pending makes
one last pass and leaves its loop, but unless stutter_will_wait(), that
pass releases the deferred hazard pointers of a single CPU.
Readers can also still defer after that pass, from the iteration they
are in when torture_must_stop() becomes true. Readers that have left
their loop hold no hazard pointer, so the writer waits on one of the 25
deferred hazard pointers above (reldefer - relundefer) that nothing
releases.
Stop the readers before do_pending, and do_pending before the writer.
do_pending still leaves its loop at torture_must_stop(), and now
releases the deferred hazard pointers of all CPUs once
torture_kthread_stopping() returns, that is, once
hazptr_torture_cleanup() stops it, after the readers. The writer can
still wait in ->sync(), but only until then.
This should be safe because readers defer only from their own loop,
which they have left once torture_stop_kthread() returns for them, and
the hazptr_torture_acquire() IPI runs synchronously within that loop.
The readers do not need the writer to stop: they leave their loop at
torture_must_stop(), after at most one stutter_wait().
do_pending keeps the torture_must_stop() check before its sleep. A
kthread_stop() that comes after that check and before the sleep starts
still delays do_pending's exit by a full kthread_do_pending_ms sleep,
however long that is. With the readers stopped first, this happens
only if do_pending passed the check before the test began stopping and
had not yet gone to sleep when kthread_stop() came, after all the
readers had stopped.
With this change, a 30-minute PREEMPT run on x86_64 with 8 CPUs ends
with "End of test: SUCCESS" and with as many hazard pointers released
from the pending lists as were deferred (reldefer: 983727 relundefer
983727).
The hang was found with the help of an LLM.
Fixes: f23d7b47ced9 ("hazptrtorture: Add kthread to release deferred hazard pointers")
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
kernel/rcu/hazptrtorture.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/kernel/rcu/hazptrtorture.c b/kernel/rcu/hazptrtorture.c
index 7c8b5899fb011..c46083a8af093 100644
--- a/kernel/rcu/hazptrtorture.c
+++ b/kernel/rcu/hazptrtorture.c
@@ -560,6 +560,9 @@ static int hazptr_torture_do_pending(void *arg)
// Omit stutter_wait() because this function needs to do cleanup.
} while (!torture_must_stop());
torture_kthread_stopping("hazptr_torture_do_pending");
+ /* The readers have stopped, so nothing new can be deferred. */
+ for_each_possible_cpu(cpu)
+ hazptr_torture_do_one_pending(cpu, &rand);
return 0;
}
@@ -773,9 +776,7 @@ hazptr_torture_cleanup(void)
return;
}
- torture_stop_kthread(hazptr_torture_do_pending, do_pending_task);
torture_stop_kthread(hazptr_torture_preempt, preempt_task);
- torture_stop_kthread(hazptr_torture_writer, writer_task);
if (reader_tasks) {
for (i = 0; i < nrealreaders; i++)
@@ -785,6 +786,14 @@ hazptr_torture_cleanup(void)
reader_tasks = NULL;
}
+ /*
+ * Stop the writer only after do_pending, and do_pending only after
+ * the readers: the writer may be waiting on hazard pointers that
+ * readers deferred, which do_pending releases once they stopped.
+ */
+ torture_stop_kthread(hazptr_torture_do_pending, do_pending_task);
+ torture_stop_kthread(hazptr_torture_writer, writer_task);
+
torture_stop_kthread(hazptr_torture_stats, stats_task);
/* Do torture-type-specific cleanup operations. */
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH -rcu/dev 2/4] hazptr: Check per-CPU slot owner with CONFIG_HAZPTR_DEBUG
2026-10-27 16:38 [PATCH -rcu/dev 0/4] hazptrtorture: Fix shutdown hang and catch slot owner race Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 1/4] hazptrtorture: Release all deferred hazard pointers before stopping Andrea Parri
@ 2026-10-27 16:38 ` Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 3/4] hazptr: Do not warn when a backup slot is released on another CPU Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 4/4] hazptrtorture: Nest a second hazard pointer in readers Andrea Parri
3 siblings, 0 replies; 5+ messages in thread
From: Andrea Parri @ 2026-10-27 16:38 UTC (permalink / raw)
To: Paul E. McKenney, Mathieu Desnoyers
Cc: Andrea Parri, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Lai Jiangshan, Zqiang, Gary Guo, Bradley Morgan,
Kunwu Chan, rcu, lkmm, linux-kernel
When a per-CPU slot records the wrong hazptr_ctx as its owner, nothing
reports it where it happens. If the task is switched out before
releasing the slot, hazptr_note_context_switch() promotes it through the
wrong context, and if that context is detached,
hazptr_chain_backup_slot() adds its backup slot to an overflow list
again.
The kernel can then crash later in hazptr_synchronize(), far from the
cause. For example, from hazptrtorture with readers that nest a second
hazard pointer, as "hazptrtorture: Nest a second hazard pointer in
readers" makes them do, and without "hazptr: Record per-CPU slot owner
after claiming the slot":
Oops: stack segment: 0000 [#1] SMP PTI
CPU: 4 UID: 0 PID: 83 Comm: hazptr_torture_ Tainted: G W 7.3.0-rc1-00107-ga2aa5148421f #2 PREEMPTLAZY
RIP: 0010:hazptr_scan_overflow_list_period+0x94/0x100
RBP: dead000000000100 R08: 00000000002fa6bc R09: 0000000000000004
Call Trace:
<TASK>
hazptr_synchronize+0xb7/0xd0
hazptr_torture_writer+0x181/0x3f0
kthread+0xd1/0x100
ret_from_fork+0x179/0x240
ret_from_fork_asm+0x1a/0x30
</TASK>
Check the recorded owner where it is used. hazptr_release_debug() now
warns when the context releasing a per-CPU slot is not the one recorded
for it. hazptr_note_context_switch() warns when the recorded owner's
->slot is not the slot being promoted. In that run, the checks warned
1.3 and 0.08 seconds before the oops:
Hazard Pointer (addr=000000006dd14922) released from a per-CPU slot recorded with another owner. Acquire: caller=hazptr_torture_read_lock+0x43/0xb0, pid=82, cpu=7.
WARNING: ./include/linux/hazptr.h:304 at hazptr_torture_read_unlock+0x137/0x160, CPU#7: hazptr_torture_/82
Call Trace:
<TASK>
hazptr_torture_reader_tail+0x80/0x220
hazptr_torture_nested_reader+0x6c/0x90
hazptr_torture_reader+0x2f5/0x530
kthread+0xd1/0x100
ret_from_fork+0x179/0x240
ret_from_fork_asm+0x1a/0x30
</TASK>
Hazard Pointer (addr=00000000742d8665) in per-CPU slot 1 recorded with an owner that uses another slot.
WARNING: ./include/linux/hazptr.h:185 at __schedule+0x7ba/0xf10, CPU#0: hazptr_torture_/82
Call Trace:
<TASK>
preempt_schedule+0x2d/0x50
preempt_schedule_thunk+0x16/0x40
hazptr_torture_read_lock+0xaa/0xb0
hazptr_torture_nested_reader+0x39/0x90
hazptr_torture_reader+0x2f5/0x530
kthread+0xd1/0x100
ret_from_fork+0x179/0x240
ret_from_fork_asm+0x1a/0x30
</TASK>
hazptr_release_debug() reads the owner of ctx->slot only if that
per-CPU slot is in use, and skips the backup slot that a detached or
promoted ctx uses. If ctx holds the per-CPU slot, no interrupt can
claim it between the ->addr check and the owner read, so the check warns
only if the slot records the wrong owner.
If ctx has already released the slot, which the hazptr_release()
kernel-doc permits, the slot is in use only if another hazard pointer
has claimed it since, and this second release would clear that hazard
pointer, so the check warns then too.
One case can escape both checks: an interrupt handler that acquires and
releases the slot instead of detaching it, then a context switch before
the task's release. The stale owner's ->slot still names the slot, and
the promotion empties the task's per-CPU slot, so hazptr_release_debug()
warns only if another hazard pointer has claimed that slot by then.
The crash was found with the help of an LLM.
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
include/linux/hazptr.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/include/linux/hazptr.h b/include/linux/hazptr.h
index 90d8d5a3d2443..d37792af34939 100644
--- a/include/linux/hazptr.h
+++ b/include/linux/hazptr.h
@@ -182,6 +182,9 @@ void hazptr_note_context_switch(void)
if (!slot->addr)
continue;
#ifdef CONFIG_HAZPTR_DEBUG
+ WARN_ONCE(item->ctx.ctx->slot != slot,
+ "Hazard Pointer (addr=%p) in per-CPU slot %u recorded with an owner that uses another slot.",
+ slot->addr, idx);
item->ctx.ctx->detach_cpu = true;
#endif
hazptr_promote_to_backup_slot(item->ctx.ctx, slot);
@@ -284,6 +287,8 @@ void hazptr_release_debug(struct hazptr_ctx *ctx, void *addr)
int pid = current->pid, cpu = smp_processor_id();
bool warn_remote_cpu = !ctx->detach_cpu && ctx->acquire_cpu != cpu,
warn_remote_task = !ctx->detach_task && ctx->acquire_pid != pid;
+ struct hazptr_slot *slot = ctx->slot;
+ struct hazptr_slot_item *item;
WARN_ONCE(warn_remote_cpu || warn_remote_task,
"Hazard Pointer (addr=%p) released on remote %s without %s. Acquire: caller=%pS, pid=%d, cpu=%d. Release: pid=%d, cpu=%d.",
@@ -291,6 +296,14 @@ void hazptr_release_debug(struct hazptr_ctx *ctx, void *addr)
warn_remote_task ? "task" : "cpu",
warn_remote_task ? "being detached from task" : "context switch",
(void *) ctx->acquire_caller, ctx->acquire_pid, ctx->acquire_cpu, pid, cpu);
+
+ if (hazptr_slot_is_backup(ctx, slot) || !READ_ONCE(slot->addr))
+ return;
+ /* Whoever claimed a per-CPU slot must be recorded as its owner. */
+ item = container_of(slot, struct hazptr_slot_item, slot);
+ WARN_ONCE(item->ctx.ctx != ctx,
+ "Hazard Pointer (addr=%p) released from a per-CPU slot recorded with another owner. Acquire: caller=%pS, pid=%d, cpu=%d.",
+ addr, (void *) ctx->acquire_caller, ctx->acquire_pid, ctx->acquire_cpu);
}
#else
static inline void hazptr_release_debug(struct hazptr_ctx *ctx, void *addr) { }
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH -rcu/dev 3/4] hazptr: Do not warn when a backup slot is released on another CPU
2026-10-27 16:38 [PATCH -rcu/dev 0/4] hazptrtorture: Fix shutdown hang and catch slot owner race Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 1/4] hazptrtorture: Release all deferred hazard pointers before stopping Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 2/4] hazptr: Check per-CPU slot owner with CONFIG_HAZPTR_DEBUG Andrea Parri
@ 2026-10-27 16:38 ` Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 4/4] hazptrtorture: Nest a second hazard pointer in readers Andrea Parri
3 siblings, 0 replies; 5+ messages in thread
From: Andrea Parri @ 2026-10-27 16:38 UTC (permalink / raw)
To: Paul E. McKenney, Mathieu Desnoyers
Cc: Andrea Parri, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Lai Jiangshan, Zqiang, Gary Guo, Bradley Morgan,
Kunwu Chan, rcu, lkmm, linux-kernel
With CONFIG_HAZPTR_DEBUG, hazptr_release_debug() warns when a hazard
pointer is released on a CPU other than the one that acquired it,
unless ->detach_cpu is set. hazptr_detach() sets it, and so does
hazptr_note_context_switch() when it promotes a per-CPU slot to the
backup slot.
__hazptr_acquire() can also put the hazard pointer in the backup slot
directly, when all per-CPU slots are in use or the pointer changes
under __hazptr_try_acquire(). Nothing sets ->detach_cpu then, so if
the task is migrated before the release, the check warns although the
backup slot can be released on any CPU.
From hazptrtorture, with CPU hotplug and readers that nest a second
hazard pointer, as "hazptrtorture: Nest a second hazard pointer in
readers" makes them do (without the nesting, 30-minute PREEMPT and
NOPREEMPT runs do not hit it):
Hazard Pointer (addr=00000000e2979b1e) released on remote cpu without context switch. Acquire: caller=hazptr_torture_read_lock+0x43/0xb0, pid=80, cpu=0. Release: pid=80, cpu=1.
WARNING: ./include/linux/hazptr.h:293 at hazptr_torture_read_unlock+0x80/0x160, CPU#1: hazptr_torture_/80
Call Trace:
<TASK>
hazptr_torture_reader_tail+0x88/0x250
hazptr_torture_nested_reader+0x6c/0x90
hazptr_torture_reader+0x2f5/0x550
kthread+0xdf/0x110
ret_from_fork+0x179/0x240
ret_from_fork_asm+0x1a/0x30
</TASK>
Set ->detach_cpu when __hazptr_acquire() falls back to the backup slot.
The remote-task check still applies to such a hazard pointer.
The false positive was found with the help of an LLM.
Fixes: 3d48872eb74f ("hazptr: Introduce CONFIG_HAZPTR_DEBUG misuse detection")
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
kernel/hazptr.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/kernel/hazptr.c b/kernel/hazptr.c
index b15b84ebd4ceb..b06164aac3a70 100644
--- a/kernel/hazptr.c
+++ b/kernel/hazptr.c
@@ -112,6 +112,10 @@ void *__hazptr_acquire(struct hazptr_ctx *ctx, void * const *addr_p)
* synchronize forward progress.
*/
slot = hazptr_chain_backup_slot(ctx);
+#ifdef CONFIG_HAZPTR_DEBUG
+ /* Unlike a per-CPU slot, the backup slot can be released on any CPU. */
+ ctx->detach_cpu = true;
+#endif
WRITE_ONCE(slot->addr, HAZPTR_WILDCARD); /* Store B */
/* Memory ordering: Store B before Load A. */
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH -rcu/dev 4/4] hazptrtorture: Nest a second hazard pointer in readers
2026-10-27 16:38 [PATCH -rcu/dev 0/4] hazptrtorture: Fix shutdown hang and catch slot owner race Andrea Parri
` (2 preceding siblings ...)
2026-10-27 16:38 ` [PATCH -rcu/dev 3/4] hazptr: Do not warn when a backup slot is released on another CPU Andrea Parri
@ 2026-10-27 16:38 ` Andrea Parri
3 siblings, 0 replies; 5+ messages in thread
From: Andrea Parri @ 2026-10-27 16:38 UTC (permalink / raw)
To: Paul E. McKenney, Mathieu Desnoyers
Cc: Andrea Parri, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Lai Jiangshan, Zqiang, Gary Guo, Bradley Morgan,
Kunwu Chan, rcu, lkmm, linux-kernel
hazptrtorture rarely exercises the race fixed by "hazptr: Record per-CPU
slot owner after claiming the slot": an interrupt handler acquiring a
hazard pointer after the interrupted task chooses a slot in
__hazptr_acquire() and before it claims it. Readers get there only when
their pointer is NULL or changes under them.
This commit therefore adds the nest_acquire module parameter, which
makes a reader acquire and release a second hazard pointer while holding
the first, once per the specified number of acquisitions on average
(default two, zero disables). While the outer hazard pointer is in
slot 0 of the reader's CPU, the nested acquisition finds that slot in
use and goes to __hazptr_acquire(), where hazptr_torture_acquire()
handlers from other readers can interrupt it.
Without that fix, and with the checks added by "hazptr: Check per-CPU
slot owner with CONFIG_HAZPTR_DEBUG", the PREEMPT and NOPREEMPT
scenarios at default parameters report the race 6.3 and 6.0 seconds
after boot, and NOPREEMPT oopses 1.3 seconds later. With
nest_acquire=0, both run 10 minutes without a report.
With the fix, 30-minute runs of both scenarios pass, as does a 10-minute
PREEMPT run with irq_acquire=100 and nest_acquire=1.
The nesting costs about a third of the outer reader iterations, as seen
in the IPI acquisitions, which readers send once per irq_acquire
iterations. PREEMPT counts acqirq: 270645 in 10 minutes without nesting
and acqirq: 544976 in 30 minutes with it.
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
.../admin-guide/kernel-parameters.txt | 6 +++++
kernel/rcu/hazptrtorture.c | 27 +++++++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e3c5a6b68fc34..63bb7719457dd 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1992,6 +1992,12 @@ Kernel parameters
is equal to zero, in which case it instead
defaults to zero (disabled).
+ hazptrtorture.nest_acquire= [KNL]
+ Acquire a second, nested hazard pointer on
+ average one out of the specified number of
+ reader acquisitions. Zero disables. Defaults
+ to two.
+
hazptrtorture.nreaders= [KNL]
Number of hazard-pointer reader kthreads, each
of which repeatedly acquires and releases hazard
diff --git a/kernel/rcu/hazptrtorture.c b/kernel/rcu/hazptrtorture.c
index c46083a8af093..4373bc5fe11e5 100644
--- a/kernel/rcu/hazptrtorture.c
+++ b/kernel/rcu/hazptrtorture.c
@@ -37,6 +37,8 @@ torture_param(int, irq_release, -1,
"Release hazard pointers from irq handlers once per specified #, zero to disable");
torture_param(int, kthread_do_pending_ms, -1,
"Delay between cleanups for deferred hazard pointers (ms), zero to disable");
+torture_param(int, nest_acquire, 2,
+ "Nest a second hazard pointer once per specified # of reader acquires, zero to disable");
torture_param(int, nreaders, -1, "Number of hazard-pointer reader threads");
torture_param(bool, nwriters, 1, "Number of hazard-pointer writer threads, 0 or 1");
torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)");
@@ -449,6 +451,22 @@ static void hazptr_torture_defer(struct hazptr_pending *hppp, struct torture_ran
atomic_long_inc(per_cpu_ptr(&hazptr_torture_releases_defer, raw_smp_processor_id()));
}
+/*
+ * Acquire and release a second hazard pointer while the caller holds one.
+ * If the caller's hazard pointer is in this CPU's slot 0, this acquisition
+ * takes the slow path, which first tries another per-CPU slot and can race
+ * there with hazptr_torture_acquire() handlers.
+ */
+static void hazptr_torture_nested_reader(struct torture_random_state *trsp)
+{
+ struct hazptr_pending hpp;
+
+ hpp.hpp_htp = cur_ops->readlock(&hpp.hpp_hc);
+ atomic_long_inc(per_cpu_ptr(&hazptr_torture_acquires, raw_smp_processor_id()));
+ if (hpp.hpp_htp)
+ hazptr_torture_reader_tail(&hpp, trsp);
+}
+
/*
* Hazard-pointer torture reader kthread. Repeatedly dereferences
* hazptr_torture_current, incrementing the corresponding element of the
@@ -497,6 +515,8 @@ static int hazptr_torture_reader(void *arg)
schedule_timeout_interruptible(HZ / 10);
continue;
}
+ if (nest_acquire && !(torture_random(&rand) % nest_acquire))
+ hazptr_torture_nested_reader(&rand);
if (time_after(jiffies, lastsleep) && !torture_must_stop()) {
torture_hrtimeout_us(500, 1000, &rand);
lastsleep = jiffies + 10;
@@ -721,6 +741,7 @@ hazptr_torture_print_module_parms(struct hazptr_torture_ops *cur_ops, const char
pr_alert("%s" TORTURE_FLAG
"--- %s: nreaders=%d nwriters=%d "
"defer_modulus=%d irq_acquire=%d irq_release=%d kthread_do_pending_ms=%d "
+ "nest_acquire=%d "
"onoff_interval=%d onoff_holdoff=%d "
"preempt_duration=%d preempt_interval=%d "
"reader_sleep_us=%d "
@@ -728,6 +749,7 @@ hazptr_torture_print_module_parms(struct hazptr_torture_ops *cur_ops, const char
"verbose=%d\n",
torture_type, tag, nrealreaders, nwriters,
defer_modulus, irq_acquire, irq_release, kthread_do_pending_ms,
+ nest_acquire,
onoff_interval, onoff_holdoff,
preempt_duration, preempt_interval,
reader_sleep_us,
@@ -891,6 +913,11 @@ static int __init hazptr_torture_init(void)
WARN_ON(IS_BUILTIN(CONFIG_HAZPTR_TORTURE_TEST));
irq_release = 0;
}
+ if (nest_acquire < 0) {
+ pr_alert("Cannot have negative nest_acquire (%d), disabling.\n", nest_acquire);
+ WARN_ON(IS_BUILTIN(CONFIG_HAZPTR_TORTURE_TEST));
+ nest_acquire = 0;
+ }
reader_tasks = kzalloc_objs(reader_tasks[0], nrealreaders);
for (i = 0; i < nrealreaders; i++) {
firsterr = torture_create_kthread(hazptr_torture_reader, (void *)i,
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-27 16:38 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 16:38 [PATCH -rcu/dev 0/4] hazptrtorture: Fix shutdown hang and catch slot owner race Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 1/4] hazptrtorture: Release all deferred hazard pointers before stopping Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 2/4] hazptr: Check per-CPU slot owner with CONFIG_HAZPTR_DEBUG Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 3/4] hazptr: Do not warn when a backup slot is released on another CPU Andrea Parri
2026-10-27 16:38 ` [PATCH -rcu/dev 4/4] hazptrtorture: Nest a second hazard pointer in readers Andrea Parri
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®