nx_ts_slot_router.nx source
↩ module page · 74 lines · 3251 B
1// nx_ts_slot_router.nx -- TS3: THE FRONT DOOR ITSELF (the thin daemon around nx_ts_slot_lib).
2//
3// Split from the decision core deliberately: the core is a LIBRARY so a gate can drive ts_slot_flip
4// directly and in-process, while this file is the runnable front door. One capability, one home --
5// a gate that had to fork a binary just to exercise a pure decision would be testing the plumbing.
6// See nx_ts_slot_lib.nx for the full rung rationale, the standalone-by-design fence, and the scope
7// of the warm-up probe.
8// license_tier: ORIGINAL No hw writes (Rule 26).
9import "nx_ts_slot_lib.nx"
10
11// Exit code for a usage error, named rather than returned as a bare integer.
12const TSR_RC_USAGE: i64 = 2
13func main(argc: i64, argv: **u8) -> i64 {
14 if argc < TSR_ARGC_MIN {
15 tsr_puts("usage: nx_ts_slot_router <front_port> <slotA_port> <slotB_port> <state_path> [handoff_sock]\n" as *u8)
16 return TSR_RC_USAGE
17 }
18 let front: i64 = tsr_atoi(argv[TSR_A_FRONT] as *u8)
19 let ports: *i64 = (sys_mmap(TSR_OUT_BYTES)) as *i64
20 ports[TSR_SLOT_A] = tsr_atoi(argv[TSR_A_SLOTA] as *u8)
21 ports[TSR_SLOT_B] = tsr_atoi(argv[TSR_A_SLOTB] as *u8)
22 let state: *u8 = argv[TSR_A_STATE] as *u8
23 var sockp: *u8 = "" as *u8
24 if argc > TSR_A_SOCK { sockp = argv[TSR_A_SOCK] as *u8 }
25
26 // TS2 FIRST: arm the drain before a listener exists to be interrupted.
27 let dv: *i64 = (sys_mmap(TSR_OUT_BYTES)) as *i64
28 dv[0] = 0
29 let dfd: i64 = ts_drain_on_term(dv)
30
31 // ENVOY ORDERING: the forwarding buffer is allocated BEFORE the socket is acquired.
32 let buf: *u8 = sys_mmap(TSR_BUF_CAP)
33
34 // TS1: the front door takes its own listener from the handoff, so the one component that must
35 // never restart is itself replaceable without a gap.
36 let addr: *u8 = sys_mmap(TSR_SA_BYTES)
37 tsr_sa(addr, front)
38 let lv: *i64 = (sys_mmap(TSR_OUT_BYTES)) as *i64
39 lv[0] = 0
40 let lfd: i64 = ts_handoff_nodrop(sockp, addr, TSR_BACKLOG, lv)
41 if lfd < 0 {
42 tsr_puts("NX-TS-SLOT-ROUTER listener FAILED verdict=" as *u8); tsr_putn(lv[0])
43 tsr_puts(" -- fail loud\n" as *u8)
44 return 1
45 }
46 tsr_puts("NX-TS-SLOT-ROUTER front=" as *u8); tsr_putn(front)
47 tsr_puts(" slotA=" as *u8); tsr_putn(ports[TSR_SLOT_A])
48 tsr_puts(" slotB=" as *u8); tsr_putn(ports[TSR_SLOT_B])
49 tsr_puts(" active=" as *u8); tsr_putn(ts_slot_active(state))
50 tsr_puts(" drain_fd=" as *u8); tsr_putn(dfd)
51 tsr_puts("\n" as *u8)
52
53 var go: i64 = 1
54 var served: i64 = 0
55 while go == 1 {
56 let w: i64 = ts_drain_wait(lfd, dfd, TSD_WAIT_BLOCK)
57 if w == TSD_W_DRAIN {
58 go = 0
59 } else {
60 if w == TSD_W_CONN {
61 let cfd: i64 = sys_accept(lfd)
62 if cfd >= 0 {
63 sys_set_socket_timeout(cfd, ACCEPT_TMO_S)
64 // RE-READ THE CHOICE PER CONNECTION: no cached routing state can disagree with the file.
65 let slot: i64 = ts_slot_active(state)
66 if tsr_forward(cfd, ports[slot], buf) == 1 { served = served + 1 }
67 sys_close(cfd)
68 }
69 } }
70 }
71 tsr_puts("NX-TS-SLOT-ROUTER drained on TERM served=" as *u8); tsr_putn(served)
72 tsr_puts("\n" as *u8)
73 return 0
74}