nx_arena_smp_gate.nx
buildroot/runtime/nx_arena_smp_gate.nx
about
nx_arena_smp_gate.nx -- TWO THREADS MUST NEVER RECEIVE THE SAME POINTER.
THE DEFECT, MEASURED 2026-08-25 while shipping structured concurrency. The arena bump allocator
inside nx_syscalls advanced its cursor with a plain read-modify-write:
let p: i64 = nxa_st[0]
nxa_st[0] = p + need
Two threads that read the cursor before either wrote it BOTH RECEIVE THE SAME POINTER and then
write over one another. Eight pool workers calling a helper that allocates a 16-byte timespec hit
exactly this and produced ARENA-OVERRUN prev_alloc_size=16 followed by SIGSEGV. It generalises to
EVERY small allocation from more than one thread -- which is why the scoped-spawn child body was
deliberately written to allocate nothing, load-bearing rather than incidental.
WHY A BARRIER AND NOT A LOST-UPDATE COUNT. The obvious non-vacuity proof is to run an UNLOCKED
counter beside the allocator and show it loses updates. That proof is PROBABILISTIC: on a run where
the threads happen not to interleave it shows no loss, and the gate would go RED for no reason.
A DETECTOR WITH FALSE POSITIVES IS WORSE THAN NONE. So overlap is established DETERMINISTICALLY:
every task blocks on an atomic barrier until ALL of them have arrived, and only then allocates.
If the barrier is reached, the tasks provably held the allocator at the same time; if it is not,
this gate reports the pointer-distinctness result as UNPROVEN rather than GREEN, because an
AXIS THAT CANNOT SEE MUST ABSTAIN, NOT ACQUIT.
The lost-update count is still MEASURED and PRINTED beside it, as evidence about the machine
rather than as a tooth.
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_gate_verdict.nxnx_thread_pool.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 30 | const AG_PER: i64 = 256 |
| 33 | const AG_MAXTASKS: i64 = 64 |
| 38 | const AG_BARRIER_SPINS: i64 = NX_MAGIC_2000000000 |
functions
| 45 | func ag_puts(s: *u8) -> i64 |
| 52 | func ag_putn(v: i64) -> i64 |
| 70 | func ag_task(id: i64) -> i64 calls 1: sys_mmap |
| 106 | func main(argc: i64, argv: *i64) -> i64 |