code wiki / _hdl_build / nx_ws_cas_gate.nx

nx_ws_cas_gate.nx source

↩ module page · 202 lines · 7705 B

1// nx_ws_cas_gate.nx -- the REFEREE for WMS-R8 (concurrent-SSOT-safe registry write). 2// 3// Proves ws_put_locked defeats the concurrent-writer LOST-UPDATE race, AND proves the test can 4// DETECT lost updates (the mandatory negative control): 5// GOOD lane: NWORKERS fork, each ws_put_locked's NPER distinct keys into a FRESH /tmp store. 6// After join, ALL NWORKERS*NPER keys MUST be retrievable -> ZERO lost updates. 7// BAD lane (NEG-CONTROL): the SAME concurrency, SAME distinct keys, but the UNLOCKED ws_put_p -> 8// concurrent segid + manifest.txt RMW collisions LOSE updates -> retrievable < total. 9// If the bad lane loses NOTHING, the detector is worthless -> RED (no false green). 10// Each lane uses its OWN per-run unique prefix under /tmp (epoch-stamped) so every run starts on a 11// pristine store -- /tmp is disposable, so no clearing/unlink is needed (mirrors the R0 gate's 12// /tmp/fa_*.log discipline). Verification uses ss_open ONCE + ss_hget per key (zero per-call IO, 13// the same snapshot discipline the R4 audit was hardened to). The evidence line is one assembled 14// record written via fa_appendz -> it DOGFOODS the restored R0b lock-framed primitive (no torn 15// self-log -- closing the gap the critic flagged on earlier gates). Evidence -> 16// knowledge/status/ws_cas_gate.log. GREEN (exit 0) iff good==total AND bad<total; else RED (exit 1). 17// Sovereign: nx_ws_cas + nx_workstream_store + nx_seg_store + nx_framed_append + nx_syscalls. 18// license_tier: ORIGINAL 19import "nx_ws_cas.nx" 20import "nx_workstream_store.nx" 21import "nx_seg_store.nx" 22import "nx_framed_append.nx" 23import "nx_syscalls.nx" 24 25const CAS_NWORKERS: i64 = 12 26const CAS_NPER: i64 = 12 // 12*12 = 144 commits/lane (< the 256 manifest cap) 27const CAS_REC_CAP: i64 = 256 28const CAS_LOG: *u8 = "knowledge/status/ws_cas_gate.log" 29 30// stdout-only writers (the human report; the durable channel is the fa_appendz log line) 31func so(s: *u8) -> i64 { 32 var n: i64 = 0 33 while s[n] != (0 as u8) { n = n + 1 } 34 sys_write(1, s, n) 35 return 0 36} 37func son(v: i64) -> i64 { 38 let bb: *u8 = sys_mmap(28) 39 var m: i64 = v 40 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 41 let t: *u8 = sys_mmap(28) 42 var k: i64 = 0 43 if m == 0 { t[0] = 48 as u8; k = 1 } 44 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 45 var i: i64 = 0 46 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 47 sys_write(1, bb, k) 48 return 0 49} 50 51// append NUL-term s into dst at off -> new off 52func cg_cat(dst: *u8, off: i64, s: *u8) -> i64 { 53 var i: i64 = 0 54 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } 55 return off + i 56} 57// append decimal of v into dst at off -> new off 58func cg_catn(dst: *u8, off: i64, v: i64) -> i64 { 59 var m: i64 = v 60 var o: i64 = off 61 if m == 0 { dst[o] = 48 as u8; return o + 1 } 62 let t: *u8 = sys_mmap(28) 63 var k: i64 = 0 64 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 65 var i: i64 = 0 66 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 } 67 return o + k 68} 69 70// build the deterministic test key for (w,i) into out (NUL-term): "k:w<w>i<i>" 71func cg_key(out: *u8, w: i64, i: i64) -> i64 { 72 var o: i64 = 0 73 o = cg_cat(out, o, "k:w" as *u8) 74 o = cg_catn(out, o, w) 75 o = cg_cat(out, o, "i" as *u8) 76 o = cg_catn(out, o, i) 77 out[o] = 0 as u8 78 return o 79} 80 81// one worker: write CAS_NPER distinct keys to `prefix`. locked=1 -> ws_put_locked else ws_put_p. 82func cas_worker(prefix: *u8, w: i64, locked: i64) -> i64 { 83 let key: *u8 = sys_mmap(64) 84 let val: *u8 = sys_mmap(64) 85 var i: i64 = 0 86 while i < CAS_NPER { 87 cg_key(key, w, i) 88 var o: i64 = 0 89 o = cg_cat(val, o, "v" as *u8) 90 o = cg_catn(val, o, w) 91 o = cg_cat(val, o, "_" as *u8) 92 o = cg_catn(val, o, i) 93 val[o] = 0 as u8 94 if locked == 1 { ws_put_locked(prefix, key, val) } 95 else { ws_put_p(prefix, key, val) } 96 i = i + 1 97 } 98 return 0 99} 100 101// fork CAS_NWORKERS workers over `prefix`, wait all. locked selects the write path. 102func cas_spawn(prefix: *u8, locked: i64) -> i64 { 103 let pids: *i64 = sys_mmap(8 * (CAS_NWORKERS + 4)) as *i64 104 var w: i64 = 0 105 while w < CAS_NWORKERS { 106 let pid: i64 = sys_fork() 107 if pid == 0 { 108 cas_worker(prefix, w, locked) 109 sys_exit(0) 110 } 111 pids[w] = pid 112 w = w + 1 113 } 114 let st: *i64 = sys_mmap(16) as *i64 115 w = 0 116 while w < CAS_NWORKERS { 117 sys_wait4(pids[w], st, 0) 118 w = w + 1 119 } 120 return 0 121} 122 123// count how many of the CAS_NWORKERS*CAS_NPER distinct keys are retrievable from `prefix` 124// (ss_open ONCE + ss_hget per key). Returns the retrievable count. 125func cas_retrievable(prefix: *u8) -> i64 { 126 let h: *i64 = ss_open(prefix) 127 let pq: *i64 = sys_mmap(16) as *i64 128 let lq: *i64 = sys_mmap(16) as *i64 129 let key: *u8 = sys_mmap(64) 130 var got: i64 = 0 131 var w: i64 = 0 132 while w < CAS_NWORKERS { 133 var i: i64 = 0 134 while i < CAS_NPER { 135 cg_key(key, w, i) 136 if ss_hget(h, key, pq, lq) == 1 { got = got + 1 } 137 i = i + 1 138 } 139 w = w + 1 140 } 141 return got 142} 143 144// build a per-run unique prefix into out: "/tmp/" + tag + <epoch> + "-" 145func cas_prefix(out: *u8, tag: *u8, epoch: i64) -> i64 { 146 var o: i64 = 0 147 o = cg_cat(out, o, "/tmp/" as *u8) 148 o = cg_cat(out, o, tag) 149 o = cg_catn(out, o, epoch) 150 o = cg_cat(out, o, "-" as *u8) 151 out[o] = 0 as u8 152 return o 153} 154 155func main() -> i64 { 156 let epoch: i64 = sys_now_realtime_sec() 157 let total: i64 = CAS_NWORKERS * CAS_NPER 158 159 let pfx_g: *u8 = sys_mmap(128) 160 let pfx_b: *u8 = sys_mmap(128) 161 cas_prefix(pfx_g, "casg" as *u8, epoch) 162 cas_prefix(pfx_b, "casb" as *u8, epoch) 163 164 // GOOD lane: concurrent LOCKED writes 165 cas_spawn(pfx_g, 1) 166 let good_got: i64 = cas_retrievable(pfx_g) 167 168 // BAD lane (NEG-CONTROL): concurrent UNLOCKED writes 169 cas_spawn(pfx_b, 0) 170 let bad_got: i64 = cas_retrievable(pfx_b) 171 172 var green: i64 = 1 173 if good_got != total { green = 0 } // locked must lose NOTHING 174 if bad_got >= total { green = 0 } // unlocked MUST lose something (neg-control) 175 176 so("WMS-R8 concurrent-SSOT CAS gate (flock-serialized registry write)\n" as *u8) 177 so(" good(locked) retrievable=" as *u8); son(good_got) 178 so(" want=" as *u8); son(total) 179 if good_got == total { so(" PASS\n" as *u8) } else { so(" FAIL(lost-update)\n" as *u8) } 180 so(" bad(unlocked) retrievable=" as *u8); son(bad_got) 181 so(" want<" as *u8); son(total) 182 if bad_got < total { so(" PASS(neg-control-sees-loss)\n" as *u8) } else { so(" FAIL(no-loss-detected)\n" as *u8) } 183 so("verdict=" as *u8) 184 if green == 1 { so("GREEN\n" as *u8) } else { so("RED\n" as *u8) } 185 186 // ONE assembled evidence record -> fa_appendz (lock-framed atomic self-log; dogfoods R0b) 187 let rec: *u8 = sys_mmap(CAS_REC_CAP + 16) 188 var o: i64 = 0 189 o = cg_cat(rec, o, "WMS-R8-CAS epoch=" as *u8); o = cg_catn(rec, o, epoch) 190 o = cg_cat(rec, o, " workers=" as *u8); o = cg_catn(rec, o, CAS_NWORKERS) 191 o = cg_cat(rec, o, " per=" as *u8); o = cg_catn(rec, o, CAS_NPER) 192 o = cg_cat(rec, o, " good=" as *u8); o = cg_catn(rec, o, good_got) 193 o = cg_cat(rec, o, " bad=" as *u8); o = cg_catn(rec, o, bad_got) 194 o = cg_cat(rec, o, " lost_bad=" as *u8); o = cg_catn(rec, o, total - bad_got) 195 o = cg_cat(rec, o, " verdict=" as *u8) 196 if green == 1 { o = cg_cat(rec, o, "GREEN" as *u8) } else { o = cg_cat(rec, o, "RED" as *u8) } 197 rec[o] = 0 as u8 198 fa_appendz(CAS_LOG, rec, CAS_REC_CAP) 199 200 if green == 1 { return 0 } 201 return 1 202}