C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Draft proposal for fixing undefined behavior in `atomic_notify_*` operations

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Thu, 15 Jan 2026 12:30:51 +0000
On Thu, 15 Jan 2026 at 00:49, Ryan P. Nicholl via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> I'm looking to circulate a draft for a defect report proposal to get
> feedback.
>

This is not a defect, it's a design change.

I see you're still completely ignoring the feedback that this doesn't make
sense for the generic std::atomic template, it only makes sense for
specific specializations (e.g. std::atomic<int> on Linux,
std::atomic<uint64_t> on macOS, ...)
Maybe it would make sense for std::atomic_signed_lock_free
and std::atomic_unsigned_lock_free but not for *all* atomics.

None of the pthread implementations you've looked at rely on being able to
do this on arbitrary types, they work with integers of one or two very
specific widths.



> Defect Report: Well-defined behavior for atomic_notify_* on expired
> pointers
>
> *Document Number:* DxxxxR0
>
> *Date:* 2026-01-14
>
> *Audience:* SG1 (Concurrency), LEWG (Library Evolution)
>
> *Project:* ISO/IEC JTC1/SC22/WG21 14882:2020 (C++20)
>
> *Reply-to:* Ryan P. Nicholl <rnicholl_at_[hidden]> (do I put my name
> here or should this go to the standard committee?)
> 1. Abstract
>
> This paper proposes a defect report for C++20 to clarify the requirements
> for the non-member atomic notification functions (atomic_notify_one and
> atomic_notify_all). Specifically, it proposes clarifying that these
> functions remain well-defined even if the pointer provided refers to an
> object whose lifetime has ended or to an address where a new atomic object
> exists to which the pointer lacks provenance.
> 2. Motivation
>
> In the implementation of custom user defined synchronization primitives
> similar to std::mutex or std::latch, using the C++20 <atomic> waiting and
> notifying facilities, a race condition would occur if the pointer is
> required to remain valid at the time of the call to atomic_notify_one or
> atomic_notify_all.
>
> Consider a distinct implementation of a mutex. The unlock operation
> typically involves:
>
> 1. Atomically storing a "free" state to the atomic flag/integer.
> 2. Notifying waiting threads.
>
> void unlock() { state.store(0, std::memory_order_release); std::atomic_notify_one(&state);}
>
> If a thread acquires the lock immediately after the store in step 1, it
> may proceed to destroy the mutex object while the unlocking thread is still
> executing step 2 (the notification).
>
>
> However, the notification operation physically requires only the *address*
> to identify the wait queue; it does not need to access the object's
> value or metadata, and from my investigations it does not appear that any
> major implementation does this. Current implementations of std::latch rely
> on internal wake function implementations being safe even if the atomic
> object no longer exists.
>
> If atomic_notify_* is treated as a standard function requiring a valid
> pointer, implementing safe, custom synchronization primitives becomes
> impossible without external coordination or UB.
>
> It is unclear from the existing text whether these functions are safe to
> call on a now-expired pointer. This paper proposes adding clarification
> text standardizing the behavior of existing implementations allowing it.
>
> This proposal seeks to make atomic_notify_one and atomic_notify_all safe
> to call with concurrently expiring pointers, allowing the program to
> remain well-defined even if the target atomic is concurrently destroyed.
> 3. Impact on the Standard
>
> This proposal affects section 31 (Atomics) of the C++ Standard. It
> specifically modifies the semantics of non-member functions defined in [atomics.nonmembers]
> (referenced in the provided text as 31.9).
>
> References to the provided text:
>
> -
>
> *31.6* Waiting and notifying [atomics.waitnotify] defines the general
> terminology for these operations.
> -
>
> *31.6 Waiting and notifying* defines the general terminology for these
> operations.
> -
>
> *31.7.1* Operations [atomics.ref.ops] defines the member functions
> notify_one and notify_all.
> -
>
> *31.9 Non-member functions [atomics.nonmembers]* defines the
> non-member functions themselves.
>
> 4. Proposed Wording
>
> The following changes are relative to N4860 (C++20).
> 31.6 Waiting and notifying [atomics.waitnotify]
>
> -(4) A call to an atomic waiting operation on an atomic object M is
> eligible to be unblocked by a call to an atomic notifying operation *on a
> pointer to* M if there exist side effects X and Y on M such that:
>
> —(4.1) the atomic waiting operation has blocked after observing the
> result of X,
>
> —(4.2) X precedes Y in the modification order of M, and
>
> —(4.3) Y happens before the call to the atomic notifying operation
>
> 31.7.1 Operations [atomics.ref.ops]
>
> void notify_one() const noexcept; -(25) Effects: Unblocks the execution
> of at least one atomic waiting operation on *ptr that is eligible to be
> unblocked (31.6) by this call, if any such atomic waiting operations
> exist. -(26) Remarks: This function is an atomic notifying operation (31.6
> ) on *the pointer to* atomic object * ptr. void notify_all() const
> noexcept; -(27) Effects: Unblocks the execution of all atomic waiting
> operations on *ptr that are eligible to be unblocked (31.6) by this call.
> -(28) Remarks: This function is an atomic notifying operation (31.6) on *the
> pointer to* atomic object * ptr.
>
> 31.9 Non-member functions [atomics.nonmembers]
>
> 31.9 Non-member functions [atomics.nonmembers]
>
> -(1) *Except for atomic_notify_one and atomic_notify_all, a* A non-member
> function template whose name matches the pattern atomic_f or the pattern
> atomic_f _- explicit invokes the member function f , with the value of
> the first parameter as the object expression and the values of the
> remaining parameters (if any) as the arguments of the member function
> call, in order. An argument for a parameter of type atomic::value_type* is
> dereferenced when passed to the member function call. If no such member
> function exists, the program is ill-formed.
>
> -(2) [Note: The non-member functions enable programmers to write code
> that can be compiled as either C or C++, for example in a shared header
> file. — end note]
>
> *template<class T>* *void atomic_notify_one(atomic<T>*);*
>
> *-(3) Effects: Unblocks the execution of at least one atomic waiting
> operation that is eligible to be unblocked (31.6) by this call, if any such
> atomic waiting operations exist.*
>
> *-(4) If the pointer is no longer valid or the destination object is
> concurrently destroyed, it is unspecified whether any waiters on any atomic
> object at the same address are spuriously unblocked, but the program does
> not exhibit undefined behavior.*
>
> *-(5) Remarks: This function is an atomic notifying operation (31.6).*
>
> *template<class T>* *void atomic_notify_all(atomic<T>*);*
>
> *-(6) Effects: Unblocks the execution of all atomic waiting operations
> that are eligible to be unblocked (31.6) by this call.* *-(7) If the
> pointer is no longer valid or the destination object is concurrently
> destroyed, it is unspecified whether any waiters on any atomic object at
> the same address are spuriously unblocked, but the program does not exhibit
> undefined behavior.* *-(8) Remarks: This function is an atomic notifying
> operation (31.6).*
>
> 5. Rationale for "Unspecified" Unblocking
>
> If the pointer is invalid (e.g., the ABA problem on the address where an
> object was destroyed and a new one created), the notification might
> inadvertently target the wait queue of the *new* object.
>
> - We cannot guarantee the *old* waiters are woken (the queue might be
> gone).
> - We cannot guarantee the *new* waiters are ignored (the address
> collision might map to the same kernel bucket).
>
> Therefore, the side effects on waiters are *unspecified*, but the rigid
> requirement that the program must not exhibit UB is added. This ensures
> that custom synchronization primitives can be implemented safely without
> risking UB due to pointer validity issues.
>
>
>
>
> --
> Ryan P. Nicholl
> Tel: (678)-358-7765
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2026-01-15 12:31:11