code wiki / (root) / nx_lockbound_gate.nx

nx_lockbound_gate.nx source

↩ module page · 142 lines · 7953 B

1// nx_lockbound_gate.nx -- does ss_plane_lock REFUSE a wedged plane instead of hanging on it? 2// 3// WHY. The 2026-08-06 commit lock closed a real defect (nx_segrace_gate: 24/24 kept vs 4/24 under a 4// verbatim unlocked control) but it did so with a BLOCKING flock, and that changed the failure mode 5// rather than only removing the defect. MEASURED the same day: nx_segrace_gate went RED with an 6// UNCHANGED binary because one worker sat in kernel state D wchan=wait_for_commit -- a filesystem 7// journal commit -- HOLDING the lock, while its siblings sat in wchan=locks_lock_inode_wait. The lock 8// behaved correctly; the WAIT was unbounded. Trading silent data loss for an unbounded hang is not 9// obviously a win, and it must not be discovered by whoever is on call. Debt 1786070596. 10// 11// ★A GUARD THAT CANNOT BE SHOWN TO FIRE HAS NOT BEEN TESTED, and a TIMEOUT is the easiest guard in 12// the world to write and never exercise: on a healthy host the bound is simply never reached, so the 13// code path ships untested and only runs for the first time during the incident it exists for. This 14// gate manufactures the incident: a child DELIBERATELY HOLDS the plane lock for longer than the 15// ceiling while the parent tries to commit through it. 16// 17// BOTH WAYS, because a lock that always refuses would also pass a refusal-only test: 18// T1 CONTROL -- with NO holder, a commit must SUCCEED (the lock still works normally) 19// T2 BOUNDED -- with a holder, the commit must be REFUSED, and refused STRICTLY BEFORE the holder 20// lets go. Elapsed < hold time is the whole point: it proves the parent gave up on 21// its own bound rather than simply outlasting the holder, which is the failure a 22// naive "did it eventually return?" test cannot tell apart. 23// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 24import "nx_seg_store.nx" 25import "nx_syscalls.nx" 26import "nx_gate_verdict.nx" 27 28// >>THIS MUST EXCEED ss_plane_lock''s CEILING (SS_LOCK_TRIES * SS_LOCK_SLEEP_MS in nx_seg_store.nx).<< 29// COUPLING BROKEN ONCE, 2026-08-07: the ceiling was raised 30s -> 120s and this stayed at 40000, so the 30// parent OUTLASTED the holder, acquired the lock legitimately at 47.6s and returned rc=0 -- T2 failed 31// while reporting HANG-OR-PASSTHROUGH. The gate was right and the CONSTANT was stale. 32// >>A TEST WHOSE THRESHOLD IS DERIVED FROM A VALUE IT DOES NOT READ WILL SILENTLY STOP TESTING WHEN 33// THAT VALUE MOVES.<< 150s leaves 30s of headroom above the 120s ceiling; if the ceiling changes again, 34// change this WITH it. 35// >>THE HOLD IS DERIVED FROM THE LIBRARY, NOT COPIED FROM IT.<< It used to be a literal that had to 36// exceed ss_plane_lock's ceiling, and on 2026-08-07 the ceiling moved 30s -> 120s while this stayed at 37// 40000: the parent then legitimately OUTLASTED the holder, acquired the lock, and T2 failed while 38// reporting HANG-OR-PASSTHROUGH. The gate was right; the constant was stale. 39// >>A TEST WHOSE THRESHOLD IS DERIVED FROM A VALUE IT DOES NOT READ WILL SILENTLY STOP TESTING WHEN 40// THAT VALUE MOVES.<< So read it: SS_LOCK_TRIES and SS_LOCK_SLEEP_MS come from nx_seg_store.nx, which 41// this gate already imports. Now the ceiling can change freely and this follows it BY CONSTRUCTION -- 42// there is no second number to forget. 43const LB_HEADROOM_MS: i64 = 30000 44const LB_SETTLE_MS: i64 = 700 // let the child actually acquire before the parent attempts 45const LB_CAP: i64 = 4096 46const LB_KIND_LIVE: i64 = 1 47 48func lb_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 49func lb_n(v: i64) -> i64 { 50 if v == 0 { lb_p("0" as *u8); return 0 } 51 var x: i64 = v 52 if x < 0 { lb_p("-" as *u8); x = 0 - x } 53 let b: *u8 = sys_mmap(32) 54 var i: i64 = 0 55 while x > 0 { b[i] = ((x % 10) + 48) as u8; x = x / 10; i = i + 1 } 56 while i > 0 { i = i - 1; sys_write(1, ((b as i64) + i) as *u8, 1) } 57 return 0 58} 59 60func lb_commit_one(prefix: *u8, key: *u8) -> i64 { 61 let w: *i64 = ss_begin_cap(LB_CAP) 62 if ss_add(w, LB_KIND_LIVE, key, "v" as *u8, 1) < 0 { return 0 - 99 } 63 return ss_commit(prefix, w, ss_next_segid(prefix)) 64} 65 66// truncate the live manifest so the plane starts empty; segment files are left on disk (rule 13) 67func lb_reset(prefix: *u8) -> i64 { 68 let mf: *u8 = sys_mmap(512) 69 var o: i64 = ss_cat(mf, 0, prefix) 70 o = ss_cat(mf, o, "manifest.txt" as *u8) 71 mf[o] = 0 as u8 72 let e: *u8 = sys_mmap(16) 73 ss_writefile(mf, e, 0) 74 return 0 75} 76 77func main(argc: i64, argv: *i64) -> i64 { 78 let ctr: *i64 = gv_ctr() 79 lb_p("nx_lockbound_gate -- does ss_plane_lock REFUSE a wedged plane instead of hanging?\n\n" as *u8) 80 let prefix: *u8 = "knowledge/store/lockboundgate-" as *u8 81 lb_reset(prefix) 82 83 // ---- T1 CONTROL: no holder, the lock must still let a normal commit through ---- 84 let rc1: i64 = lb_commit_one(prefix, "k1" as *u8) 85 var t1: i64 = 0 86 if rc1 == 0 { t1 = 1 } 87 lb_p(" T1 CONTROL no holder -> ss_commit rc=" as *u8); lb_n(rc1) 88 lb_p(" (want 0)\n" as *u8) 89 gv_check("T1 CONTROL: with no holder a commit SUCCEEDS (the lock still works normally)" as *u8, t1, ctr) 90 91 // ---- T2 BOUNDED: a child holds the lock longer than the ceiling ---- 92 // derived here, BEFORE the fork, so parent and child agree on it by inheritance 93 let ceiling: i64 = SS_LOCK_TRIES * SS_LOCK_SLEEP_MS 94 // >>THE REAL FLOOR IS ONE SLEEP SHORT OF THE NOMINAL CEILING.<< SS_LOCK_TRIES counts failed 95 // ATTEMPTS and the backoff sleeps BETWEEN them, so the last attempt is not followed by a sleep: 96 // elapsed lands at (TRIES-1)*SLEEP plus per-attempt overhead. MEASURED both ways on 2026-08-07 by 97 // moving the library ceiling: at 120000ms it refused at 120124 (overhead > the missing 50ms, so 98 // the off-by-one was INVISIBLE) and at 10000ms it refused at 9973 (overhead smaller, so it showed). 99 // >>A TOLERANCE THAT IS LARGE RELATIVE TO AN OFF-BY-ONE HIDES IT; SHRINK THE SCALE TO SEE IT.<< 100 let floor_ms: i64 = ceiling - SS_LOCK_SLEEP_MS 101 let hold: i64 = ceiling + LB_HEADROOM_MS 102 let pid: i64 = sys_fork() 103 if pid == 0 { 104 let hfd: i64 = ss_plane_lock(prefix) 105 sys_sleep_ms(hold) 106 ss_plane_unlock(hfd) 107 sys_exit_group(0) 108 } 109 sys_sleep_ms(LB_SETTLE_MS) 110 let t_start: i64 = sys_now_ms() 111 let rc2: i64 = lb_commit_one(prefix, "k2" as *u8) 112 let elapsed: i64 = sys_now_ms() - t_start 113 114 lb_p(" T2 BOUNDED ceiling=" as *u8); lb_n(ceiling); lb_p("ms holder held " as *u8); lb_n(hold) 115 lb_p("ms -> ss_commit rc=" as *u8); lb_n(rc2) 116 lb_p(" after " as *u8); lb_n(elapsed); lb_p("ms\n" as *u8) 117 118 var t2: i64 = 0 119 if rc2 != 0 { 120 // Refused BEFORE the holder released: proves the parent gave up on its OWN bound rather than 121 // merely outlasting the holder. Without this second clause a blocking flock would also pass. 122 // THREE clauses, not one: refused (rc!=0), refused AT the ceiling (not -- a lock that always 123 // refuses would pass a weaker test), and refused BEFORE the holder let go (not merely 124 // outlasting it, which is the old blocking behaviour wearing a refusal). 125 if elapsed >= floor_ms { if elapsed < hold { t2 = 1 } } 126 } 127 if rc2 == 0 { 128 lb_p(" HANG-OR-PASSTHROUGH: the commit SUCCEEDED while another process held the lock.\n" as *u8) 129 } 130 if rc2 != 0 { if elapsed >= hold { 131 lb_p(" VACUOUS: it returned only after the holder let go -- that is the OLD blocking\n" as *u8) 132 lb_p(" behaviour wearing a refusal, not a bounded wait.\n" as *u8) 133 } } 134 gv_check("T2 BOUNDED: a held plane is REFUSED, strictly before the holder releases" as *u8, t2, ctr) 135 136 let st: *i64 = sys_mmap(16) as *i64 137 sys_wait4(0 - 1, st, 0) 138 139 let rc: i64 = gv_verdict("LOCKBOUND-GATE" as *u8, ctr, "ss_plane_lock refuses a wedged plane on a bounded wait" as *u8) 140 sys_exit(rc) 141 return rc 142}