nx_hangprobe_gate.nx source
↩ module page · 28 lines · 1646 B
1// nx_hangprobe_gate.nx -- a gate that DELIBERATELY HANGS, to prove /api/gate_run's deadline actually fires
2// AND reaps. license_tier: ORIGINAL
3// WHY THIS EXISTS: seq1425 said the deadline freed the CALLER but not the CHILDREN, turning a hang into a
4// leak. You cannot prove that fixed with a gate that exits on its own -- you need one that genuinely hangs.
5// SHAPE = the exact seq1383 incident: fork a GRANDCHILD that sleeps holding the inherited stdout write end,
6// then the gate itself sleeps past any sane deadline. That grandchild is the hard case: killing only the
7// direct child leaves the pipe open, so the parent's blocking read never sees EOF (tr_run_capture_to's own
8// header calls this out, which is why it closes wfd BEFORE forking its watchdog).
9// EXPECTED via POST /api/gate_run target=nx_hangprobe_gate&deadline_ms=3000:
10// verdict TIMEOUT, response returns in ~3s, and ZERO nx_hangprobe processes survive afterwards.
11// This gate NEVER passes -- it is a fixture, not a check. It is named *_gate only so gate_run will run it.
12import "nx_syscalls.nx"
13import "nx_gate.nx"
14
15func main() -> i64 {
16 gw("=== nx_hangprobe_gate: deliberately hanging fixture (expect the CALLER's deadline to kill me) ===\n" as *u8)
17 let pid: i64 = sys_fork()
18 if pid == 0 {
19 // GRANDCHILD: hold the inherited stdout write end open and sleep well past any deadline.
20 sys_sleep_ms(600000)
21 sys_exit(0)
22 }
23 gw(" spawned a grandchild holding stdout; now sleeping past the deadline\n" as *u8)
24 sys_sleep_ms(600000)
25 gw(" UNREACHABLE unless the deadline never fired\n" as *u8)
26 sys_exit(0)
27 return 0
28}