code wiki / _hdl_build / nx_ts_handoff_gate.nx
nx_ts_handoff_gate.nx
buildroot/runtime/_hdl_build/nx_ts_handoff_gate.nx
about
nx_ts_handoff_gate.nx -- TS1: KEEP THE SOCKET, REPLACE THE PROCESS.
/compare/trafficsafety rung TS1, accept rule taken VERBATIM from trafficsafety.plan and not
re-invented here: "a swap under a synthetic request stream drops ZERO connections, measured by a
client that counts refusals and resets, not by the daemon's own log. The neg-control is mandatory
-- the same harness against the CURRENT ordinary-listener path must show a non-zero drop, or the
test proves nothing."
THE MEASUREMENT IS DROPPED CONNECTIONS, NOT BINDERS. nx_hotlisten_gate proves two processes can
co-bind a port, which is the NECESSARY condition and never the sufficient one -- LWN documents
SO_REUSEPORT as itself dropping connections during the three-way handshake when the listening set
changes. So nothing here counts binders. A client process drives real TCP connections, and every
connect that is refused and every exchange that does not complete is counted as a DROP.
THE DECISIVE TOOTH IS THE ARRIVAL DURING THE GAP. Both arms run the same script: generation one
serves a stream, generation one EXITS AND IS REAPED so that zero servers exist, a client connects
at that exact instant, and only then is generation two started. With an owner outside the service
holding the listener the arrival completes its handshake into the kernel's accept queue and is
served microseconds later; with the current bind-per-process path there is nothing bound and the
arrival is refused. That difference is deterministic, which is why it can be a gate.
SCOPE DECLARED UP FRONT RATHER THAN IMPLIED. This proves the STRUCTURAL property -- an arrival
during the swap is not refused. It is NOT a failure-RATE measurement: HAProxy's published figure
for the un-fixed race is 155 failures per million connections, and no run of this size could see
a rate like that. Measuring that would need a load generator and is a different instrument; the
ceiling is stated here rather than left for a reader to assume it was covered.
SAFETY: loopback only, and every port is FOUND by a bind that would have failed if the port were
occupied -- a probe port you did not verify free is not a control, it is a second instance. No
serving daemon is touched.
expect_exit: 0 license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 4 imports · 0 importers
imports: nx_syscalls.nxnx_http_server.nxnx_gate_emit_lib.nxnx_gate_verdict.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 37 | const TH_NUM_SCRATCH: i64 = 24 |
| 38 | const TH_ASCII_ZERO: i64 = 48 |
| 39 | const TH_B10: i64 = 10 |
| 40 | const TH_ONE_BYTE: i64 = 1 |
| 41 | const TH_SA_IN_BYTES: i64 = 16 |
| 42 | const TH_BYTE_RADIX: i64 = 256 |
| 43 | const TH_OUT_SLOTS: i64 = 8 |
| 44 | const TH_OUT_BYTES: i64 = 64 |
| 45 | const TH_LOOPBACK_A: i64 = 127 |
| 46 | const TH_LOOPBACK_D: i64 = 1 |
| 47 | const TH_PORT_BASE: i64 = 39100 |
| 48 | const TH_PORT_TRIES: i64 = 64 |
| 49 | const TH_PORT_NONE: i64 = 0 - 1 |
| 50 | const TH_PING_BYTE: i64 = 80 |
| 51 | const TH_ECHO_BYTE: i64 = 90 |
| 52 | const TH_READY_BYTE: i64 = 82 |
| 53 | const TH_BOOL_TRUE: i64 = 1 |
| 54 | const TH_BOOL_FALSE: i64 = 0 |
| 59 | const TH_REQS_PER_GEN: i64 = TS_RV_BACKLOG |
| 62 | const TH_DEADLINE_S: i64 = ACCEPT_TMO_S |
| 63 | const TH_CHILD_ALARM_S: i64 = ACCEPT_TMO_S * 2 |
| 65 | const TH_O_ATTEMPTED: i64 = 0 |
| 66 | const TH_O_SERVED: i64 = 1 |
| 67 | const TH_O_GAP_RC: i64 = 2 |
| 68 | const TH_O_GAP_DONE: i64 = 3 |
| 69 | const TH_O_PORT: i64 = 4 |
| 70 | const TH_O_C1: i64 = 5 |
| 71 | const TH_O_C2: i64 = 6 |
| 72 | const TH_GAP_UNRUN: i64 = 0 - 999 |
| 74 | const TH_C_OK: i64 = 0 |
| 75 | const TH_C_ACQUIRE: i64 = 2 |
| 76 | const TH_C_BIND: i64 = 3 |
| 77 | const TH_C_READY: i64 = 4 |
| 78 | const TH_C_ACCEPT: i64 = 5 |
| 79 | const TH_C_BADREQ: i64 = 6 |
| 80 | const TH_C_WRITE: i64 = 7 |
| 81 | const TH_SOCK_ARMED: *u8 = "/tmp/nx_ts_handoff_gate/armed.sock" as *u8 |
| 82 | const TH_SOCK_NEG: *u8 = "/tmp/nx_ts_handoff_gate/neg.sock" as *u8 |
| 83 | const TH_DIR: *u8 = "/tmp/nx_ts_handoff_gate" as *u8 |
functions
| 85 | func g_putn(v: i64) -> i64 |
| 96 | func gq(label: *u8, got: i64, want: i64, ctr: *i64) -> i64 |
| 108 | func th_sa(sa: *u8, port: i64) -> i64 |
| 122 | func th_put1(fd: i64, v: i64) -> i64 |
| 127 | func th_get1(fd: i64) -> i64 |
| 137 | func th_listen(port: i64, reuse: i64) -> i64 called by 3: th_find_portth_generationts_swap_run calls 7: sys_socketsys_mmapsys_setsockoptth_sasys_bindsys_close+1 |
| 157 | func th_find_port(from: i64) -> i64 |
| 169 | func th_exchange(fd: i64) -> i64 |
| 176 | func th_drive_one(port: i64) -> i64 called by 1: ts_swap_run calls 7: sys_mmapth_sasys_socketsys_set_socket_timeoutsys_connectsys_close+1 |
| 190 | func th_generation(armed: i64, port: i64, sock_path: *u8, reqs: i64) -> i64 called by 1: ts_swap_run calls 10: sys_alarmsys_mmapts_handoff_acquireth_listensys_unix_connect_fdth_put1+4 |
| 227 | func th_serve_rv(rv: i64, listen_fd: i64, armed: i64) -> i64 called by 1: ts_swap_run calls 6: sys_mmapts_handoff_publishsys_acceptsys_set_socket_timeoutth_get1sys_close |
| 246 | func ts_swap_run(armed: i64, port: i64, sock_path: *u8, out: *i64) -> i64 called by 1: main calls 17: sys_mmapts_handoff_opensys_set_socket_timeoutth_listensys_closesys_unlinkat+11 |
| 331 | func th_report(tag: *u8, drops: i64, out: *i64) -> i64 |
| 344 | func main() -> i64 |