Skip to main content
Each region has an atomic 32-bit notification counter. notify() increments it with release ordering. Each handle tracks the last counter value it consumed, starting at zero. wait(timeout_ms) returns when the counter differs from that handle’s cursor. A notification sent before the wait remains pending. try_wait() checks and consumes a pending change without sleeping, returning true when it finds one.
Several notifications can coalesce into one observed change. This API signals that state changed; it does not queue every event. A handle opened after earlier notifications can observe them on its first wait. After a full wrap of 2^32 unobserved notifications, the counter can equal the old cursor again.

Timeouts

Timeouts are in milliseconds. Zero checks for a pending change and otherwise returns TimedOut. u32::MAX is about 49.7 days, not an infinite wait.
The C function zinc_try_wait returns 0 for a pending change, -11 (WouldBlock) if none is pending, and -22 for a null handle.

Multiple readers

Open a separate handle for each reader that needs an independent notification cursor. Linux wakes all threads blocked on the region’s futex. Concurrent waits on the same handle share a cursor and can coalesce observations.

Data access

A successful wait acquires the writer’s release notification, publishing writes that happened before it. This does not prevent the writer from modifying data while readers use it. Use atomic data or an application protocol that prevents concurrent writes and reads. For a reusable plain-data buffer, readers need to acknowledge completion before the writer overwrites it.

Linux

notify() always calls FUTEX_WAKE, even when no thread is waiting. wait() checks the counter before calling FUTEX_WAIT, then checks it again after waking. Spurious wakes and interrupted syscalls retry within the original timeout. A wake alone does not count as a notification. See FUTEX_WAIT for the kernel’s wait semantics.

macOS

The fallback polls for 64 iterations, then sleeps for up to 50 microseconds between checks. Sleeping reduces idle CPU use, but wake latency depends on the scheduler. A shared kernel wait backend remains on the roadmap.