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}