Date: Wed, 14 Jan 2026 23:59:55 +0300
On 14 Jan 2026 03:55, Ryan P. Nicholl via Std-Proposals wrote:
> It has come to my attention that it isn't possible to implement data
> structures like std::latch or a uselock using the C++ atomic library
> without undefined behavior.
>
> Namely, std::latch guarantees that count_down doesn't cause a race even
> if there are waiters, this isn't possible to implement on the abstract
> machine unless the notification can be safely executed atomically with
> the value set. Ordinary notify_one has undefined behavior if it races
> with the destructor of the atomic object.
I'm not sure what the destructor of the atomic has to do with
count_down. Assuming std::latch implementation is using std::atomic
internally (which is not the only possible implementation), I don't see
why count_down would ever destroy the atomic.
If you're having in mind a specific usage scenario involving std::latch,
where one of the waiters destroys the latch as soon as it wakes up, then
that would be an error of the user of std::latch.
> Of course, it's possible to implement std::latch using things like
> SYS_futex, so this isn't impossible to implement per se, only impossible
> to implement using the C++ standard library without relying on undefined
> behavior.
>
> I would propose adding atomic_compare_exchange_and_notify. This would
> have the same behavior as atomic compare_exchange followed by notify,
> except that it doesn't cause undefined behavior if a waiter unblocks on
> the new value and destroys the atomic, which could race with notify.
Aside from the issue with the motivating example with std::latch,
blessing only compare_exchange but not other modifying operations seem
unnecessarily specific.
Note that it is generally impossible to make the modifying operation and
the notifying operation atomic with the currently available futex-like
system APIs. For a lock-free atomic, the modifying operation is
typically one or few instructions that are distinct from a notifying
syscall, and it is always possible that a blocked thread wakes up and
destroys the atomic after the modifying operation has completed but
before the notifying syscall has started. Note that "destroying" may not
be limited to just C++ object model but also include e.g. unmapping
memory location where the atomic was placed.
Even if you move the modifying operation inside the notifying syscall,
under the lock that presumably protects the in-kernel list of waiters,
that wouldn't help because a thread calling a wait may observe the
modified value of the atomic before blocking and return immediately and
destroy the atomic.
So the proposed operation doesn't really solve anything. The only thing
that you could ask for is to bless notifying operations by saying they
don't access the atomic and don't need a valid object at its location.
Or not say it (the status quo), which means users must ensure the atomic
object exists at all times while its operations are called. For the
std::latch example, this means users must not destroy it until all
waiters and callers of count_down have returned from the respective
methods (e.g. by using std::shared_ptr<std::latch>).
> It has come to my attention that it isn't possible to implement data
> structures like std::latch or a uselock using the C++ atomic library
> without undefined behavior.
>
> Namely, std::latch guarantees that count_down doesn't cause a race even
> if there are waiters, this isn't possible to implement on the abstract
> machine unless the notification can be safely executed atomically with
> the value set. Ordinary notify_one has undefined behavior if it races
> with the destructor of the atomic object.
I'm not sure what the destructor of the atomic has to do with
count_down. Assuming std::latch implementation is using std::atomic
internally (which is not the only possible implementation), I don't see
why count_down would ever destroy the atomic.
If you're having in mind a specific usage scenario involving std::latch,
where one of the waiters destroys the latch as soon as it wakes up, then
that would be an error of the user of std::latch.
> Of course, it's possible to implement std::latch using things like
> SYS_futex, so this isn't impossible to implement per se, only impossible
> to implement using the C++ standard library without relying on undefined
> behavior.
>
> I would propose adding atomic_compare_exchange_and_notify. This would
> have the same behavior as atomic compare_exchange followed by notify,
> except that it doesn't cause undefined behavior if a waiter unblocks on
> the new value and destroys the atomic, which could race with notify.
Aside from the issue with the motivating example with std::latch,
blessing only compare_exchange but not other modifying operations seem
unnecessarily specific.
Note that it is generally impossible to make the modifying operation and
the notifying operation atomic with the currently available futex-like
system APIs. For a lock-free atomic, the modifying operation is
typically one or few instructions that are distinct from a notifying
syscall, and it is always possible that a blocked thread wakes up and
destroys the atomic after the modifying operation has completed but
before the notifying syscall has started. Note that "destroying" may not
be limited to just C++ object model but also include e.g. unmapping
memory location where the atomic was placed.
Even if you move the modifying operation inside the notifying syscall,
under the lock that presumably protects the in-kernel list of waiters,
that wouldn't help because a thread calling a wait may observe the
modified value of the atomic before blocking and return immediately and
destroy the atomic.
So the proposed operation doesn't really solve anything. The only thing
that you could ask for is to bless notifying operations by saying they
don't access the atomic and don't need a valid object at its location.
Or not say it (the status quo), which means users must ensure the atomic
object exists at all times while its operations are called. For the
std::latch example, this means users must not destroy it until all
waiters and callers of count_down have returned from the respective
methods (e.g. by using std::shared_ptr<std::latch>).
Received on 2026-01-14 20:59:59
