Issue 1017: Unspecified timing and synchronization for cnd_t functions

Authors: Jens Gustedt
Date: 2025-06-24
Submitted against: C23
Status: Open

The timing and synchronization of calls to cnd_t functions are underspecified. In particular the text for cnd_broadcast and cnd_signal talk about

... threads that are blocked on the condition variable pointed to by cond at the time of the call

Fortunately, this lack of precision has not yet resulted in severe misunderstandings or even bugs. Nevertheless, it has merely pushed implementors to go on the safe side, perhaps imposing too much synchronization constraints on calls to cnd_t functions. For example, it seems that many implementations regulate the access to cnd_t variables through an internal mutex and thus impose acquire-release semantics not only on the user mutex *mtx that is involved in the calls to the wait functions, but on all calls to cnd_signal and cnd_broadcast.

Liaison

The feature had been inspired by a similar feature in POSIX, but it does not seem that the situation is better, there. Their text probably dates from before synchronization and happens-before had been formalized in C11, so it is as vague as the text in C23.

Questions

What is the "time of the call", here?

The only inter-thread time model that we have for multi-threaded executions is the happens-before relation, and that relation need synchronization either through atomic objects, fences or mutexes.

Which atomic operations would participate in such a happens-before relationship between calls.

Is there implicitly a modification order on cnd_t objects as a whole?

Such a modification order would be consistent with the handling we have for mtx_t where C23 7.28.4.1 (7.30.4.1 in working draft N3550) introduces such a (poorly worded) order.

If there is such a modification order on a cnd_t which are the functions that participate in that modification order?

One could assume that wait functions have two entries in the modification order, one for the entry into the call (maybe with release semantics) and the other for a wakeup (maybe with acquire semantics).

Signal and broadcast operations would probably only count each as one operation. For these function not even some form of atomicity is currently specified. Even the general paragraph 7.1.4 p5 that talks about race conditions for library functions does not help: its provision is explicitly only formulated for races that would result from functions that access hidden state. Since these cnd_t functions potentially access the same data, nothing currently guarantees that calls to these functions is atomic.

Are cnd_broadcast and cnd_signal load-modify-store operations on the cnd_t?

If yes, what would their memory ordering be? Two plausible candidates would be memory_order_relaxed and memory_order_acq_rel.

How do the different operations on cnd_t synchronize with each other?

Suggested partial solution

I think it is unavoidable to add a "General" section to C23 7.28.3 (7.30.3 in working draft N3550) that describes a consistency model between different calls that act on the same cnd_t object. This would best be done by claiming that there is a over-all modification order on a cnd_t variable on which wait, signal and broadcast operate atomically.

How the synchronization constraints for these operations would be handled seems to be quite open and needs harmonization between POSIX, C++ and C at the least.