code wiki / (root) / nx_segrace_gate.nx

nx_segrace_gate.nx

buildroot/runtime/nx_segrace_gate.nx

13081 B287 linesdepth 3pulls 4 transitivereach 0 importersview sourcekind gate/proof
docsdependenciesstructsconstsfunctions

about

nx_segrace_gate.nx -- does ss_commit lose whole SEGMENTS under concurrency? nx_sts_lock_gate proved the sts_ family loses ROWS without <prefix>plock. This gate goes one layer DOWN, to the act every writer in the fleet performs: ss_commit. Both sts_seed and sts_append_fast end in ss_commit(prefix, w, ss_next_segid(prefix)), and so do 261 ss_ callers. If ss_commit is itself an unlocked read-modify-write, then NO lock above it can help, because the two writer families do not share one. TWO LOSSES ARE POSSIBLE AND THEY ARE DIFFERENT: (a) SEGID COLLISION -- ss_next_segid is an unlocked max+1 scan, so two writers pick the SAME id and ss_write_seg has one segment file overwrite the other. Bytes destroyed on disk. (b) MANIFEST LOST UPDATE -- ss_commit reads manifest.txt, appends one seg- line, writes the temp and renames. The rename is atomic, so the manifest is never TORN -- which is exactly what disguises the lost update. Last renamer wins and the other writer's seg- line is gone; its segment file still exists on disk but no reader can ever see it. THE CONTROL IS DELIBERATELY A HAND COPY, AND THAT IS A HAZARD I AM CLOSING ON PURPOSE. g_commit_unlocked below is the CURRENT ss_commit body copied VERBATIM minus the lock. A hand-copied differential probe is evidence ONLY if it is faithful -- a copy that silently fixes the bug exonerates the guilty. So the acceptance rule is two-phase: PRE-FIX : arm U and arm R must BOTH lose, and lose comparably. That is what proves the copy is faithful, because it is being raced against the very code it was copied from. POST-FIX : arm U still loses, arm R loses NOTHING. The lock is then the only variable. Running only the second phase would let an unfaithful copy pass as proof. NON-VACUITY: no artificial sleep is injected. The window is the real one -- ss_begin_cap + ss_add between the segid read and the commit, and the readall->rename span inside the commit. If real contention does not reproduce the loss the gate reports VACUOUS, never GREEN. license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0

dependencies 3 imports · 0 importers

nx_seg_store.nx nx_syscalls.nx nx_gate_verdict.nx nx_segrace_gate.nx

imports: nx_seg_store.nxnx_syscalls.nxnx_gate_verdict.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main g_p sys_write sys_mkdir g_arm g_reset sys_mmap nxa_die sys_write ↻ sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ ss_cat ss_writefile sys_openat_wr sys_write ↻ sys_close sys_fsync sys_fork g_worker sys_mmap ↻ g_key ss_cat ↻ ss_catn sys_mmap ↻ ss_begin_cap sys_mmap ↻ ss_add ss_add2 ss_w32 ss_len

structs

none

consts

34const G_W: i64 = 6 // concurrent writer processes
35const G_R: i64 = 4 // commits each writer performs (deadline-bounded: the locked arm serialises)
36const G_KEYCAP: i64 = 64
37const G_SEGCAP: i64 = 65536
38const G_KIND_LIVE: i64 = 1
39const G_FOLDS: i64 = 6 // compaction rounds raced against the writers in T4

functions

41func g_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
called by 2: g_nummain calls 1: sys_write
42func g_num(v: i64) -> i64
called by 1: main calls 3: g_psys_mmapsys_write
54func g_key(out: *u8, wid: i64, r: i64) -> i64
called by 2: g_workerg_survivors calls 2: ss_catss_catn
64func g_commit_unlocked(prefix: *u8, w: *i64, segid: i64) -> i64
97func g_reset(prefix: *u8) -> i64
108func g_worker(prefix: *u8, wid: i64, mode: i64) -> i64
122func g_survivors(prefix: *u8) -> i64
140func g_arm(prefix: *u8, mode: i64) -> i64
165func g_compactor(prefix: *u8) -> i64
177func g_arm_compact(prefix: *u8, live: *i64) -> i64
211func main(argc: i64, argv: *i64) -> i64