Module sync/cond
Condition variable for thread synchronization.
A Cond parks threads until another thread announces that something
they are waiting for may now be true. It carries no state of its own
beyond the wait queue: the CONDITION lives in data the caller protects
with a Mutex, and every wait must be wrapped in a loop that re-checks
it (std/sync/waitgroup.yo's wait is the reference shape).
Stability
unstable — the operations are Rust's Condvar (wait, wait_timeout,
signal/broadcast for notify_one/notify_all) and are tested
(tests/sync/cond.test.yo, 6 cases, added 2026-09-10), but the CALLING
CONVENTION is known-wrong and slated to change: wait takes a
*__YO_THREAD_SYNC_TYPE raw pointer that callers can only obtain from
Mutex._raw_handle_ptr(). plans/STD_API_STABILIZATION.md §4
"Concurrency" carries taking _raw_handle_ptr off the public surface as
an open row, and doing that changes every signature in this file.
Freezing follows a Cond that couples to a Mutex(T) without naming
its OS primitive — Rust passes a MutexGuard, and Mutex(T)
deliberately has no guard type to pass.
Types
Reference-counted condition variable with automatic cleanup via Dispose.
Uses atomic reference counting for safe cross-thread sharing.
Fields
| Name | Type | Description |
|---|---|---|
cv | __YO_COND_TYPE |
Trait Implementations
impl(Cond, ...)
new : (Cond) fn() -> CondCreate a condition variable with an empty wait queue — Rust's
Condvar::new.
On Linux the underlying pthread_cond_t is bound to
CLOCK_MONOTONIC here, at construction, because the clock is a
property of the condvar and not of an individual wait; that is what
makes wait_timeout immune to a wall-clock step.
Returns: Cond
wait : (Cond) fn(self : Cond, handle : *(__YO_THREAD_SYNC_TYPE)) -> unitBlock until another thread calls signal or broadcast — Rust's
Condvar::wait, with the mutex passed as its raw handle.
handle must be the handle of a mutex this thread currently
holds, obtained as m._raw_handle_ptr(). The wait atomically
releases that mutex and parks; when it returns, the mutex is held
again. Passing the handle of a mutex that is not held, or of a
different mutex than other waiters use, is undefined behaviour in the
C primitive underneath — this is the sharp edge the open
"_raw_handle_ptr off the public surface" row exists to remove.
A return does not mean the condition is true. Condvars are
allowed to wake spuriously, and broadcast wakes every waiter
including those whose predicate is still false, so the call belongs
inside a re-checking loop:
m._raw_lock();
while(runtime(!ready()), {
cv.wait(m._raw_handle_ptr());
});
m._raw_unlock();
A signal sent while no thread is waiting is LOST — it is not
remembered for the next waiter — which is why the predicate must be
mutated under the same mutex before signalling. Use wait_timeout
when the wait needs a deadline.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Cond | |
handle | *(__YO_THREAD_SYNC_TYPE) |
Returns: unit
wait_timeout : (Cond) fn(self : Cond, handle : *(__YO_THREAD_SYNC_TYPE), timeout : Duration) -> boolWait until signalled or until timeout elapses. Rust's
Condvar::wait_timeout.
Returns true when the wait ended in a wake and false only on a
genuine timeout. A true may be a SPURIOUS wake — that is the condvar
contract, not a defect — so the caller must re-check its predicate
either way:
deadline := Duration.from_millis(i64(500));
while(runtime(!ready()), {
cond(
!cv.wait_timeout(m._raw_handle_ptr(), deadline) => {
// timed out — give up, or re-arm with the remaining budget
break;
},
true => ()
);
});
The timeout is per WAIT, not per loop. A spurious wake restarts the
full timeout, so a loop like the one above can outlast its nominal
budget. Track an Instant deadline and pass the REMAINING time if the
total matters — the same caveat Rust states, and the reason
wait_timeout_while exists there.
The clock is monotonic wherever the platform allows it, so a wall-clock
step cannot lengthen or cut short the wait. A negative or zero timeout
does not block.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Cond | |
handle | *(__YO_THREAD_SYNC_TYPE) | |
timeout | Duration |
Returns: bool
signal : (Cond) fn(self : Cond) -> unitWake ONE waiting thread, if any — Rust's Condvar::notify_one.
Which one is the platform's choice, not FIFO. The call never blocks
and does nothing when no thread is waiting; a signal sent to an empty
queue is lost, so publish the change to the predicate under the mutex
first and signal after. Use broadcast when more than one waiter
could be satisfied, or when different waiters are waiting for
different predicates on the same condvar — waking the wrong one there
loses the notification for good.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Cond |
Returns: unit
broadcast : (Cond) fn(self : Cond) -> unitWake EVERY waiting thread — Rust's Condvar::notify_all.
Each woken thread re-acquires the mutex one at a time and re-checks
its own predicate, so this is the safe default whenever the change
could satisfy several waiters (a counter reaching zero, a queue going
from empty to non-empty for many consumers). The cost is a thundering
herd: N wakes and N re-checks where signal would have done one.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Cond |
Returns: unit
impl(Cond, Dispose(...))
dispose : (Cond) fn(self : Cond) -> unitRelease the resources self owns — a file descriptor, a socket, a lock,
a buffer the allocator handed out. Called automatically when the last
reference to the value goes away, so an implementor never calls it
directly and must tolerate being the only one who ever does.
It must be safe to run exactly once: the runtime calls it at refcount
zero, and a type that also exposes an explicit close/release is
responsible for making the second call a no-op.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Cond |
Returns: unit