std.sync
std.sync
Section titled “std.sync”std.sync provides the current synchronization floor for Janus :service and :cluster work.
There are two layers:
std.sync.modkeeps the pthread-backed compatibility primitives and exportsSpinLock/SpinMutex.- Leaf modules expose the Janus-native surface: atomics, parker,
Mutex[T], bounded channels, mailboxes, and cancellation.
Import Janus-native primitives from their leaf modules:
use std.sync.atomics.mod as atomicsuse std.sync.parker as parkeruse std.sync.mutex as mtxuse std.sync.chan as chanuse std.sync.mailbox as mbuse std.sync.cancel as cancelDo not rely on re-exports from std.sync.mod for the leaf modules yet. Those re-exports are deferred until the current cross-module Atomic[T] layout gap is closed.
Live Surface
Section titled “Live Surface”| Module | Primitive | Status |
|---|---|---|
std.sync.atomics.mod | Atomic[T], ordering-checked atomic ops | Live |
std.sync.parker | permit-model Parker over Linux futexes | Live |
std.sync.mutex | guard-based Mutex[T] | Live, single-thread smoke covered |
std.sync.chan | bounded SPSC Chan[T] | Live |
std.sync.mailbox | actor-shaped Mailbox[Msg] wrapper | Live |
std.sync.cancel | one-shot CancelToken | Live |
std.sync.mod | pthread Mutex, RWLock, Condvar | Compatibility layer |
std.sync.mod | SpinLock, SpinMutex | Live |
Atomic[T]
Section titled “Atomic[T]”Atomic[T] is the typed wrapper over the SPEC-059 atomic intrinsic substrate. It gates T through the compiler-owned AtomicEligible trait and validates ordering choices at monomorphization.
use std.sync.atomics.mod as atomics
var flag = atomics.new[u32](0)atomics.store[u32, .release](&flag, 1)let seen = atomics.load[u32, .acquire](&flag)Use Atomic[u32] for flags today. Atomic[bool] is deferred because the current LLVM path rejects i1 atomics.
Parker
Section titled “Parker”Parker is the wait/wake substrate used by Janus-native blocking primitives.
use std.sync.parker as parker
var p = parker.parker_new()parker.parker_unpark(&p)parker.parker_park(&p)The contract is permit-based:
parker_unparkdeposits a permit.parker_parkconsumes an existing permit or blocks until one arrives.parker_park_timeoutreturnsUnparkedorTimedOut.
The current backend is Linux futexes. NexusOS backend selection is future work.
Mutex[T]
Section titled “Mutex[T]”std.sync.mutex.Mutex[T] protects a value and returns a guard.
use std.sync.mutex as mtx
var mu = mtx.mutex_new[u32](41)var guard = mtx.mutex_lock[u32](&mu)let value = mtx.guard_value_of[u32](&guard)value.* = value.* + 1mtx.mutex_release[u32](&guard)The mutex uses a three-state futex-style algorithm:
0: unlocked1: locked without known waiters2: locked with possible waiters
Keep the mutex address stable while any thread may park on it. Moving a parker-backed type breaks the futex address contract until Pin / move-on-drop support lands.
Chan[T]
Section titled “Chan[T]”Chan[T] is a bounded single-producer, single-consumer ring buffer over caller-owned backing storage.
use std.sync.chan as chan
var backing: [4]u32 = .undefinedvar c = chan.chan_new[u32](backing[..])
let sent = chan.chan_send[u32](&c, 42)if sent != chan.ChanError.Ok do returnend
let got = chan.chan_recv[u32](&c)Important semantics:
- FIFO ordering.
- Bounded capacity from the backing slice.
- Close is idempotent.
- Receivers drain buffered values before closed state becomes terminal.
chan_recvreturnsnullonly when the channel is closed and drained.
Struct payloads work directly through chan_new — no special constructor:
pub struct Msg { kind: u32, value: u32 }
var slots: [8]Msg = .undefinedvar c = chan.chan_new[Msg](slots[..])Both fields round-trip per-call across multiple sends. (The earlier
chan_new_with_cap[T] workaround was retired once the cross-module
generic struct-by-value path closed — see the v2026.5.15 release note.)
Timed and Cancellable Receive
Section titled “Timed and Cancellable Receive”chan_recv_timeout waits for a relative nanosecond duration:
let msg = chan.chan_recv_timeout[u32](&c, 1_000_000)if msg == null do // timeout, or closed-and-drainedendchan_recv_cancellable also observes a CancelToken:
use std.sync.cancel as cancel
var tok = cancel.cancel_token_new()let msg = chan.chan_recv_cancellable[u32](&c, &tok)The receive helpers return ?T. null merges timeout, cancellation, and closed-drained. If a caller needs the cause, check chan_is_closed or cancel_token_is_cancelled after return.
Mailbox[Msg]
Section titled “Mailbox[Msg]”Mailbox[Msg] wraps Chan[Msg] with actor-oriented names.
use std.sync.mailbox as mb
pub struct Cmd { kind: u32, payload: u32 }
var slots: [16]Cmd = .undefinedvar box = mb.mailbox_new[Cmd](slots[..])
_ = mb.mailbox_send[Cmd](&box, Cmd{ kind: 1, payload: 42 })let got = mb.mailbox_recv[Cmd](&box)Struct messages preserve every field per-call. chan_smoke scenario 9
and test-mailbox-struct-payload lock this end-to-end.
The current mailbox inherits the channel’s SPSC contract. Multi-producer fan-in is not part of the shipped surface yet.
CancelToken
Section titled “CancelToken”CancelToken is a one-shot, idempotent cancellation signal.
use std.sync.cancel as cancel
var tok = cancel.cancel_token_new()cancel.cancel_token_cancel(&tok)
if cancel.cancel_token_is_cancelled(&tok) do returnendFor channel or mailbox consumers, cancellation code should usually signal the token and close the queue so blocked receivers wake promptly:
cancel.cancel_token_cancel(&tok)mb.mailbox_close[Cmd](&box)Current Limits
Section titled “Current Limits”Chan[T]andMailbox[Msg]are SPSC only.- Multi-thread proof coverage is still narrower than the API shape.
- Leaf-module re-exports from
std.sync.modare deferred. Atomic[bool]andMutex[bool]are deferred.- NexusOS parker backend is deferred.