code wiki / (root) / nx_run_timeout.nx

nx_run_timeout.nx source

↩ module page · 67 lines · 3236 B

1// nx_run_timeout.nx -- sovereign RUN-WITH-TIMEOUT primitive (bits-up, reusable). 2// 3// REASON TO EXIST: the engineer's cockpit (nx_engineer.nx) runs gates with a 4// BLOCKING sys_wait4(pid, st, 0) -- so a gate that HANGS (infinite loop) hangs 5// the engineer itself. It can clock SLOW but cannot detect a HANG. This is the 6// bits-up capability gap. nx_run_timeout closes it: fork -> exec -> poll-wait 7// against a monotonic deadline -> KILL on overrun. Returns the child's exit 8// code, or NX_RT_TIMEOUT if it had to be killed (a true infinite loop). 9// 10// Reuse set: nx_engineer (detect hanging gates, not just slow), the compiler 11// loop-detector (nx_cc_probe), any supervised spawn that must not block forever. 12// NishiLang-native -- replaces `timeout N cmd` shell scaffolding. 13// license_tier: ORIGINAL 14 15import "nx_syscalls.nx" 16 17// kill(2) number: x86_64=62, rv64=129 (asm-generic). Matches nx_syscalls' own 18// @ifdef-selected process-control consts, which are NOT backend-translated. 19// alarm(2): x86_64 = 37. (rv64 has no plain alarm -> setitimer; dev box is x86.) 20@ifdef TARGET_X86_64 21const NX_RT_SYS_ALARM: i64 = 37 22@endif 23@ifndef TARGET_X86_64 24const NX_RT_SYS_ALARM: i64 = 37 25@endif 26 27const NX_RT_TIMEOUT: i64 = 1000 // child SIGALRM'd at the deadline (a HANG/loop) 28const NX_RT_FORK_FAIL: i64 = 1001 // fork or wait anomaly 29const NX_RT_SIGALRM: i64 = 14 // the deadline signal (default action = terminate) 30 31// arm a self-timeout on the CURRENT process: SIGALRM after `sec` seconds. The 32// child arms this just before execve, so a hung child terminates itself -- no 33// fragile parent-side poll/kill (which failed to stop a truly-looping child). 34func nx_rt_alarm(sec: i64) -> i64 { 35 return __syscall(NX_RT_SYS_ALARM, sec, 0, 0, 0, 0, 0) 36} 37 38// Run `path` with `argv`/`envp`, stdout+stderr -> /dev/null, killing it if it 39// runs past `timeout_ms`. Writes wall-time (ms) to *elapsed_ms_out if non-null. 40// Returns child exit code (0..255), NX_RT_TIMEOUT (killed), or NX_RT_FORK_FAIL. 41func nx_run_timeout(path: *u8, argv: *i64, envp: *i64, 42 timeout_ms: i64, elapsed_ms_out: *i64) -> i64 { 43 let t0: i64 = sys_now_ms() 44 var tsec: i64 = (timeout_ms + 999) / 1000 // ceil to whole seconds 45 if tsec < 1 { tsec = 1 } 46 let pid: i64 = sys_fork() 47 if pid == 0 { 48 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0) 49 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 50 nx_rt_alarm(tsec) // self-deadline: SIGALRM terminates a hung child 51 sys_execve(path, argv, envp) 52 sys_exit(127) 53 } 54 if pid < 0 { 55 if (elapsed_ms_out as i64) != 0 { elapsed_ms_out[0] = 0 } 56 return NX_RT_FORK_FAIL 57 } 58 let st: *i64 = sys_mmap(16) as *i64 59 let r: i64 = sys_wait4(pid, st, 0) // BLOCKING wait -- robust, proven in nx_engineer 60 if (elapsed_ms_out as i64) != 0 { elapsed_ms_out[0] = sys_now_ms() - t0 } 61 if r < 0 { return NX_RT_FORK_FAIL } 62 let status: i64 = st[0] 63 let termsig: i64 = status & 0x7f 64 if termsig == NX_RT_SIGALRM { return NX_RT_TIMEOUT } // deadline hit = HANG/loop 65 if termsig != 0 { return 1000 + termsig } // other signal = crashed (not healthy) 66 return wait_exit_code(status) 67}