nx_hotlisten_gate.nx source
↩ module page · 69 lines · 3722 B
1// nx_hotlisten_gate.nx -- R5 proof: SO_REUSEPORT is what makes a ZERO-DOWNTIME handoff possible.
2// license_tier: ORIGINAL
3// THE CLASS UNDER TEST: today every /api/deploy and /api/restart drops the in-flight request (FETCH-FAIL,
4// ~10x in one session) because the daemon is KILLED while answering, and we wrote DOCTRINE around that
5// ("503 = EXPECTED, do NOT retry-hammer") -- a doctrine patch over a missing mechanism. A second process
6// cannot take the port until the first is gone, so there is always a gap. SO_REUSEPORT removes the gap.
7// T1 is the NON-VACUITY CONTROL and it is the whole point: it proves the SECOND bind genuinely FAILS on the
8// standard listener. Without T1, T2 succeeding would prove nothing -- it could just mean the port was free.
9// A cure with no proven disease is not a proof.
10import "nx_http_server.nx"
11import "nx_gate.nx"
12
13const HL_PORT_A: i64 = 19731 // control: standard listener, second bind must FAIL
14const HL_PORT_B: i64 = 19732 // cure: hot listener, second bind must SUCCEED
15
16func main() -> i64 {
17 gw("=== nx_hotlisten_gate: SO_REUSEPORT enables the zero-downtime handoff (R5) ===\n" as *u8)
18 var pass: i64 = 0
19 var tot: i64 = 0
20 let v: *i64 = sys_mmap(16) as *i64
21
22 // T1 CONTROL / NON-VACUITY: standard listener -- the SECOND bind on the same port must be REFUSED.
23 let a1: *u8 = sys_mmap(32)
24 nx_http_server_addr_loopback(a1, HL_PORT_A)
25 let f1: i64 = nx_http_server_listen(a1, 8, v)
26 let a2: *u8 = sys_mmap(32)
27 nx_http_server_addr_loopback(a2, HL_PORT_A)
28 let f2: i64 = nx_http_server_listen(a2, 8, v)
29 tot = tot + 1
30 var t1: i64 = 0
31 if f1 >= 0 { if f2 < 0 { t1 = 1 } }
32 if t1 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
33 gw("T1 CONTROL: standard listener REFUSES a second bind (this is the outage gap: first=" as *u8)
34 gn(f1); gw(" second=" as *u8); gn(f2); gw(")\n" as *u8)
35 if f1 >= 0 { sys_close(f1) }
36 if f2 >= 0 { sys_close(f2) }
37
38 // T2 CURE: hot listener -- BOTH processes can hold the port, which IS the handoff window.
39 let b1: *u8 = sys_mmap(32)
40 nx_http_server_addr_loopback(b1, HL_PORT_B)
41 let g1: i64 = nx_http_server_listen_hot(b1, 8, v)
42 let b2: *u8 = sys_mmap(32)
43 nx_http_server_addr_loopback(b2, HL_PORT_B)
44 let g2: i64 = nx_http_server_listen_hot(b2, 8, v)
45 tot = tot + 1
46 var t2: i64 = 0
47 if g1 >= 0 { if g2 >= 0 { t2 = 1 } }
48 if t2 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
49 gw("T2 CURE: hot listener admits a SECOND live binder = the drain window (old=" as *u8)
50 gn(g1); gw(" new=" as *u8); gn(g2); gw(")\n" as *u8)
51 if g1 >= 0 { sys_close(g1) }
52 if g2 >= 0 { sys_close(g2) }
53
54 // T3: the hot listener is still a WORKING listener, not merely a socket that binds. Same port, after both
55 // closed, must accept a fresh listen -- proving we did not leave the port poisoned for the next start.
56 let c1: *u8 = sys_mmap(32)
57 nx_http_server_addr_loopback(c1, HL_PORT_B)
58 let h1: i64 = nx_http_server_listen_hot(c1, 8, v)
59 tot = tot + 1
60 var t3: i64 = 0
61 if h1 >= 0 { t3 = 1 }
62 if t3 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
63 gw("T3 the port is not poisoned: a fresh hot listen after handoff succeeds (fd=" as *u8); gn(h1); gw(")\n" as *u8)
64 if h1 >= 0 { sys_close(h1) }
65
66 gw("\n=== nx_hotlisten_gate " as *u8); gn(pass); gw("/" as *u8); gn(tot); gw(" ===\n" as *u8)
67 if pass == tot { gw("HOTLISTEN GREEN -- a new process CAN take the port while the old still serves; the 503 gap is closable\n" as *u8); sys_exit(0); return 0 }
68 gw("HOTLISTEN RED\n" as *u8); sys_exit(1); return 1
69}