Re: [PATCH 2/2] hazptr: Introduce CONFIG_HAZPTR_DEBUG misuse detection
From: Paul E. McKenney
Date: Thu Jul 09 2026 - 16:44:27 EST
On Thu, Jul 09, 2026 at 02:56:15PM -0400, Mathieu Desnoyers wrote:
> On 2026-07-09 14:47, Paul E. McKenney wrote:
> > On Thu, Jul 09, 2026 at 02:29:33PM -0400, Mathieu Desnoyers wrote:
> > > Introduce Hazard Pointers debug assert, which detects misuse of hazard
> > > pointers, namely failure to detach the hazard pointer from its owner
> > > thread before releasing it from a different thread.
> > >
> > > Prints the following to the console when a failure is detected:
> >
> > Thank you, Mathieu!
> >
> > I am testing this one first to verify the debugging, and then I will
> > test the patch 1/2 hazptrtorture.c changes to verify the fix.
> >
> > We are going to need to carefully document the hazard-pointer handling
> > rules! ;-)
>
> Good point!
>
> So the general rule for the hazptr API is that the hazptr context
> is attached to the task that does the hazptr acquire.
>
> The scheduler integration is responsible for ensuring that the
> hazptr context is moved to a backup slot in case of context switch,
> which takes care of handling task migrations without user intervention.
>
> The case for which the user needs to explicitly invoke "detach from
> task" is when the hazptr context is handed off to a _different_ task.
> In that case the scheduler really cannot help, so it needs to be
> done explicitly.
>
> The debug code I proposed here discovers two scenarios:
>
> - Release vs acquire are done on different tasks without explicit detach
> from task (user error),
> - Release vs acquire are done on different CPUs, without scheduler
> context switch in between. This could happen if the user has
> a confused hazptr context ownership model.
Or if they do hazptr_acquire() in one interrupt handler on one CPU
and hazptr_release() in another on some other CPU. Which, as you say,
requires that the first interrupt handler invoke hazptr_detach_from_task()
before making the hazptr_ctx structure available to the other CPU's
handler.
Do the kernel-doc headers below accurately capture the rules of the
hazard-pointer road?
I am sure that I am confused on at least a few points, but we have
to start somewhere! ;-)
Thanx, Paul
------------------------------------------------------------------------
commit 64241bf30d2cea1be8114f5be3ceb16b2db38d5f
Author: Paul E. McKenney <paulmck@xxxxxxxxxx>
Date: Thu Jul 9 13:39:09 2026 -0700
hazptr: Upgrade kernel-doc headers
Upgrade the kernel-doc headers for hazptr_acquire(), hazptr_release(),
and hazptr_detach_from_task().
Signed-off-by: Paul E. McKenney <paulmck@xxxxxxxxxx>
Cc: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
Cc: Boqun Feng <boqun@xxxxxxxxxx>
diff --git a/include/linux/hazptr.h b/include/linux/hazptr.h
index c5aa4fb03b11cf..8b6c0c8cdfb987 100644
--- a/include/linux/hazptr.h
+++ b/include/linux/hazptr.h
@@ -126,6 +126,24 @@ void hazptr_promote_to_backup_slot(struct hazptr_ctx *ctx, struct hazptr_slot *s
ctx->slot = backup_slot;
}
+/**
+ * hazptr_detach_from_task - Allow a hazard pointer to be released by some other task
+ *
+ * @ctx: The hazard-pointer context to be migrated.
+ *
+ * By default, a given hazptr_acquire() and the corresponding
+ * hazptr_release() must run in the context of a single task. When you
+ * need to acquire a hazard pointer in one task and release it in another,
+ * you must first invoke hazptr_detach_from_task() on that hazard pointer's
+ * context. It is permissible to invoke hazptr_detach_from_task() multiple
+ * times on the same @ctx while it is protecting the same pointer.
+ *
+ * For example, if a hazard pointer is acquired by a task and released
+ * by a timer handler, that task would need to pass the hazard pointer's
+ * context to hazptr_detach_from_task() after the hazptr_acquire() and
+ * before arming the timer (or at least before the handler had a chance
+ * to execute).
+ */
static inline
void hazptr_detach_from_task(struct hazptr_ctx *ctx)
{
@@ -160,17 +178,29 @@ void hazptr_note_context_switch(void)
}
}
-/*
- * hazptr_acquire: Load pointer at address and protect with hazard pointer.
+/**
+ * hazptr_acquire - Load pointer at address and protect with hazard pointer.
+ *
+ * @ctx: The hazard-pointer context to be passed to hazptr_release().
+ * @addr_p: Pointer to the pointer that is to be hazard-pointer protected.
*
* Load @addr_p, and protect the loaded pointer with hazard pointer.
- * When using hazptr_acquire from interrupt handlers, the acquired slots
- * need to be released before returning from the interrupt handler.
+ * This protection is roughly similar to that of a reference counter.
+ * It is not permissible to invoke hazptr_acquire() twice on the same @ctx
+ * without an intervening hazptr_release().
*
* Returns a non-NULL protected address if the loaded pointer is non-NULL.
* Returns NULL if the loaded pointer is NULL.
*
* On success the protected hazptr slot is stored in @ctx->slot.
+ *
+ * By default, the call to hazptr_release() must be running in the context
+ * of the same task that executed hazptr_acquire(). When it is necessary
+ * to instead call hazptr_release() from the context of some other task,
+ * pass @ctx to hazptr_detach_from_task() after invoking hazptr_acquire()
+ * but before making the hazard pointer available to that other task.
+ * Please note that "in the context of some other task" includes things
+ * like interrupt handlers.
*/
static inline
void *hazptr_acquire(struct hazptr_ctx *ctx, void * const *addr_p)
@@ -233,7 +263,22 @@ void hazptr_release_debug(struct hazptr_ctx *ctx, void *addr)
static inline void hazptr_release_debug(struct hazptr_ctx *ctx, void *addr) { }
#endif
-/* Release the protected hazard pointer from @slot. */
+/**
+ * hazptr_release - Release the specified hazard pointer
+ *
+ * @ctx: The hazard-pointer context that was passed to hazptr_acquire().
+ * @addr_p: The pointer that is to be hazard-pointer unprotected.
+ *
+ * Release the protected hazard pointer recorded in @ctx.
+ *
+ * By default, hazptr_release() must execute in the context of the
+ * same task that executed the corresponding hazptr_acquire(), but
+ * please see hazptr_detach_from_task().
+ *
+ * It is permissible (though likely unwise from a maintainability
+ * viewpoint) to invoke hazptr_release() twice on the same @ctx without
+ * an intervening hazptr_acquire().
+ */
static inline
void hazptr_release(struct hazptr_ctx *ctx, void *addr)
{