nx_runsv.nx source
↩ module page · 96 lines · 5217 B
1// nx_runsv.nx -- RUN + SUPERVISE one service (s6/runit `runsv` convention). This is the REAL fork/wait-liveness +
2// real-health + backoff supervisor that the existing nx_supervise gate (runtime/_hdl_build/nx_supervise.nx) explicitly
3// flags as "remaining" (its health is MODELED; this wires it to reality). Best-practice model (June-2026: supervisord/
4// s6/runit/systemd): the SUPERVISOR daemonizes, the SERVICE runs FOREGROUND under it. Composes the team's organs --
5// NO hand-rolled daemonize/health/backoff:
6// nx_health_probe.hp_probe -> readiness (serving, not just bound; catches HUNG)
7// nx_restart_guard.rg_should_restart -> crash-loop backoff (systemd-parity + exponential)
8// fork + fd->log + execve + wait4(WNOHANG) -> foreground spawn + liveness
9// Roadmap: knowledge/research/2026-06-30-service-supervisor-roadmap.md. Usage: nx_runsv <elf-path> <port>
10// license_tier: ORIGINAL
11import "nx_health_probe.nx"
12import "nx_restart_guard.nx"
13import "nx_svcreg.nx"
14import "nx_gate.nx"
15const RS_MAGIC_30000: i64 = 30000
16const RS_MAGIC_5000: i64 = 5000
17
18const RS_OK: i64 = 0
19const RS_RESPAWN: i64 = 1
20const RS_BACKOFF: i64 = 2
21const RS_KILLHUNG: i64 = 3
22
23func rs_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let d: i64=s[i] as i64; if d>=48 { if d<=57 { v=v*10+(d-48) } } i=i+1 } return v }
24
25// spawn `elf` as a FOREGROUND child of the supervisor; its stdout/stderr -> logpath; stdin <- /dev/null. Returns pid.
26func rs_spawn_fg(elf: *u8, logpath: *u8) -> i64 {
27 let pid: i64 = sys_fork()
28 if pid == 0 {
29 let din: i64 = sys_openat_rd("/dev/null" as *u8)
30 let dlog: i64 = sys_openat_wr(logpath, 0x1a4)
31 if din >= 0 { sys_dup3(din, 0, 0) }
32 if dlog >= 0 { sys_dup3(dlog, 1, 0); sys_dup3(dlog, 2, 0) }
33 let av: *i64 = sys_mmap(16) as *i64; av[0] = elf as i64; av[1] = 0
34 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 // real env (matches nx_hostctl / nx_sov_build_run's proven spawn)
35 sys_execve(elf, av, envp)
36 sys_exit(127)
37 }
38 return pid
39}
40
41// decide the supervisor action from liveness (dead) + readiness (verdict), updating the crash-loop state.
42func rs_decide(dead: i64, verdict: i64, ws: *i64, cnt: *i64, now: i64) -> i64 {
43 var down: i64 = 0
44 if dead == 1 { down = 1 }
45 if verdict == HP_REFUSED { down = 1 }
46 if down == 1 {
47 if rg_should_restart(ws, cnt, now, RS_MAGIC_30000, 100) == 1 { return RS_RESPAWN }
48 return RS_BACKOFF
49 }
50 if verdict == HP_HUNG { return RS_KILLHUNG }
51 return RS_OK
52}
53
54func main(argc: i64, argv: *i64) -> i64 {
55 if argc < 2 { gw("usage: nx_runsv <service-name> | <elf-path> <port>\n" as *u8); sys_exit(2); return 2 }
56 var elf: *u8 = argv[1] as *u8
57 var port: i64 = 0
58 if argc == 2 {
59 // declarative (R5): resolve the service BY NAME from the registry -> elf + port
60 let box: *i64 = sys_mmap(16) as *i64; let lenbox: *i64 = sys_mmap(16) as *i64
61 if svc_get(argv[1] as *u8, box, lenbox) != 1 { gw("nx_runsv: unknown service '" as *u8); gw(argv[1] as *u8); gw("' (not in registry)\n" as *u8); sys_exit(1); return 1 }
62 let rec: *u8 = box[0] as *u8; let rl: i64 = lenbox[0]
63 let out2: *i64 = sys_mmap(16) as *i64
64 if tr_field(rec, 0, rl, 1, out2) == 0 { gw("nx_runsv: bad unit record\n" as *u8); sys_exit(1); return 1 }
65 let elfbuf: *u8 = sys_mmap(512); var bi: i64=0; while bi < out2[1] { elfbuf[bi]=rec[out2[0]+bi]; bi=bi+1 } elfbuf[out2[1]]=0 as u8
66 elf = elfbuf
67 tr_field(rec, 0, rl, 2, out2); port = tr_atoi(rec, out2[0], out2[1])
68 gw("nx_runsv: resolved '" as *u8); gw(argv[1] as *u8); gw("' -> " as *u8); gw(elf); gw(" :" as *u8); gn(port); gw("\n" as *u8)
69 } else {
70 port = rs_atoi(argv[2] as *u8)
71 }
72
73 // FOREGROUND supervisor, kept alive by init (the init -> supervisor -> service hierarchy). On a real sovereign
74 // host nx_runsv self-daemonizes; on WSL-from-Windows a detached daemon is torn down when the invoking command
75 // exits, so the supervisor runs foreground under the persistent session (the WSL init-equivalent). It does NOT
76 // redirect its own fds -- init captures its output; it redirects the SERVICE's fds (rs_spawn_fg).
77 gw("nx_runsv: supervising " as *u8); gw(elf); gw(" on :" as *u8); gn(port); gw(" (foreground supervisor)\n" as *u8)
78
79 let ws: *i64 = sys_mmap(16) as *i64; let cnt: *i64 = sys_mmap(16) as *i64; ws[0] = 0; cnt[0] = 0
80 let svclog: *u8 = "/tmp/nx_runsv_service.log" as *u8
81 var pid: i64 = rs_spawn_fg(elf, svclog)
82 var go: i64 = 1
83 while go == 1 {
84 sys_sleep_ms(RS_MAGIC_5000)
85 let st: *i64 = sys_mmap(16) as *i64
86 let reaped: i64 = sys_wait4(pid, st, 1) // WNOHANG -- liveness
87 var dead: i64 = 0; if reaped == pid { dead = 1 }
88 var verdict: i64 = HP_SERVING
89 if dead == 0 { verdict = hp_probe(port, 3) } // readiness
90 let now: i64 = sys_now_ms()
91 let act: i64 = rs_decide(dead, verdict, ws, cnt, now)
92 if act == RS_RESPAWN { pid = rs_spawn_fg(elf, svclog) }
93 if act == RS_KILLHUNG { nx_kill(pid, 9) }
94 }
95 return 0
96}