code wiki / (root) / nx_cond.nx

nx_cond.nx source

↩ module page · 97 lines · 3111 B

1// nx_cond.nx -- condition variable on top of nx_mutex + futex. 2// 3// Condition variables let a thread block until another thread 4// signals "the condition you were waiting on is now true". The 5// classic producer/consumer pattern: 6// 7// producer: consumer: 8// mutex.lock mutex.lock 9// queue.push(item) while queue.empty: 10// cond.signal cond.wait(mutex) # atomic unlock+sleep 11// mutex.unlock item = queue.pop 12// mutex.unlock 13// 14// Implementation uses Linux futex with a sequence counter. Each 15// nx_cond_wait reads the current seq, drops the mutex, blocks on 16// futex(seq), then reacquires the mutex on wakeup. signal/broadcast 17// bump seq + futex_wake. 18// 19// Pairs with nx_mutex (the lock you must hold when calling wait / 20// signal) + nx_atom (eventual SMP correctness). 21 22// nx_safety_envelope: 23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 24// sil_target: SIL1 25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 26// verdict: NOT_YET_EVALUATED 27 28import "syscalls.nx" 29import "nx_mutex.nx" 30 31const NX_SYS_FUTEX_2: i64 = 98 32 33const NX_FUTEX_WAIT_2: i64 = 0 34const NX_FUTEX_WAKE_2: i64 = 1 35const NX_FUTEX_PRIVATE_2: i64 = 128 36 37struct NxCond { 38 seq: i64, 39} 40 41const NX_COND_BYTES: i64 = 8 42 43func nx_cond_new() -> *NxCond { 44 let raw: *u8 = sys_mmap(NX_COND_BYTES) 45 let c: *NxCond = raw as *NxCond 46 c.seq = 0 47 return c 48} 49 50// Atomic-unlock-and-wait pattern. Caller MUST hold `m`; we drop 51// it, wait for the next seq bump, then reacquire. 52func nx_cond_wait(c: *NxCond, m: *NxMutex) -> i64 { 53 let seq_at_wait: i64 = c.seq 54 nx_mutex_unlock(m) 55 let op: i64 = NX_FUTEX_WAIT_2 | NX_FUTEX_PRIVATE_2 56 __syscall(NX_SYS_FUTEX_2, c as i64, op, seq_at_wait, 0, 0, 0) 57 nx_mutex_lock(m) 58 return 0 59} 60 61// Wake one waiter. Bumps the seq counter so any waiter that 62// hasn't yet entered the syscall sees a different value and 63// returns -EAGAIN immediately. 64func nx_cond_signal(c: *NxCond) -> i64 { 65 c.seq = c.seq + 1 66 let op: i64 = NX_FUTEX_WAKE_2 | NX_FUTEX_PRIVATE_2 67 return __syscall(NX_SYS_FUTEX_2, c as i64, op, 1, 0, 0, 0) 68} 69 70// Wake all waiters. 71func nx_cond_broadcast(c: *NxCond) -> i64 { 72 c.seq = c.seq + 1 73 let op: i64 = NX_FUTEX_WAKE_2 | NX_FUTEX_PRIVATE_2 74 return __syscall(NX_SYS_FUTEX_2, c as i64, op, 0x7FFFFFFF, 0, 0, 0) 75} 76 77// ---- self-test --------------------------------------------------- 78 79func main() -> i64 { 80 let c: *NxCond = nx_cond_new() 81 if c.seq != 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 82 83 nx_cond_signal(c) 84 if c.seq != 1 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 85 86 nx_cond_signal(c) 87 if c.seq != 2 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 88 89 nx_cond_broadcast(c) 90 if c.seq != 3 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 91 92 // We don't exercise nx_cond_wait in the self-test because it 93 // would block indefinitely without a paired signaller. Real 94 // tests live in the multi-threaded harness once SMP arrives. 95 96 return 0 97}