code wiki / (root) / nx_daemon_reap_gate.nx

nx_daemon_reap_gate.nx source

↩ module page · 149 lines · 8637 B

1// nx_daemon_reap_gate.nx -- referee for nx_daemon_reap_lib, the ONE child reaper every forking daemon 2// in the estate should compose. 3// 4// It gates the SAME functions a daemon would run, not a reimplementation, and it reaps REAL children it 5// forks itself -- a reaper proven only on synthetic inputs has never met wait4. 6// 7// THE TEETH THAT MATTER: 8// T1 is the empty-set case, and it is not decoration: a drain called with no children must RETURN, not 9// block. Getting WNOHANG wrong turns every idle accept loop in the estate into a hang. 10// T3 proves reaping actually happens against children this gate created, so a pass cannot come from a 11// function that quietly does nothing -- the exact vacuity that let sixteen daemons leak for weeks. 12// T6/T7 are the counter-underflow guard. A live-child counter that goes NEGATIVE turns a concurrency 13// cap into a permanent grant, and that failure direction is silent: the daemon keeps serving, keeps 14// forking, and nothing looks wrong until the box is the thing that reports it. 15// T5 neg-control: below the cap the throttle must NOT wait. A throttle that always blocks would pass 16// every "did it reap?" tooth while serialising the whole server. 17// T8 bite: the drain FIRES on an exited child and stays SILENT when there is none, in one cell. 18// license_tier: ORIGINAL expect_exit: 0 19import "nx_gate_verdict.nx" 20import "nx_daemon_reap_lib.nx" 21 22// Bound on the reap-observation spin. A forked child that calls sys_exit immediately is normally reaped 23// within a handful of iterations; this bound exists so a scheduling stall on a loaded box FAILS THE 24// FIXTURE TOOTH LOUDLY instead of hanging the gate forever. Each iteration is one wait4, so the worst 25// case is roughly a second of syscalls, not an unbounded wait. 26const DRG_SPIN_MAX: i64 = 1000000 27const DRG_KIDS: i64 = 3 28// How long the T9 child stays ALIVE before exiting on its own. Long enough that the parent's drain is 29// certainly called while the child is still running, short enough that a BLOCKING mutant merely returns 30// late instead of hanging the roster. 31const DRG_ALIVE_MS: i64 = 300 32 33func main(argc: i64, argv: *i64) -> i64 { 34 let ctr: *i64 = gv_ctr() 35 gv_head("nx_daemon_reap_gate -- the shared child reaper (nx_daemon_reap_lib)" as *u8) 36 37 let status: *i64 = sys_mmap(8) as *i64 38 39 // T1 -- EMPTY SET: with no children at all, the non-blocking drain returns 0 and RETURNS. 40 // If WNOHANG were passed wrong this call would block here and the gate would never print again, 41 // which is itself the signal -- but the tooth states the contract explicitly. 42 var t1: i64 = 0 43 if dr_reap_nonblocking(status) == 0 { t1 = 1 } 44 gv_check("T1-drain-with-no-children-returns-zero-and-does-not-block" as *u8, t1, ctr) 45 46 // T2 -- FIXTURE REACHED: fork DRG_KIDS children that exit at once. Assert every fork SUCCEEDED before 47 // asserting anything about reaping -- a fork that failed would make T3 pass for the wrong reason. 48 var forked: i64 = 0 49 var i: i64 = 0 50 while i < DRG_KIDS { 51 let pid: i64 = sys_fork() 52 if pid == 0 { sys_exit(0) } 53 if pid > 0 { forked = forked + 1 } 54 i = i + 1 55 } 56 var t2: i64 = 0 57 if forked == DRG_KIDS { t2 = 1 } 58 gv_check("T2-fixture-reached-all-three-children-forked" as *u8, t2, ctr) 59 gv_puts(" forked=" as *u8); gv_num(forked); gv_puts(" of " as *u8); gv_num(DRG_KIDS); gv_puts("\n" as *u8) 60 61 // T3 -- the blocking reap collects exactly one child per call, three times over. 62 var reaped: i64 = 0 63 var j: i64 = 0 64 while j < DRG_KIDS { reaped = reaped + dr_reap_one(status); j = j + 1 } 65 var t3: i64 = 0 66 if reaped == DRG_KIDS { t3 = 1 } 67 gv_check("T3-blocking-reap-collects-one-real-child-per-call" as *u8, t3, ctr) 68 gv_puts(" reaped=" as *u8); gv_num(reaped); gv_puts("\n" as *u8) 69 70 // T4 -- after the children are gone the drain is back to the empty-set answer. 71 var t4: i64 = 0 72 if dr_reap_nonblocking(status) == 0 { t4 = 1 } 73 gv_check("T4-drain-returns-to-zero-once-every-child-is-reaped" as *u8, t4, ctr) 74 75 // T5 -- NEG-CONTROL: below the cap the throttle must not wait and must not touch the counter. 76 // A throttle that always blocks would still satisfy every reaping tooth above while serialising a 77 // fork-per-connection server down to one request at a time. 78 let live: *i64 = sys_mmap(8) as *i64 79 live[0] = 2 80 var t5: i64 = 0 81 if dr_throttle(status, live, 8) == 0 { if live[0] == 2 { t5 = 1 } } 82 gv_check("neg-control-T5-throttle-below-cap-does-not-wait-and-leaves-counter-alone" as *u8, t5, ctr) 83 84 // T6 -- at the cap with NO children the throttle reaps nothing and the counter does not underflow. 85 live[0] = 0 86 var t6: i64 = 0 87 if dr_throttle(status, live, 0) == 0 { if live[0] == 0 { t6 = 1 } } 88 gv_check("T6-throttle-at-cap-with-no-children-leaves-counter-at-zero" as *u8, t6, ctr) 89 90 // T7 -- THE UNDERFLOW GUARD, against a real child. live starts at 0 while one child actually exists, 91 // so an unclamped drain would drive the counter to -1. Bounded spin; the fixture tooth reports 92 // honestly if the child was never observed rather than letting the outcome tooth pass vacuously. 93 let live2: *i64 = sys_mmap(8) as *i64 94 live2[0] = 0 95 var kid: i64 = sys_fork() 96 if kid == 0 { sys_exit(0) } 97 var got: i64 = 0 98 var spins: i64 = 0 99 while spins < DRG_SPIN_MAX { 100 got = got + dr_drain(status, live2) 101 if got >= 1 { spins = DRG_SPIN_MAX } else { spins = spins + 1 } 102 } 103 var t7a: i64 = 0 104 if kid > 0 { if got == 1 { t7a = 1 } } 105 gv_check("T7a-fixture-reached-the-real-child-was-observed-and-drained" as *u8, t7a, ctr) 106 var t7b: i64 = 0 107 if live2[0] == 0 { t7b = 1 } 108 gv_check("T7b-live-counter-clamps-at-zero-and-never-goes-negative" as *u8, t7b, ctr) 109 gv_puts(" got=" as *u8); gv_num(got); gv_puts(" live_after=" as *u8); gv_num(live2[0]); gv_puts("\n" as *u8) 110 111 // T8 -- BITE, in one cell: the drain must FIRE on an exited child and stay SILENT with none. 112 // Two teeth that each see only one condition cannot show the function discriminates between them. 113 var bad: i64 = 0 114 let kid2: i64 = sys_fork() 115 if kid2 == 0 { sys_exit(0) } 116 var s2: i64 = 0 117 while s2 < DRG_SPIN_MAX { 118 if dr_reap_nonblocking(status) > 0 { bad = 1; s2 = DRG_SPIN_MAX } else { s2 = s2 + 1 } 119 } 120 var good: i64 = 0 121 if dr_reap_nonblocking(status) > 0 { good = 1 } 122 gv_bite("T8-bite-drain-fires-on-an-exited-child-and-is-silent-with-none" as *u8, bad, good, ctr) 123 124 // T9 -- THE WNOHANG DISCRIMINATOR, the tooth this gate was missing and the reason a mutation run over 125 // it kept coming back INCONCLUSIVE. NO EXISTING TOOTH SEPARATES NON-BLOCKING FROM BLOCKING: T1 tests 126 // the EMPTY SET and passes either way, and T3 exercises the blocking path on purpose. So a mutant that 127 // turned WNOHANG into a blocking wait changed no verdict, and the gate could not be shown able to fail. 128 // This forks a child that is ALIVE AND NOT YET EXITED while the drain is called: a correct drain 129 // returns 0 at once, a blocking one waits for the child and comes back with 1. 130 // THE DEADLOCK HAZARD IS SOLVED RATHER THAN ACCEPTED. An earlier design had the child wait for the 131 // PARENT to release it, which meant a blocking mutant would HANG the roster instead of going RED -- a 132 // harness timeout is a far weaker signal than a kill, and shipping that would have traded a known-weak 133 // gate for one that can wedge the beat. The child now sleeps a BOUNDED time and exits on its own, so a 134 // blocking mutant merely returns late with the wrong answer and this tooth fails cleanly. 135 // sys_poll with no descriptors is the sleep: sys_nanosleep is ABSENT-PROVEN in this estate. 136 let kid3: i64 = sys_fork() 137 if kid3 == 0 { sys_poll(0 as *u8, 0, DRG_ALIVE_MS); sys_exit(0) } 138 var t9a: i64 = 0 139 if kid3 > 0 { t9a = 1 } 140 gv_check("T9a-fixture-reached-a-child-was-forked-and-is-still-running" as *u8, t9a, ctr) 141 var t9b: i64 = 0 142 if dr_reap_nonblocking(status) == 0 { t9b = 1 } 143 gv_check("T9b-drain-does-NOT-block-while-a-live-unexited-child-exists" as *u8, t9b, ctr) 144 var t9c: i64 = 0 145 if dr_reap_one(status) == 1 { t9c = 1 } 146 gv_check("T9c-and-that-same-child-is-still-collected-once-it-exits" as *u8, t9c, ctr) 147 148 return gv_verdict("DAEMON-REAP-GATE" as *u8, ctr, "the reaper is proven against real forked children, the drain distinguishes an exited child from none, the throttle waits only at the cap, and the live counter cannot underflow" as *u8) 149}