Re: [RFC PATCH v5 1/2] hazptr: Implement Hazard Pointers

From: Paul E. McKenney

Date: Thu Jul 09 2026 - 12:11:01 EST


On Wed, Jul 08, 2026 at 08:28:29PM -0700, Paul E. McKenney wrote:
> On Wed, Jul 08, 2026 at 10:45:23PM -0400, Mathieu Desnoyers wrote:
> > On 2026-07-08 18:55, Paul E. McKenney wrote:
> > [...]
> > > OK, I did check, and all of the hazptr_torture_do_pending() function's
> > > hazard pointers have been passed to hazptr_detach_from_task().
> > >
> > > But sometimes a given hazard pointer might be passed to
> > > hazptr_detach_from_task() twice, once just before that llist_add(),
> > > and again if hazptr_torture_reader_tail() decides to release that hazard
> > > pointer via smp_call_function_single().
> > >
> > > Do I instead need to be careful to avoid detaching a given hazard pointer
> > > more than once?
> > That should be OK to call the "detach" more than once, because it checks
> > if the hazptr is already detached with:
> >
> > + if (unlikely(hazptr_slot_is_backup(ctx, slot)))
> > + return;
> >
> > (while under preempt off)
> >
> > My concern is about this worker thread:
> >
> > static int hazptr_torture_do_pending(void *arg)
> > {
> > int cpu = 0;
> > DEFINE_TORTURE_RANDOM(rand);
> >
> > VERBOSE_TOROUT_STRING("hazptr_torture_do_pending task started");
> > do {
> > if (stutter_will_wait()) {
> > for_each_possible_cpu(cpu)
> > hazptr_torture_do_one_pending(cpu, &rand);
> > } else {
> > cpu = cpumask_next_wrap(cpu, cpu_possible_mask);
> > hazptr_torture_do_one_pending(cpu, &rand);
> > }
> > if (torture_must_stop())
> > torture_hrtimeout_ms(kthread_do_pending_ms, USEC_PER_MSEC, &rand);
> > // Omit stutter_wait() because this function needs to do cleanup.
> > } while (!torture_must_stop());
> > torture_kthread_stopping("hazptr_torture_do_pending");
> > return 0;
> > }
> >
> > which as you will notice happily invoke "do_one_pending" on either
> > all possible cpus or on a next cpu (non-local), which does:
> >
> > llhp = per_cpu_ptr(&hazptr_pending, cpu);
> > llnp = llist_del_all(llhp);
>
> Except that every item on every CPU's llist has already been detached,
> courtesy of the call to hazptr_detach_from_task() from within
> hazptr_torture_defer() prior to the llist_add().
>
> > if (!llnp)
> > return;
> > llist_for_each_entry_safe(hppp, hppp1, llnp, hpp_node) {
> > hazptr_torture_reader_tail(hppp, trsp);
> > atomic_long_inc(per_cpu_ptr(&hazptr_torture_releases_undefer,
> > raw_smp_processor_id()));
> > kfree(hppp);
> > }
> >
> > And here the reader_tail calls cur_ops->readunlock(hcp, htp); which
> > does a hazptr_release from the wrong CPU without previously detaching
> > the hazptr from its associated thread as it should.
> >
> > Does this line of thinking make sense ?
>
> Huh.
>
> There are actually two calls to hazptr_detach_from_task() within
> hazptr_torture_defer(), one outside of the guard(preempt) and the other
> within, as in I somehow misapplied your patch.
>
> But that would certainly not explain failures in the NOPREEMPT case.
>
> Still, time to ditch the call preceding the guard(preempt) and see
> what happens. Like this:
>
> static void hazptr_torture_defer(struct hazptr_pending *hppp, struct torture_random_state *trsp)
> {
> int cpu = torture_random(trsp) % nr_cpu_ids;
> struct llist_head *llhp;
>
> guard(preempt)();
> hazptr_detach_from_task(&hppp->hpp_hc);
> cpu = cpumask_next_wrap(cpu, cpu_online_mask);
> llhp = per_cpu_ptr(&hazptr_pending, cpu);
> llist_add(&hppp->hpp_node, llhp);
> atomic_long_inc(per_cpu_ptr(&hazptr_torture_releases_defer, raw_smp_processor_id()));
> }
>
> Except that the same thing happens:
>
> [ 8.042111] Oops: general protection fault, probably for non-canonical address 0xdead000000000110: 0000 [#1] SMP PTI
> [ 8.043367] CPU: 8 UID: 0 PID: 135 Comm: hazptr_torture_ Not tainted 7.1.0-rc4-00080-g3d59e48086a0-dirty #1519 PREEMPT(full)
> [ 8.044685] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.15.0-1 04/01/2014
> [ 8.045626] RIP: 0010:hazptr_synchronize_overflow_list+0x3e/0x90
> [ 8.046356] Code: 08 48 89 c6 48 85 db 74 54 4d 8b 65 10 eb 1b e8 48 12 fa 00 f3 90 4c 89 ef e8 4e 14 fa 00 48 89 c6 49 8b 45 10 4c 39 e0 75 41 <48> 8b 43 10 4c 89 ef 48 39 e8 74 d9 48 83 f8 01 74 d3 e8 1b 12 fa
> [ 8.048592] RSP: 0000:ffffb289004f7e60 EFLAGS: 00010086
> [ 8.049231] RAX: 0000000000000801 RBX: dead000000000100 RCX: 0000000000000003
> [ 8.050136] RDX: 0000000000000001 RSI: 0000000000000286 RDI: ffff9006df3d80e0
> [ 8.050993] RBP: ffffffff9b002940 R08: 0000000000122a09 R09: 0000000000000008
> [ 8.051836] R10: 0000000000000001 R11: ffff9006c1d1b200 R12: 0000000000000801
> [ 8.052889] R13: ffff9006df3d80e0 R14: ffffffff9b002940 R15: ffff9006df3d80c0
> [ 8.053746] FS: 0000000000000000(0000) GS:ffff90074448f000(0000) knlGS:0000000000000000
> [ 8.054641] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> [ 8.055286] CR2: 0000000000000000 CR3: 0000000017830000 CR4: 00000000000006f0
> [ 8.056086] Call Trace:
> [ 8.056378] <TASK>
> [ 8.056648] hazptr_synchronize+0xd1/0x130
> [ 8.057118] hazptr_torture_writer+0x180/0x3f0
> [ 8.057642] ? __pfx_hazptr_torture_writer+0x10/0x10
> [ 8.058241] kthread+0xe5/0x120
> [ 8.058629] ? __pfx_kthread+0x10/0x10
> [ 8.059090] ret_from_fork+0x1bd/0x220
> [ 8.059529] ? __pfx_kthread+0x10/0x10
> [ 8.059954] ret_from_fork_asm+0x1a/0x30
> [ 8.060417] </TASK>
> [ 8.060685] Modules linked in:
> [ 8.061041] ---[ end trace 0000000000000000 ]---
>
> So what am I missing here?

Am I perhaps just overflowing the backup slots? Or is there some sort
of memory allocation behind the scenes that I am missing?

Thanx, Paul