nx_jobcancel_lib.nx source
↩ module page · 233 lines · 10634 B
1// nx_jobcancel_lib.nx -- THE ONE cancel path for the jobs nx_job_run launched (engineshift ES29, 2026-09-15).
2//
3// WHY. MEASURED 2026-09-15: two cross-encoder one-shots launched through nx_job_run held the estate host at load 27
4// on 8 cpus for over 30 hours with 0 bytes of output, and admission correctly refused every build -- including the
5// builds that would have replaced them. No sovereign surface could stop them: the launcher had no cancel verb, the
6// resource governor's law forbids recycling a one-shot (never terminate what nothing will restart), and the
7// /proc-walking diagnostics refuse to run above the very load the runaway causes. A RUNAWAY JOB THAT CANNOT BE
8// CANCELLED IS A RESOURCE INCIDENT WITH NO REMEDY. This lib is the remedy, and it is BOUNDED BY CONSTRUCTION:
9// * it cancels only a pid the launcher's own journal recorded for that out-file (REFUSED-ABSENT otherwise);
10// * it signals only if /proc/<pid>/cmdline argv[0] is the elf the journal recorded (REFUSED-MISMATCH otherwise --
11// a recycled pid, a stranger, a daemon: never touched);
12// * TERM first, a named grace (knowledge/jobrun.conf cancel_grace_ms, bootstrap default below), then KILL, then
13// one more grace; every outcome is journaled beside the launch it cancels, so the record reads
14// launch -> cancel -> outcome in one file.
15// The journal row: <kind> TAB <epoch> TAB <pid> TAB <elf> TAB <out-file> TAB <name-or-outcome>, kind = launch | cancel.
16// license_tier: ORIGINAL. No hw writes (Rule 26).
17import "nx_syscalls.nx"
18import "nx_itoa_lib.nx"
19
20const JC_JRNL: *u8 = "knowledge/status/jobrun.jrnl"
21const JC_CONF: *u8 = "knowledge/jobrun.conf"
22const JC_CONF_KEY: *u8 = "cancel_grace_ms"
23const JC_GRACE_MS_DEFAULT: i64 = 3000 // bootstrap default (rule 17: env > svc-config > bootstrap > code); the conf row overrides
24const JC_POLL_MS: i64 = 100
25const JC_SIGTERM: i64 = 15
26const JC_SIGKILL: i64 = 9
27const JC_CMDLINE_CAP: i64 = 4096 // /proc/<pid>/cmdline has no size (lseek END reads 0), so ONE bounded read; only argv[0] is compared, a longer cmdline is compared on its prefix and that is announced by JC_CMDLINE_CAP in the receipt
28const JC_PATH_CAP: i64 = 128
29const JC_ROW_CAP: i64 = 2048
30const JC_MODE_644: i64 = 420
31const JC_TAB: i64 = 9
32const JC_LF: i64 = 10
33const JC_SPACE: i64 = 32
34const JC_CH_ZERO: i64 = 48
35const JC_CH_NINE: i64 = 57
36const JC_OK_TERM: i64 = 0
37const JC_OK_KILL: i64 = 1
38const JC_REFUSED_ABSENT: i64 = 2
39const JC_REFUSED_MISMATCH: i64 = 3
40const JC_ALREADY_GONE: i64 = 4
41const JC_STILL_ALIVE: i64 = 5
42const JC_F_KIND: i64 = 0
43const JC_F_EPOCH: i64 = 1
44const JC_F_PID: i64 = 2
45const JC_F_ELF: i64 = 3
46const JC_F_OUT: i64 = 4
47const JC_F_NAME: i64 = 5
48const JC_KIND_LAUNCH: *u8 = "launch"
49const JC_KIND_CANCEL: *u8 = "cancel"
50
51func jc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
52func jc_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i }
53// a NUL-terminated string equals the byte run s[0..n)
54func jc_eq_n(a: *u8, s: *u8, n: i64) -> i64 {
55 if jc_slen(a) != n { return 0 }
56 var i: i64 = 0
57 while i < n { if a[i] != s[i] { return 0 } i = i + 1 }
58 return 1
59}
60func jc_streq(a: *u8, b: *u8) -> i64 { return jc_eq_n(a, b, jc_slen(b)) }
61func jc_atoi_n(s: *u8, n: i64) -> i64 {
62 var v: i64 = 0
63 var i: i64 = 0
64 while i < n {
65 let c: i64 = s[i] as i64
66 if c < JC_CH_ZERO { return v }
67 if c > JC_CH_NINE { return v }
68 v = v * 10 + (c - JC_CH_ZERO)
69 i = i + 1
70 }
71 return v
72}
73// field k of a TAB row [line, line+n): offset and length; 1 when present
74func jc_field(line: *u8, n: i64, k: i64, off: *i64, len: *i64) -> i64 {
75 var f: i64 = 0
76 var s: i64 = 0
77 var i: i64 = 0
78 while i <= n {
79 var hit: i64 = 0
80 if i == n { hit = 1 } else { if line[i] == (JC_TAB as u8) { hit = 1 } }
81 if hit == 1 {
82 if f == k { off[0] = s; len[0] = i - s; return 1 }
83 f = f + 1
84 s = i + 1
85 }
86 i = i + 1
87 }
88 return 0
89}
90func jc_procpath(out: *u8, pid: i64, suffix: *u8) -> i64 {
91 var o: i64 = jc_cat(out, 0, "/proc/" as *u8)
92 o = ccz_cat_num(out, o, pid)
93 out[o] = 47 as u8; o = o + 1
94 o = jc_cat(out, o, suffix)
95 out[o] = 0 as u8
96 return o
97}
98// is there a process at pid at all (a zombie still answers yes here; jc_verify tells it apart)
99func jc_alive(pid: i64) -> i64 {
100 let p: *u8 = sys_mmap(JC_PATH_CAP)
101 jc_procpath(p, pid, "stat" as *u8)
102 let fd: i64 = sys_openat_rd(p)
103 if fd < 0 { return 0 }
104 sys_close(fd)
105 return 1
106}
107// ONE bounded read of /proc/<pid>/cmdline into buf; bytes read, or -1 when the process is gone
108func jc_cmdline(pid: i64, buf: *u8, cap: i64) -> i64 {
109 let p: *u8 = sys_mmap(JC_PATH_CAP)
110 jc_procpath(p, pid, "cmdline" as *u8)
111 let fd: i64 = sys_openat_rd(p)
112 if fd < 0 { return 0 - 1 }
113 var n: i64 = 0
114 var r: i64 = 1
115 while r > 0 { if n < cap { r = sys_read(fd, (buf as i64 + n) as *u8, cap - n); if r > 0 { n = n + r } } else { r = 0 } }
116 sys_close(fd)
117 return n
118}
119// 1 = argv[0] of the live process IS the elf the journal recorded; 0 = a different process (or a zombie, whose
120// cmdline is empty) sits at that pid; -1 = no process at that pid
121func jc_verify(pid: i64, elf: *u8) -> i64 {
122 let buf: *u8 = sys_mmap(JC_CMDLINE_CAP)
123 let n: i64 = jc_cmdline(pid, buf, JC_CMDLINE_CAP)
124 if n < 0 { return 0 - 1 }
125 if n == 0 { return 0 }
126 var a0: i64 = 0
127 while a0 < n { if buf[a0] == (0 as u8) { break } a0 = a0 + 1 }
128 if jc_eq_n(elf, buf, a0) == 1 { return 1 }
129 return 0
130}
131// the LAST launch row whose out-file is outfile: pid into pidp, elf copied into elfbuf; 1 found / 0 absent
132func jc_find(jrnl: *u8, outfile: *u8, pidp: *i64, elfbuf: *u8, elfcap: i64) -> i64 {
133 let nb: *i64 = sys_mmap(16) as *i64
134 nb[0] = 0
135 let buf: *u8 = sys_read_file(jrnl, nb)
136 if (buf as i64) == 0 { return 0 }
137 let n: i64 = nb[0]
138 let off: *i64 = sys_mmap(16) as *i64
139 let len: *i64 = sys_mmap(16) as *i64
140 var found: i64 = 0
141 var p: i64 = 0
142 while p < n {
143 var e: i64 = p
144 while e < n { if buf[e] == (JC_LF as u8) { break } e = e + 1 }
145 let line: *u8 = (buf as i64 + p) as *u8
146 let ln: i64 = e - p
147 p = e + 1
148 if jc_field(line, ln, JC_F_KIND, off, len) == 1 { if jc_eq_n(JC_KIND_LAUNCH, (line as i64 + off[0]) as *u8, len[0]) == 1 {
149 if jc_field(line, ln, JC_F_OUT, off, len) == 1 { if jc_eq_n(outfile, (line as i64 + off[0]) as *u8, len[0]) == 1 {
150 if jc_field(line, ln, JC_F_PID, off, len) == 1 { pidp[0] = jc_atoi_n((line as i64 + off[0]) as *u8, len[0]) }
151 if jc_field(line, ln, JC_F_ELF, off, len) == 1 {
152 var l: i64 = len[0]
153 if l >= elfcap { l = elfcap - 1 }
154 var i: i64 = 0
155 while i < l { elfbuf[i] = line[off[0] + i]; i = i + 1 }
156 elfbuf[l] = 0 as u8
157 }
158 found = 1
159 } }
160 } }
161 }
162 return found
163}
164// append one row; fail-soft (bookkeeping must never break the work): 1 written, 0 not
165func jc_journal(jrnl: *u8, kind: *u8, epoch: i64, pid: i64, elf: *u8, outfile: *u8, tail: *u8) -> i64 {
166 let fd: i64 = sys_openat_append(jrnl, JC_MODE_644)
167 if fd < 0 { return 0 }
168 let row: *u8 = sys_mmap(JC_ROW_CAP)
169 var o: i64 = jc_cat(row, 0, kind); row[o] = JC_TAB as u8; o = o + 1
170 o = ccz_cat_num(row, o, epoch); row[o] = JC_TAB as u8; o = o + 1
171 o = ccz_cat_num(row, o, pid); row[o] = JC_TAB as u8; o = o + 1
172 o = jc_cat(row, o, elf); row[o] = JC_TAB as u8; o = o + 1
173 o = jc_cat(row, o, outfile); row[o] = JC_TAB as u8; o = o + 1
174 o = jc_cat(row, o, tail); row[o] = JC_LF as u8; o = o + 1
175 sys_write(fd, row, o)
176 sys_close(fd)
177 return 1
178}
179// `cancel_grace_ms <n>` from the conf; the default when the conf or the row is absent
180func jc_conf_grace(conf: *u8, dflt: i64) -> i64 {
181 let nb: *i64 = sys_mmap(16) as *i64
182 nb[0] = 0
183 let buf: *u8 = sys_read_file(conf, nb)
184 if (buf as i64) == 0 { return dflt }
185 let n: i64 = nb[0]
186 let kl: i64 = jc_slen(JC_CONF_KEY)
187 var p: i64 = 0
188 while p < n {
189 var e: i64 = p
190 while e < n { if buf[e] == (JC_LF as u8) { break } e = e + 1 }
191 if e - p > kl { if jc_eq_n(JC_CONF_KEY, (buf as i64 + p) as *u8, kl) == 1 { if buf[p + kl] == (JC_SPACE as u8) {
192 let v: i64 = jc_atoi_n((buf as i64 + p + kl + 1) as *u8, e - p - kl - 1)
193 if v > 0 { return v }
194 } } }
195 p = e + 1
196 }
197 return dflt
198}
199func jc_outcome_name(code: i64) -> *u8 {
200 if code == JC_OK_TERM { return "CANCELLED" as *u8 }
201 if code == JC_OK_KILL { return "CANCELLED-KILLED" as *u8 }
202 if code == JC_REFUSED_ABSENT { return "REFUSED-ABSENT" as *u8 }
203 if code == JC_REFUSED_MISMATCH { return "REFUSED-MISMATCH" as *u8 }
204 if code == JC_ALREADY_GONE { return "ALREADY-GONE" as *u8 }
205 return "STILL-ALIVE" as *u8
206}
207// wait up to grace_ms for the journaled elf to leave the pid; 1 when it left
208func jc_wait_gone(pid: i64, elf: *u8, grace_ms: i64, poll_ms: i64) -> i64 {
209 var waited: i64 = 0
210 while waited < grace_ms {
211 sys_sleep_ms(poll_ms)
212 waited = waited + poll_ms
213 if jc_verify(pid, elf) != 1 { return 1 }
214 }
215 return 0
216}
217// THE VERB. Returns a JC_* code; pidp carries the pid acted on (0 when none). Every outcome is journaled.
218func jc_cancel(jrnl: *u8, outfile: *u8, grace_ms: i64, poll_ms: i64, pidp: *i64) -> i64 {
219 pidp[0] = 0
220 let elf: *u8 = sys_mmap(JC_ROW_CAP)
221 if jc_find(jrnl, outfile, pidp, elf, JC_ROW_CAP) == 0 { return JC_REFUSED_ABSENT }
222 let pid: i64 = pidp[0]
223 let now: i64 = sys_now_realtime_sec()
224 let v: i64 = jc_verify(pid, elf)
225 if v < 0 { jc_journal(jrnl, JC_KIND_CANCEL, now, pid, elf, outfile, jc_outcome_name(JC_ALREADY_GONE)); return JC_ALREADY_GONE }
226 if v == 0 { jc_journal(jrnl, JC_KIND_CANCEL, now, pid, elf, outfile, jc_outcome_name(JC_REFUSED_MISMATCH)); return JC_REFUSED_MISMATCH }
227 nx_kill(pid, JC_SIGTERM)
228 if jc_wait_gone(pid, elf, grace_ms, poll_ms) == 1 { jc_journal(jrnl, JC_KIND_CANCEL, now, pid, elf, outfile, jc_outcome_name(JC_OK_TERM)); return JC_OK_TERM }
229 nx_kill(pid, JC_SIGKILL)
230 if jc_wait_gone(pid, elf, grace_ms, poll_ms) == 1 { jc_journal(jrnl, JC_KIND_CANCEL, now, pid, elf, outfile, jc_outcome_name(JC_OK_KILL)); return JC_OK_KILL }
231 jc_journal(jrnl, JC_KIND_CANCEL, now, pid, elf, outfile, jc_outcome_name(JC_STILL_ALIVE))
232 return JC_STILL_ALIVE
233}