nx_segrace_gate.nx
buildroot/runtime/nx_segrace_gate.nx
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
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
structs
| none |
consts
| 34 | const G_W: i64 = 6 // concurrent writer processes |
| 35 | const G_R: i64 = 4 // commits each writer performs (deadline-bounded: the locked arm serialises) |
| 36 | const G_KEYCAP: i64 = 64 |
| 37 | const G_SEGCAP: i64 = 65536 |
| 38 | const G_KIND_LIVE: i64 = 1 |
| 39 | const G_FOLDS: i64 = 6 // compaction rounds raced against the writers in T4 |
functions
| 41 | func 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 } |
| 42 | func g_num(v: i64) -> i64 |
| 54 | func g_key(out: *u8, wid: i64, r: i64) -> i64 |
| 64 | func g_commit_unlocked(prefix: *u8, w: *i64, segid: i64) -> i64 |
| 97 | func g_reset(prefix: *u8) -> i64 |
| 108 | func g_worker(prefix: *u8, wid: i64, mode: i64) -> i64 |
| 122 | func g_survivors(prefix: *u8) -> i64 |
| 140 | func g_arm(prefix: *u8, mode: i64) -> i64 |
| 165 | func g_compactor(prefix: *u8) -> i64 |
| 177 | func g_arm_compact(prefix: *u8, live: *i64) -> i64 |
| 211 | func main(argc: i64, argv: *i64) -> i64 |