nx_ts_slot_lib.nx
buildroot/runtime/nx_ts_slot_lib.nx
about
nx_ts_slot_lib.nx -- TS3: THE SLOT-FLIP DECISION CORE (importable, no main).
/compare/trafficsafety rung TS3. Two backend SLOTS on two ports behind one small front door that
owns the upstream choice and is independent of both. Accept rule taken VERBATIM from
trafficsafety.plan: "a flip changes which slot answers with zero failed requests across the flip,
a slot that fails its warm-up probe is NEVER flipped to, and the front door's own uptime is
unbroken across the whole exercise."
IT DOES NOT TOUCH sites.elf OR nx_proxy_route, AND THAT IS A DESIGN DECISION, NOT AN OMISSION.
The live front door is what all 69 route rows cross; editing it to gain a deploy-safety property is
the one change whose blast radius is every request in the estate. So this ships as a STANDALONE
router that fronts one non-critical backend. The pattern is identical and provable; adopting it for
the estate's real front door is an operator decision that should be taken against this evidence,
not smuggled in beneath it.
THE FRONT DOOR IS THE ONLY THING THAT MUST NEVER RESTART, so it is also the only thing that must be
replaceable without a gap -- and that is precisely why TS3 depends on TS1. This router takes its
own listener from ts_handoff_nodrop, so the component whose restart would be an outage is itself
hot-replaceable. A front door that cannot be replaced hitlessly is refused rather than deployed;
this one closes that dependency in its own first line of work rather than deferring it.
THE UPSTREAM CHOICE IS DATA, NOT A RECOMPILED BRANCH. The active slot lives in a conf file read
through rm_conf -- the estate's line-anchored reader, not a second one -- and it is re-read PER
CONNECTION. So a flip takes effect on the very next request with no signal, no restart and no
reload race, and the router holds no cached routing state that could disagree with the file.
A FLIP IS GUARDED BY A WARM-UP PROBE AND THE GUARD FAILS TOWARD DOING NOTHING. ts_slot_flip probes
the CANDIDATE before it writes, and on a cold candidate it leaves the active slot exactly where it
was and returns a NAMED refusal. A heuristic that gates a destructive action must be wrong in the
direction of doing nothing, and flipping traffic onto a dead slot is the destructive action here.
SCOPE OF THE PROBE, STATED RATHER THAN IMPLIED: it establishes that the candidate ACCEPTS A
CONNECTION AND ANSWERS BYTES. That is liveness, not health. The estate already owns the deeper
instrument -- nx_http_probe_lib's hp_run, with thirteen distinct exit codes so a compound health
assertion cannot fail without naming WHICH half failed -- and the honest upgrade path is to compose
that, never to re-implement a second probe here.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 4 imports · 2 importers
imports: nx_syscalls.nxnx_http_server.nxnx_ts_drain_lib.nxnx_resmon_lib.nx
imported by: nx_ts_slot_router.nxnx_ts_slot_router_gate.nx
structs
| none |
consts
| 43 | const TSR_SA_BYTES: i64 = 16 |
| 46 | const TSR_SA_PORT_OFF: i64 = 2 |
| 47 | const TSR_SA_ADDR_OFF: i64 = 4 |
| 48 | const TSR_ADDR_BYTES: i64 = 4 |
| 49 | const TSR_BYTE_RADIX: i64 = 256 |
| 50 | const TSR_LOOPBACK_A: i64 = 127 |
| 51 | const TSR_LOOPBACK_D: i64 = 1 |
| 52 | const TSR_BACKLOG: i64 = 16 |
| 53 | const TSR_OUT_BYTES: i64 = 64 |
| 54 | const TSR_NUM_SCRATCH: i64 = 24 |
| 55 | const TSR_ASCII_0: i64 = 48 |
| 56 | const TSR_ASCII_9: i64 = 57 |
| 57 | const TSR_B10: i64 = 10 |
| 58 | const TSR_BUF_CAP: i64 = 65536 |
| 59 | const TSR_PROBE_BYTE: i64 = 80 |
| 63 | const TSR_SLOT_A: i64 = 0 |
| 64 | const TSR_SLOT_B: i64 = 1 |
| 65 | const TSR_SLOT_N: i64 = 2 |
| 69 | const TSR_FLIP_OK: i64 = 0 |
| 70 | const TSR_FLIP_NOOP: i64 = 1 |
| 71 | const TSR_FLIP_COLD: i64 = 2 |
| 72 | const TSR_FLIP_BAD_SLOT: i64 = 3 |
| 73 | const TSR_FLIP_WRITE_ERR: i64 = 4 |
| 75 | const TSR_KEY_ACTIVE: *u8 = "active_slot" as *u8 |
| 76 | const TSR_ARGC_MIN: i64 = 5 |
| 77 | const TSR_A_FRONT: i64 = 1 |
| 78 | const TSR_A_SLOTA: i64 = 2 |
| 79 | const TSR_A_SLOTB: i64 = 3 |
| 80 | const TSR_A_STATE: i64 = 4 |
| 81 | const TSR_A_SOCK: i64 = 5 |
functions
| 83 | func tsr_atoi(s: *u8) -> i64 called by 1: main |
| 93 | func tsr_itoa(v: i64, out: *u8) -> i64 |
| 105 | func tsr_sa(sa: *u8, port: i64) -> i64 |
| 117 | func tsr_dial(port: i64) -> i64 |
| 130 | func ts_slot_active(state_path: *u8) -> i64 |
| 145 | func ts_slot_probe(port: i64) -> i64 |
| 160 | func ts_slot_flip(state_path: *u8, ports: *i64, want: i64) -> i64 |
| 187 | func tsr_forward(cfd: i64, upstream_port: i64, buf: *u8) -> i64 |
| 205 | func tsr_puts(s: *u8) -> i64 |
| 210 | func tsr_putn(v: i64) -> i64 |