code wiki / _hdl_build / nx_arbiter_race_gate.nx

nx_arbiter_race_gate.nx source

↩ module page · 104 lines · 5197 B

1// nx_arbiter_race_gate.nx -- MEASURED before/after proof that nx_arbiter turns F-class coordination into 2// S-class on a REAL shared-sink race. RG_N child PROCESSES (sys_fork -- our own kernel clone, no shell) each 3// do RG_K iterations of: read counter file -> increment -> write back. That read-modify-write is EXACTLY the 4// pattern that clobbered MEMORY.md + genesis_lineage.tsv mid-write today. A 0.5ms window between read and 5// write forces the scheduler to interleave the children. 6// UNLOCKED : writers race -> updates are lost -> final count << RG_N*RG_K. 7// ARBITRATED: fl_acquire serializes the critical section -> final count == RG_N*RG_K, exactly, every run. 8// Self-contained + sovereign: our fork/wait4 + our flock-based arbiter + our counter file. No 3rd party, no 9// shell-driven race. GREEN iff (a) the unlocked race PROVABLY loses updates AND (b) the arbiter eliminates 10// every one of them. This is the honest measured exceed -- a number, not a claim. license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_arbiter.nx" 13import "nx_runpath.nx" 14 15const RG_N: i64 = 4 16const RG_K: i64 = 100 17 18func g_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19func g_n(v: i64) -> i64 { var m: i64=v; if m<0{g_w("-");m=0-m} let t:*u8=sys_mmap(24); var k:i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i:i64=0; let o:*u8=sys_mmap(24); while i<k{o[i]=t[k-1-i];i=i+1}; sys_write(1,o,k); return 0 } 20func g_row(id: *u8, ok: i64, pass: *i64) -> i64 { g_w(" "); g_w(id); g_w(": "); if ok==1 { g_w("OK\n"); pass[0]=pass[0]+1 } else { g_w("FAIL\n") } return 0 } 21 22func rg_nap_us(us: i64) -> i64 { let ts: *i64 = sys_mmap(16) as *i64; ts[0]=0; ts[1]=us*1000; __syscall(35, ts as i64, 0, 0, 0, 0, 0); return 0 } 23 24// read the 8-byte i64 counter from the file (missing/short -> 0). 25func rg_read(path: *u8) -> i64 { 26 let rfd: i64 = sys_openat_rd(path) 27 if rfd < 0 { return 0 } 28 let buf: *u8 = sys_mmap(16) 29 let n: i64 = sys_read(rfd, buf, 8) 30 sys_close(rfd) 31 if n < 8 { return 0 } 32 let p: *i64 = buf as *i64 33 return p[0] 34} 35// write the 8-byte i64 counter (O_TRUNC -> file is exactly 8 bytes). 36func rg_write(path: *u8, v: i64) -> i64 { 37 let wfd: i64 = sys_openat_wr(path, 0x180) 38 let ob: *i64 = sys_mmap(16) as *i64; ob[0] = v 39 sys_write(wfd, ob as *u8, 8) 40 sys_close(wfd) 41 return 0 42} 43 44// child body: RG_K read-modify-writes with a race window between read and write. locked==1 wraps each 45// critical section in the arbiter. NEVER returns -- the child exits. 46func rg_child(path: *u8, lock_res: *u8, locked: i64) -> i64 { 47 var k: i64 = 0 48 while k < RG_K { 49 var lfd: i64 = 0 - 1 50 if locked == 1 { lfd = fl_acquire(lock_res, 100000, 1) } 51 let v: i64 = rg_read(path) 52 rg_nap_us(500) 53 rg_write(path, v + 1) 54 if locked == 1 { fl_release(lfd) } 55 k = k + 1 56 } 57 sys_exit(0) 58 return 0 59} 60 61// init counter to 0, fork RG_N children to hammer it, reap all, return the final count. 62func rg_phase(path: *u8, lock_res: *u8, locked: i64) -> i64 { 63 rg_write(path, 0) 64 var i: i64 = 0 65 while i < RG_N { 66 let pid: i64 = sys_fork() 67 if pid == 0 { rg_child(path, lock_res, locked) } 68 i = i + 1 69 } 70 let status: *i64 = sys_mmap(8) as *i64 71 i = 0 72 while i < RG_N { sys_wait4(0 - 1, status, 0); i = i + 1 } 73 return rg_read(path) 74} 75 76func main() -> i64 { 77 let pass: *i64 = sys_mmap(8) as *i64; pass[0] = 0 78 g_w("=== NX-ARBITER RACE GATE (measured lost-update before/after on a shared counter file) ===\n") 79 80 // per-workstream counter file + lock resource so concurrent sibling gate runs don't contend. 81 let wsid: *u8 = sys_mmap(64); rp_wsid(wsid); rp_ensure(wsid) 82 let path: *u8 = sys_mmap(512); rp_path(wsid, "arb_race_ctr" as *u8, path) 83 let lock_res: *u8 = sys_mmap(128); var o: i64=0; var i: i64=0 84 let pfx: *u8 = "arb_race_"; while pfx[i]!=(0 as u8){ lock_res[o]=pfx[i]; o=o+1; i=i+1 } 85 i=0; while wsid[i]!=(0 as u8){ lock_res[o]=wsid[i]; o=o+1; i=i+1 } lock_res[o]=0 as u8 86 87 let expected: i64 = RG_N * RG_K 88 let unlocked: i64 = rg_phase(path, lock_res, 0) 89 let locked: i64 = rg_phase(path, lock_res, 1) 90 91 g_w(" config: "); g_n(RG_N); g_w(" processes x "); g_n(RG_K); g_w(" increments expected="); g_n(expected); g_w("\n") 92 g_w(" UNLOCKED final = "); g_n(unlocked); g_w(" (lost updates = "); g_n(expected - unlocked); g_w(")\n") 93 g_w(" ARBITER final = "); g_n(locked); g_w(" (lost updates = "); g_n(expected - locked); g_w(")\n") 94 95 g_row("RACE IS REAL: unlocked lost updates (final < expected)" as *u8, (unlocked < expected) as i64, pass) 96 g_row("ARBITER FIXES IT: arbitrated final == expected (zero lost updates)" as *u8, (locked == expected) as i64, pass) 97 var no_phantom: i64 = 0 98 if unlocked <= expected { if locked <= expected { no_phantom = 1 } } 99 g_row("LIAR-KILL: neither phase phantom-incremented (final <= expected)" as *u8, no_phantom, pass) 100 101 g_w("ARBITER-RACE rows=3 pass="); g_n(pass[0]) 102 if pass[0] == 3 { g_w(" verdict=GREEN\n"); sys_exit(0); return 0 } 103 g_w(" verdict=RED\n"); sys_exit(1); return 1 104}