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}