Zinc provides a lightweight signal mechanism for cross-process synchronization. It is not a mutex or condition variable. It is a one-shot “data changed” signal.Documentation Index
Fetch the complete documentation index at: https://mine-27913f41.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
How it works
The header of every shared region contains anotify_seq field: an atomic 32-bit counter. The notify/wait API builds on this single counter:
- The writer calls
notify(), which atomically incrementsnotify_seqand wakes any waiting threads. - The reader calls
wait(), which blocks untilnotify_seqchanges from the value it last observed.
wait() tracks a per-handle last_seq value instead. It waits for the counter to change from the last value it saw, not from whatever it currently reads.
On Linux, wait() uses the futex syscall. It parks the calling thread in the kernel until the futex address changes or the timeout expires. Zero overhead with no waiters: notify() still does an atomic increment and a FUTEX_WAKE, but with no waiters the wake is a no-op.
On macOS and other non-Linux platforms, the wait uses an adaptive spin loop with exponential backoff. It spins for a brief period, then yields the thread, periodically checking for the timeout.
Usage pattern
The typical pattern is a producer-consumer arrangement:Timeouts
wait() accepts a timeout in milliseconds. If no notification arrives within the timeout, the call returns Err(TimedOut) (or false / an exception in language adapters that wrap the error).
A timeout of u32::MAX (4294967295) is effectively infinite. On Linux, the futex timespec for this value is about 49.7 days. For practical purposes, it will not expire.
Multiple waiters
notify() wakes all waiters simultaneously. Each waiter that returns from wait() reads the current notify_seq into its last_seq and returns. All waiters see the same counter value, which means they all see the same generation of data.
This is by design: if you have one writer and multiple readers, all readers are notified of every update. Each reader independently reads the shared data. There is no need for coordinated wake order.
Caveats
Notify coalesces. If the writer callsnotify() twice before the reader calls wait(), the reader sees only one notification. The sequence number goes from N to N+2. The reader’s wait() unblocks because N+2 != last_seq. But the reader does not know there were two updates. That works for state-based sync (the consumer always reads the latest data), not event-based sync (where every event must be processed).
Notify and wait are not a mutex. Writing to shared memory while another thread is reading can produce torn reads if the data is larger than the native word size. If you need atomic multi-field updates, layer your own synchronization on top. A simple approach: use the first 8 bytes of the data area as a generation counter, increment it at the start of a write and again at the end. Readers can then detect in-progress writes by checking whether the two counters match.
Wait is not re-entrant. Calling wait() recursively from different threads is not tested and not guaranteed to work correctly. Each handle should be used from at most one waiting thread at a time.
Linux futex details
On Linux, the notify/wait path goes through thefutex syscall:
notify()callsFUTEX_WAKEwithwake_count = i32::MAXto wake all waiters.wait()callsFUTEX_WAITwith atimespecderived from the millisecond timeout.
futex syscall is well understood. Waking no waiters costs a single syscall. Waking one waiter costs two (one in the waker, one returning in the waiter after the kernel unparks it). Roundtrip latency is typically under 1 microsecond on modern hardware.
macOS and other platforms
On macOS,notify() increments the sequence number atomically but does not perform a kernel-assisted wake. The wait() function spins in user space with a yield after 256 iterations. This is less efficient than futex but avoids the complexity of Mach semaphores or the private ulock API. Future versions may add a ulock backend for macOS.