3. Memory ordering
Fields marked atomic MUST be read and written with atomic operations of their full width: loads with acquire, stores with release semantics, unless stated otherwise. All other fields are plain memory protected by one of the three mechanisms below.
3.1 Registry lock (non-real-time only)
Every change to the endpoint, input or output tables, and every change to a
connection, happens while holding header.registry_lock:
- Acquire: compare-and-swap 0 → own PID. While it fails, yield; after a few attempts, sleep 1 ms between attempts.
- If the holder's process no longer exists, a waiter MAY take the lock over with compare-and-swap holder PID → own PID.
- Give up after 2 s (
BB_ERR_TIMEOUT). - Release: store 0.
The lock MUST NOT be taken on an audio thread. Readers that need consistent strings (names in listings) SHOULD take it too.
3.2 Seqlocks (audio thread readers)
Output slots and the connection list of an input use sequence locks.
Writer (holding the slot exclusively; for inputs, holding the registry lock):
s = load_relaxed(seq);
store_relaxed(seq, s + 1); /* odd: writing */
fence_release();
/* write the protected fields */
store_release(seq, s + 2); /* even: consistent */
Reader:
s1 = load_acquire(seq);
if (s1 & 1) retry_or_skip;
/* copy the protected fields */
fence_acquire();
s2 = load_relaxed(seq);
if (s1 != s2) retry_or_skip;
A reader on an audio thread MUST NOT spin on a seqlock; it skips the candidate and uses another block or its held value (section 5).
3.3 Publication order
To activate a record, fill every field first, then store state = 1 with
release. To change an output owner's UID, store endpoint_uid (atomic) and
then increment registry_generation. Readers on the audio thread cache the
mapping of a cable to an output index and recompute it only when
registry_generation or the input's seq changes. Before using a cached
output they re-check state, incarnation and endpoint_uid.