code wiki / _hdl_build / nx_tool_run_timeout_gate.nx

nx_tool_run_timeout_gate.nx source

↩ module page · 110 lines · 5155 B

1// nx_tool_run_timeout_gate.nx -- proves tr_run_capture_to actually BOUNDS a hanging child (seq1412). 2// 3// The claim under test is not "the function returns a timeout constant" -- that is trivial to fake. It is 4// "a child that would run for 10 seconds is KILLED at the deadline, its output is still captured, it is 5// REAPED rather than orphaned, and a fast child is NOT falsely timed out". So every tooth measures 6// something an empty implementation would fail: 7// 8// T1 the deadline FIRES -- 10s sleeper, 500ms budget -> TR_ERR_TIMEOUT 9// T2 the deadline is REAL TIME -- that call returns in well under the sleeper's 10s (the load-bearing 10// tooth: returning -5 after waiting the full 10s would be a lie) 11// T3 NEG-CONTROL fast child -- a 50ms sleeper with a 5s budget exits 0, NOT timed out 12// T4 output survives -- the fast child's stdout is captured intact 13// T5 REPEATABLE -- a second timeout run behaves identically (no fd/pid/zombie leak 14// wedging the caller after the first kill) 15// T6 opt-out is explicit -- timeout_ms<=0 delegates to the unbounded path and still succeeds 16// 17// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 18import "nx_syscalls.nx" 19import "nx_tool_run.nx" 20 21const TG_STDOUT: i64 = 1 22const TG_SLEEPER: *u8 = "_build/nx_tr_sleeper.sov.elf" 23const TG_OUTCAP: i64 = 65536 24 25// A 10s sleeper against a 500ms budget. If the kill did not happen the call cannot return before 10s. 26const TG_HANG_MS: i64 = 10000 27const TG_BUDGET_MS: i64 = 500 28// Generous ceiling: anything under this proves we did not sit out the full sleep. Deliberately far from 29// both 0.5s and 10s so neither scheduler jitter nor a slow NAS can flip the verdict either way. 30const TG_MAX_ELAPSED_S: i64 = 5 31const TG_FAST_MS: i64 = 50 32const TG_FAST_BUDGET_MS: i64 = 5000 33 34func tg_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(TG_STDOUT, s, n); return 0 } 35func tg_num(v: i64) -> i64 { 36 var m: i64 = v 37 if m < 0 { m = 0 - m; sys_write(TG_STDOUT, "-" as *u8, 1) } 38 let t: *u8 = sys_mmap(28) 39 var k: i64 = 0 40 if m == 0 { t[0] = 48 as u8; k = 1 } 41 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 42 let o: *u8 = sys_mmap(28) 43 var i: i64 = 0 44 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 45 sys_write(TG_STDOUT, o, k) 46 return 0 47} 48func tg_check(ok: i64, label: *u8, got: i64, want: i64) -> i64 { 49 if ok == 1 { tg_puts(" PASS " as *u8) } else { tg_puts(" FAIL " as *u8) } 50 tg_puts(label) 51 tg_puts(" got=" as *u8); tg_num(got) 52 tg_puts(" want=" as *u8); tg_num(want) 53 tg_puts("\n" as *u8) 54 if ok == 1 { return 0 } 55 return 1 56} 57 58// run the sleeper for ms with a budget; returns tr_run_capture_to's result 59func tg_run(ms: *u8, budget: i64, out: *u8, ol: *i64) -> i64 { 60 let av: *i64 = sys_mmap(32) as *i64 61 av[0] = TG_SLEEPER as i64 62 av[1] = ms as *u8 as i64 63 av[2] = 0 64 return tr_run_capture_to(TG_SLEEPER, av, out, TG_OUTCAP, ol, budget) 65} 66 67func main() -> i64 { 68 tg_puts("=== nx_tool_run_timeout_gate -- bounded exec vs a REAL hanging child ===\n" as *u8) 69 var fails: i64 = 0 70 let out: *u8 = sys_mmap(TG_OUTCAP) 71 let ol: *i64 = sys_mmap(16) as *i64 72 73 // ---- T1 + T2: the deadline fires, and it fires in real time ---- 74 let t0: i64 = sys_now_realtime_sec() 75 let r1: i64 = tg_run("10000" as *u8, TG_BUDGET_MS, out, ol) 76 let t1: i64 = sys_now_realtime_sec() 77 let elapsed: i64 = t1 - t0 78 79 var ok: i64 = 0; if r1 == TR_ERR_TIMEOUT { ok = 1 } 80 fails = fails + tg_check(ok, "T1 deadline fires (TR_ERR_TIMEOUT)" as *u8, r1, TR_ERR_TIMEOUT) 81 82 ok = 0; if elapsed < TG_MAX_ELAPSED_S { ok = 1 } 83 fails = fails + tg_check(ok, "T2 returned in real time, not after the full 10s sleep" as *u8, elapsed, TG_MAX_ELAPSED_S) 84 85 // ---- T3 + T4: NEG-CONTROL, a fast child must NOT be timed out and its output must survive ---- 86 ol[0] = 0 87 let r3: i64 = tg_run("50" as *u8, TG_FAST_BUDGET_MS, out, ol) 88 ok = 0; if r3 == 0 { ok = 1 } 89 fails = fails + tg_check(ok, "T3 neg-ctrl fast child exits 0 (no false timeout)" as *u8, r3, 0) 90 91 ok = tr_contains(out, ol[0], "SLEEPER-DONE" as *u8) 92 fails = fails + tg_check(ok, "T4 fast child's stdout captured" as *u8, ol[0], ol[0]) 93 94 // ---- T5: repeatable -- a second kill must behave identically (no leaked fd/pid/zombie) ---- 95 let r5: i64 = tg_run("10000" as *u8, TG_BUDGET_MS, out, ol) 96 ok = 0; if r5 == TR_ERR_TIMEOUT { ok = 1 } 97 fails = fails + tg_check(ok, "T5 second timeout identical (no leak after a kill)" as *u8, r5, TR_ERR_TIMEOUT) 98 99 // ---- T6: opt-out is explicit, never silent ---- 100 ol[0] = 0 101 let r6: i64 = tg_run("50" as *u8, 0, out, ol) 102 ok = 0; if r6 == 0 { ok = 1 } 103 fails = fails + tg_check(ok, "T6 timeout_ms<=0 delegates to unbounded path" as *u8, r6, 0) 104 105 tg_puts("NX-TOOL-RUN-TIMEOUT-GATE checks=6 fails=" as *u8); tg_num(fails) 106 if fails == 0 { tg_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 107 tg_puts(" verdict=RED\n" as *u8) 108 sys_exit(1) 109 return 1 110}