C++ Logo

std-proposals

Advanced search

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

From: Ryan P. Nicholl <rnicholl_at_[hidden]>
Date: Thu, 15 Jan 2026 19:49:51 +0000
The implementation of std::atomic_notify_* on libc++, gcc, musl and microsoft STL is already in compliance with the proposed wording change.

They are already​ fully implemented, this proposal would only be a standardese fix. You cannot tell me this is not possible to do, it is already done. I can write code that will work fine on all platforms, but according to the standard C++ language it's not allowed. AFAIK, all platforms already do this. If you know an implementation that doesn't do this with std::atomic_notify_*, I would like to know what that is. Saying "this can't be done" without doing any investigation on your own is not that productive. There are certainly some OS where you can't directly wait on the atomic address itself if the wait area is larger than the atomic region, using a single​ system call, that doesn't mean it isn't possible to do using another algorithm, though. As it turns out, address monitor queues work for an arbitrarily sized type. Is it a bit slower? Yes. Is it unimplementable? No. Do all atomic implementations use monitor queues for types that don't fit in the os sync primitives? As far as I can tell, yes. And of course, even lock based atomics work fine with monitor queues too, so I see no reason not to allow it. If you can wait on any value of any size, you can create a monitor queue for values of arbitrary sizes.

--
Ryan P. Nicholl
Tel: (678)-358-7765
On Thursday, January 15th, 2026 at 04:31, Jonathan Wakely <cxx_at_[hidden]> wrote:
> 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 foratomic_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_oneandatomic_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 tostd::mutexorstd::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 toatomic_notify_oneoratomic_notify_all.
>>
>> Consider a distinct implementation of a mutex.Theunlockoperation typically involves:
>>
>> - Atomically storing a "free" state to the atomic flag/integer.
>> - 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 theaddressto 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 ofstd::latchrely on internal wake function implementations being safe even if the atomic object no longer exists.
>>
>> Ifatomic_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 makeatomic_notify_oneandatomic_notify_allsafe 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.6Waiting and notifying [atomics.waitnotify] defines the general terminology for these operations.
>>
>> -
>>
>> 31.6 Waiting and notifyingdefines the general terminology for these operations.
>>
>> -
>>
>> 31.7.1Operations [atomics.ref.ops] defines the member functionsnotify_oneandnotify_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 atomicnotifying operationon a pointer toM 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 beunblocked(31.6)by this call,if any such atomic waiting operations exist.-(26)Remarks:This function is an atomic notifying operation(31.6)onthe pointer toatomicobject *ptr.void notify_all()const noexcept;-(27)Effects:Unblocks the execution of all atomic waiting operations on*ptr that are eligible to beunblocked(31.6)by this call.-(28)Remarks:This function is an atomic notifying operation(31.6)onthe pointer toatomicobject *ptr.
>>
>> 31.9 Non-member functions [atomics.nonmembers]
>>
>>> 31.9 Non-member functions[atomics.nonmembers]
>>>
>>> -(1)Except foratomic_notify_oneandatomic_notify_all, aAnon-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 andthe values of the remaining parameters(if any)as the arguments of the member function call,in order.Anargument for a parameter of type atomic::value_type*is dereferenced when passed to the memberfunction 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 orC++,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 thenewobject.
>>
>> - We cannot guarantee theoldwaiters are woken (the queue might be gone).
>> - We cannot guarantee thenewwaiters are ignored (the address collision might map to the same kernel bucket).
>>
>> Therefore,the side effects on waiters areunspecified,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 19:49:58