code wiki / _hdl_build / nx_clock_sched.nx

nx_clock_sched.nx source

↩ module page · 567 lines · 32476 B

1// nx_clock_sched.nx -- RENAMED FROM nx_clock.nx 2026-07-31 (lib-reconcile). PERMANENT FIX for a 2// two-libraries-one-name collision: runtime/nx_clock.nx is the TIMING library (nx_clock_monotonic_ns, 3// 62 importers); THIS file is an unrelated tickless JOB SCHEDULER (clk_* registry + dispatcher) that 4// only shared the filename. nx_cc binds an import to the IMPORTER'S OWN DIRECTORY FIRST, so every 5// _hdl_build organ importing nx_clock.nx silently got the SCHEDULER -- _clk_probe.nx, whose whole 6// purpose is to prove nx_clock_monotonic_ns works, could not resolve it. The RENAME is the fix. 7// so we avoid issues ... dont have a million pulses and daemons"). Researched (knowledge/fetched/sched_*.raw: 8// clock-distribution = ONE oscillator -> a tree of DIVIDERS; PLL derives every frequency from ONE reference; 9// cron = ONE daemon reads ONE table of timed jobs; tickless = don't burn a constant tick when idle). The S-class 10// pattern is identical: ONE tick source + ONE durable JOB REGISTRY + ONE dispatcher. Every periodic capability 11// REGISTERS a job (name, interval-in-ticks) = a divider off the single clock -- it does NOT spin up its own 12// daemon/pulse loop. So N capabilities cost ONE loop, not N. This library is the registry + dispatcher; the single 13// tick source drives it (one external spark, like a crystal). license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_store_seed_lib.nx" 16const CLK_MAGIC_1000000: i64 = 1000000 17 18const CLK_MAXJOBS: i64 = 128 19const CLK_NAMEW: i64 = 128 // bytes per name/organ slot. WAS 48 (2026-08-03, debt 1784413227): 20 // every organ path longer than 47 bytes was SILENTLY TRUNCATED at 21 // register/load/save -- sitecheck (51-char abs path), worldgen-gate 22 // and bootstrap-gate (63-char _build paths) all fork-failed 127 on 23 // every tick, invisibly, because the dispatcher chmods+execs the 24 // truncated string. 128 holds every path shape the estate uses. 25const CLK_ROWW: i64 = 320 // per-row serialization budget (2 slots + 2 numbers + tabs) 26const CLK_REG: *u8 = "knowledge/sched/jobs.tsv" // RETIRED LEGACY PATH (2026-08-03, debt 1785792856): 27 // kept only so old fixtures parse; the live state SSOT is the 28 // clocksched- seg-store plane (clk_load_state / clk_save_plane below) 29 30func clk_slot(names: *u8, i: i64) -> *u8 { return ((names as i64) + i*CLK_NAMEW) as *u8 } 31func clk_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 } 32 33func clk_find(names: *u8, n: i64, name: *u8) -> i64 { var i: i64=0; while i<n { if clk_streq(clk_slot(names,i), name)==1 { return i } i=i+1 } return 0-1 } 34 35// REGISTER a periodic job = add a divider off the one clock. Idempotent: re-registering the same name is a no-op 36// (so capabilities can declare their job every boot without ever creating a duplicate pulse). returns 1 if added. 37func clk_register(names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, name: *u8, organ: *u8, interval: i64) -> i64 { 38 let n: i64 = np[0] 39 if clk_find(names, n, name) >= 0 { return 0 } 40 if n >= CLK_MAXJOBS { return 0 } 41 var d: *u8 = clk_slot(names, n); var i: i64=0 42 while i<CLK_NAMEW-1 { if name[i]==(0 as u8){ d[i]=0 as u8; i=CLK_NAMEW } else { d[i]=name[i]; i=i+1 } } 43 if i==CLK_NAMEW-1 { d[i]=0 as u8 } 44 var e: *u8 = clk_slot(organs, n); var j: i64=0 45 while j<CLK_NAMEW-1 { if organ[j]==(0 as u8){ e[j]=0 as u8; j=CLK_NAMEW } else { e[j]=organ[j]; j=j+1 } } 46 if j==CLK_NAMEW-1 { e[j]=0 as u8 } 47 var iv: i64 = interval; if iv < 1 { iv = 1 } 48 intervals[n] = iv; next_due[n] = iv; np[0] = n + 1 49 return 1 50} 51 52// THE DISPATCHER: advance to logical tick `now`; mark + return how many jobs are DUE (next_due<=now), advancing 53// each due job's next_due by its interval (catch-up safe: a job never fires more than once for a missed window). 54func clk_tick(intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 { 55 var count: i64 = 0; var i: i64 = 0 56 while i < n { 57 if next_due[i] <= now { 58 fired[i] = 1; count = count + 1 59 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (no runaway on a skipped tick) 60 } else { fired[i] = 0 } 61 i = i + 1 62 } 63 return count 64} 65 66// THE FUNCTIONAL DISPATCH: advance to `now` and actually RUN each due job by fork+exec of its organ elf path 67// (the same fork+exec idiom nx_god_pulse / nx_aw_hostctl use). One dispatcher runs N jobs at their divided rates; 68// there is NO per-job daemon. Parent waits each child so a slow job can't be lost (a real scheduler can make this 69// bounded-concurrent / fire-and-forget). `organs` is a parallel slot array (organ[i] = an executable path). Returns 70// the number of jobs dispatched this tick. 71func clk_dispatch_run(organs: *u8, intervals: *i64, next_due: *i64, n: i64, now: i64, fired: *i64) -> i64 { 72 var count: i64 = 0; var i: i64 = 0 73 while i < n { 74 if next_due[i] <= now { 75 fired[i] = 1 76 while next_due[i] <= now { next_due[i] = next_due[i] + intervals[i] } // re-arm past now (catch-up safe) 77 let path: *u8 = clk_slot(organs, i) 78 __syscall(90, path as i64, 0x1ed, 0, 0, 0, 0) // chmod 0755 first: recv-shipped organs aren't reliably +x (HC_MGMT_CMD chmods for the same reason) -> without this execve fails 127 = silent no-dispatch 79 let pid: i64 = sys_fork() 80 if pid == 0 { 81 let argv: *i64 = sys_mmap(32) as *i64; argv[0] = path as i64; argv[1] = 0 82 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 83 sys_execve(path, argv, envp) 84 sys_exit(127) 85 } 86 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0 87 sys_wait4(pid, st, 0) 88 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff 89 // HONEST count: a job whose organ failed to exec (child exit 127 = missing/broken organ) did NOT run, 90 // so it is not counted as dispatched (it gets fired[]=1 so a caller can flag it, mirroring the 91 // publisher's dead-letter). A crash (signalled) or any other exit means the organ DID run. 92 if sig != 0 { count = count + 1 } else { if code != 127 { count = count + 1 } } 93 } else { fired[i] = 0 } 94 i = i + 1 95 } 96 return count 97} 98 99// TICKLESS run (sched_tickless lesson): from start_tick, run up to maxbeats beats, but before each beat SLEEP 100// exactly until the MINIMUM next_due across all jobs -- skipping every idle tick -- then dispatch the due organs. 101// Advances next_due[] in place (the caller persists for resume). out[0..3] = beats, dispatches, skipped_idle, 102// final_tick. ONE implementation, shared by the driver (nx_clock_tickless) and its gate (no parallel copy). 103func clk_run_tickless(organs: *u8, intervals: *i64, next_due: *i64, n: i64, start_tick: i64, maxbeats: i64, tick_ms: i64, out: *i64) -> i64 { 104 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 105 var T: i64 = start_tick; var beats: i64 = 0; var disp: i64 = 0; var skipped: i64 = 0 106 while beats < maxbeats { 107 var minnd: i64 = next_due[0]; var i: i64 = 1 108 while i < n { if next_due[i] < minnd { minnd = next_due[i] } i = i + 1 } 109 if minnd <= T { minnd = T + 1 } 110 let skip: i64 = minnd - T 111 if skip > 1 { skipped = skipped + (skip - 1) } 112 let totms: i64 = skip * tick_ms 113 let ts: *i64 = sys_mmap(16) as *i64; ts[0] = totms / 1000; ts[1] = (totms - (totms/1000)*1000) * CLK_MAGIC_1000000 114 __syscall(35, ts as i64, 0, 0, 0, 0, 0) // nanosleep until the next due event (tickless) 115 T = minnd 116 disp = disp + clk_dispatch_run(organs, intervals, next_due, n, T, fired) 117 beats = beats + 1 118 } 119 out[0] = beats; out[1] = disp; out[2] = skipped; out[3] = T 120 return disp 121} 122 123// STATUS helpers (the consolidation payoff: ONE place shows every periodic job). clk_due_in = ticks until a job 124// fires (<=0 means due now). clk_twin_ok = 1 iff the job's organ elf actually exists (a 0 means it is registered 125// but un-blessed -> the dispatcher would honestly report it didn't run -> surface it in status, do not hide it). 126func clk_due_in(next_due_i: i64, now: i64) -> i64 { return next_due_i - now } 127func clk_twin_ok(organ: *u8) -> i64 { let fd: i64 = sys_openat_rd(organ); if fd < 0 { return 0 } sys_close(fd); return 1 } 128 129func clk_itoa(buf: *u8, o: i64, v: i64) -> i64 { var w: i64=o; var m: i64=v; if m==0{buf[w]=48 as u8;return w+1} if m<0{buf[w]=45 as u8;w=w+1;m=0-m} let t:*u8=sys_mmap(24); var k:i64=0; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var j:i64=0; while j<k{buf[w]=t[k-1-j];w=w+1;j=j+1} return w } 130 131// persist the registry (the ONE crontab): name<TAB>interval<TAB>next_due per line. atomic via tmp+rename. 132func clk_save(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 { 133 // ---- RELOAD-MERGE BEFORE SAVE (2026-07-31, id 1785559048) ------------------------------------------- 134 // PROVEN CLOBBER, by controlled experiment not inference: a well-formed row appended to the registry via 135 // the sovereign write path returned OK and read back present IMMEDIATELY -- and was GONE 75 seconds later, 136 // with no error, no log and no rejection. Reproduced twice. Cause is right here: this function serialized 137 // ONLY the caller's in-memory set, so the registry could contain nothing but what THIS process happened to 138 // load, and every row added between our load and our save was erased by the atomic rename below. 139 // WHY IT MATTERED: it silently closed what was then the only working extension point for scheduling. 140 // (⚠A CLAIM HERE WAS STALE AND IS RETRACTED 2026-08-03: this comment used to assert the clockjobs- 141 // plane was "retired/dead". MEASURED FALSE -- the live tickless clock merges that plane every window 142 // and rows registered via `nx_store_put knowledge/store/clockjobs- put <actor> <name> <interval> 143 // <organ>` demonstrably arm and fire. The plane IS the sanctioned add lane.) 144 // THE REMEDY IS THE ONE sts_append_row ALREADY APPLIED TO THE SEG-STORE: re-read the file we are about to 145 // overwrite and keep whatever we did not know about. Rows WE hold win outright (ours carry the advanced 146 // next_due); a row we have never seen is appended verbatim, keeping its own next_due so it arms exactly 147 // when its author intended instead of being silently re-armed or dropped. 148 // u00e2u02dcu2026A WRITER THAT SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER. 149 // u00e2u02dcu2026AN EXTENSION POINT THAT SILENTLY DISCARDS EXTENSIONS IS A CLOSED SYSTEM WEARING OPEN DOCUMENTATION. 150 var nn: i64 = n 151 let rb: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW) 152 let nm2: *u8 = sys_mmap(CLK_NAMEW) 153 let og2: *u8 = sys_mmap(CLK_NAMEW) 154 let rfd: i64 = sys_openat_rd(path) 155 if rfd >= 0 { 156 var rn: i64 = 0 157 var rr: i64 = 1 158 while rr > 0 { 159 rr = sys_read(rfd, ((rb as i64) + rn) as *u8, CLK_MAXJOBS*CLK_ROWW - rn) 160 if rr > 0 { rn = rn + rr } 161 } 162 sys_close(rfd) 163 var p: i64 = 0 164 var ls: i64 = 0 165 while p <= rn { 166 var eol: i64 = 0 167 if p == rn { eol = 1 } else { if rb[p] == (10 as u8) { eol = 1 } } 168 if eol == 1 { 169 if p > ls { 170 var f: i64 = 0 171 var q: i64 = ls 172 var iv2: i64 = 0 173 var nd2: i64 = 0 174 var w2: i64 = 0 175 var g2: i64 = 0 176 while q < p { 177 if rb[q] == (9 as u8) { f = f + 1 } else { 178 if f == 0 { if w2 < CLK_NAMEW - 1 { nm2[w2] = rb[q]; w2 = w2 + 1 } } 179 else { if f == 1 { if rb[q] >= (48 as u8) { if rb[q] <= (57 as u8) { iv2 = iv2*10 + ((rb[q] - (48 as u8)) as i64) } } } 180 else { if f == 2 { if rb[q] >= (48 as u8) { if rb[q] <= (57 as u8) { nd2 = nd2*10 + ((rb[q] - (48 as u8)) as i64) } } } 181 else { if g2 < CLK_NAMEW - 1 { og2[g2] = rb[q]; g2 = g2 + 1 } } } } 182 } 183 q = q + 1 184 } 185 nm2[w2] = 0 as u8 186 og2[g2] = 0 as u8 187 if w2 > 0 { if g2 > 0 { if clk_find(names, nn, nm2) < 0 { if nn < CLK_MAXJOBS { 188 var d2: *u8 = clk_slot(names, nn) 189 var a2: i64 = 0 190 while a2 < w2 { d2[a2] = nm2[a2]; a2 = a2 + 1 } 191 d2[w2] = 0 as u8 192 var e2: *u8 = clk_slot(organs, nn) 193 var b2: i64 = 0 194 while b2 < g2 { e2[b2] = og2[b2]; b2 = b2 + 1 } 195 e2[g2] = 0 as u8 196 if iv2 < 1 { iv2 = 1 } 197 intervals[nn] = iv2 198 next_due[nn] = nd2 199 nn = nn + 1 200 } } } } 201 } 202 ls = p + 1 203 } 204 p = p + 1 205 } 206 } 207 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0 208 var i: i64 = 0 209 while i < nn { 210 let nm: *u8 = clk_slot(names, i); var k: i64=0; while nm[k]!=(0 as u8){ buf[o]=nm[k]; o=o+1; k=k+1 } 211 buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, intervals[i]); buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, next_due[i]); buf[o]=9 as u8; o=o+1 212 let og: *u8 = clk_slot(organs, i); var g: i64=0; while og[g]!=(0 as u8){ buf[o]=og[g]; o=o+1; g=g+1 } 213 buf[o]=10 as u8; o=o+1 214 i = i + 1 215 } 216 sys_mkdir("knowledge" as *u8, 0x1ed); sys_mkdir("knowledge/sched" as *u8, 0x1ed) 217 let tmp: *u8 = sys_mmap(256); var t: i64=0; let pp: *u8 = path; while pp[t]!=(0 as u8){ tmp[t]=pp[t]; t=t+1 } tmp[t]=46 as u8; tmp[t+1]=116 as u8; tmp[t+2]=109 as u8; tmp[t+3]=112 as u8; tmp[t+4]=0 as u8 // path + ".tmp" 218 let fd: i64 = sys_openat_wr(tmp, 0x1a4); if fd<0 { return 0 } sys_write(fd, buf, o); sys_close(fd) 219 __syscall(82, tmp as i64, path as i64, 0, 0, 0, 0) // atomic rename .tmp -> registry 220 return nn 221} 222 223// ---- PLANE-NATIVE STATE PERSISTENCE (2026-08-03, debts 1784828927 + 1784868625: the operator law is 224// planes, never tsv). The mutable schedule state (name·interval·next_due·organ per row, the thing the 225// clock resumes from) lives in a seg-store plane, written once per window via sts_seed. The plane is 226// CLOCK-EXCLUSIVE by doctrine (external adds ride the separate clockjobs- ADD plane, which the clock 227// is read-only on) -- but the same reload-merge that saved the tsv from the 1785559048 clobber is kept 228// here: rows we have never seen are preserved with their own next_due, because A WRITER THAT 229// SERIALIZES ONLY ITS OWN MEMORY CANNOT COEXIST WITH ANY OTHER WRITER. -------------------------------- 230 231// load schedule state from the plane; same 4-col row grammar as the legacy file. returns #jobs. 232func clk_load_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 { 233 np[0] = 0 234 let data: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW) 235 let dn: i64 = sts_load(prefix, data, CLK_MAXJOBS*CLK_ROWW) 236 if dn <= 0 { return 0 } 237 var i: i64=0; var ls: i64=0; var n: i64=0 238 while i < dn { 239 if data[i] == (10 as u8) { 240 if i > ls { if n < CLK_MAXJOBS { 241 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls 242 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n) 243 var f: i64=0; var p: i64=0; var iv: i64=0; var nd: i64=0; var w: i64=0; var g: i64=0 244 while p < ll { 245 if line[p] == (9 as u8) { f = f + 1 } 246 else { 247 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } } 248 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } } 249 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) { 250 let dig: i64 = (line[p]-(48 as u8)) as i64 251 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig } 252 } } } } 253 } 254 p = p + 1 255 } 256 d[w] = 0 as u8; e[g] = 0 as u8 257 if w > 0 { if g > 0 { 258 intervals[n]=iv; next_due[n]=nd; n=n+1 259 } } 260 } } 261 ls = i + 1 262 } 263 i = i + 1 264 } 265 np[0] = n 266 return n 267} 268 269// persist schedule state to the plane (reload-merge first, then ONE whole-plane sts_seed commit). 270// Rows WE hold win (ours carry the advanced next_due); unknown rows are preserved verbatim. 271func clk_save_plane(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, n: i64) -> i64 { 272 var nn: i64 = n 273 let rnames: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW) 274 let rorgs: *u8 = sys_mmap(CLK_MAXJOBS*CLK_NAMEW) 275 let riv: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 276 let rnd: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 277 let rnp: *i64 = sys_mmap(8) as *i64 278 clk_load_plane(prefix, rnames, rorgs, riv, rnd, rnp) 279 var ri: i64 = 0 280 while ri < rnp[0] { 281 if clk_find(names, nn, clk_slot(rnames, ri)) < 0 { if nn < CLK_MAXJOBS { 282 var d2: *u8 = clk_slot(names, nn); let sm: *u8 = clk_slot(rnames, ri) 283 var a2: i64 = 0 284 while a2 < CLK_NAMEW-1 { if sm[a2]==(0 as u8){ d2[a2]=0 as u8; a2=CLK_NAMEW } else { d2[a2]=sm[a2]; a2=a2+1 } } 285 if a2 == CLK_NAMEW-1 { d2[a2]=0 as u8 } 286 var e2: *u8 = clk_slot(organs, nn); let so: *u8 = clk_slot(rorgs, ri) 287 var b2: i64 = 0 288 while b2 < CLK_NAMEW-1 { if so[b2]==(0 as u8){ e2[b2]=0 as u8; b2=CLK_NAMEW } else { e2[b2]=so[b2]; b2=b2+1 } } 289 if b2 == CLK_NAMEW-1 { e2[b2]=0 as u8 } 290 intervals[nn] = riv[ri]; next_due[nn] = rnd[ri]; nn = nn + 1 291 } } 292 ri = ri + 1 293 } 294 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW); var o: i64 = 0 295 var i: i64 = 0 296 while i < nn { 297 let nm: *u8 = clk_slot(names, i); var k: i64=0; while nm[k]!=(0 as u8){ buf[o]=nm[k]; o=o+1; k=k+1 } 298 buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, intervals[i]); buf[o]=9 as u8; o=o+1; o = clk_itoa(buf, o, next_due[i]); buf[o]=9 as u8; o=o+1 299 let og: *u8 = clk_slot(organs, i); var g: i64=0; while og[g]!=(0 as u8){ buf[o]=og[g]; o=o+1; g=g+1 } 300 buf[o]=10 as u8; o=o+1 301 i = i + 1 302 } 303 sts_seed(prefix, buf, o) 304 return nn 305} 306 307// THE ONE STATE LOADER for the live clock: plane first; if the plane is empty AND a legacy tsv 308// exists, load it (the one-time migration path) -- the caller's next clk_save_plane completes the 309// cutover. Returns 0=loaded-from-plane, 1=migrated-from-legacy, -1=nothing anywhere. 310func clk_load_state(prefix: *u8, legacy: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 { 311 if clk_load_plane(prefix, names, organs, intervals, next_due, np) > 0 { return 0 } 312 if clk_load(legacy, names, organs, intervals, next_due, np) > 0 { return 1 } 313 return 0 - 1 314} 315 316// MERGE new job declarations from an nx_store PLANE (off-tsv, additive) into the in-memory registry. 317// THE CLOBBER FIX: the clock is READ-ONLY on this plane -- external adds go via 318// `nx_store_put <plane> put <actor> <name> <interval> <organ>` (upsert-by-name, additive) and can NEVER be 319// clobbered by the clock's own (now clock-private) tsv save. Plane row cols (TAB): 0=name 1=interval(sec) 2=organ. 320// A job already present (by name) is skipped (idempotent -- re-merge is a no-op). New jobs arm at base_tick+interval. 321// Returns the number of NEW jobs added this call. 322func clk_merge_store(prefix: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64, base_tick: i64) -> i64 { 323 let buf: *u8 = sys_mmap(CLK_MAXJOBS*CLK_ROWW) 324 let dn: i64 = sts_load(prefix, buf, CLK_MAXJOBS*CLK_ROWW) 325 if dn <= 0 { return 0 } 326 var added: i64 = 0 327 var i: i64=0; var ls: i64=0 328 while i < dn { 329 if buf[i] == (10 as u8) { 330 if i > ls { 331 let line: *u8 = ((buf as i64)+ls) as *u8; let ll: i64 = i-ls 332 let nm: *u8 = sys_mmap(CLK_NAMEW); let og: *u8 = sys_mmap(CLK_NAMEW) 333 var f: i64=0; var p: i64=0; var iv: i64=0; var w: i64=0; var g: i64=0 334 while p < ll { 335 if line[p]==(9 as u8) { f=f+1 } 336 else { 337 if f==0 { if w<CLK_NAMEW-1 { nm[w]=line[p]; w=w+1 } } 338 else { if f==1 { if line[p]>=(48 as u8) { if line[p]<=(57 as u8) { iv=iv*10+((line[p]-(48 as u8)) as i64) } } } 339 else { if f==2 { if g<CLK_NAMEW-1 { og[g]=line[p]; g=g+1 } } } } 340 } 341 p=p+1 342 } 343 nm[w]=0 as u8; og[g]=0 as u8 344 // MALFORMED-ROW GUARD (2026-08-03, debt 1784604739): a row lacking a name or an organ 345 // (e.g. a put that dropped an arg) must be SEEN, never silently skipped -- but never 346 // registered either (a nameless job / organless exec is garbage in the dispatcher). 347 if w == 0 { sts_werr("clk_merge_store: MALFORMED plane row (empty name) SKIPPED\n" as *u8) } 348 if w > 0 { if g == 0 { sts_werr("clk_merge_store: MALFORMED plane row (no organ) SKIPPED\n" as *u8) } } 349 if w > 0 { if g > 0 { 350 let n: i64 = np[0] 351 if clk_find(names, n, nm) < 0 { if n < CLK_MAXJOBS { 352 var d: *u8=clk_slot(names,n); var k: i64=0; while k<CLK_NAMEW-1 { if nm[k]==(0 as u8){d[k]=0 as u8;k=CLK_NAMEW} else {d[k]=nm[k];k=k+1} } if k==CLK_NAMEW-1 {d[k]=0 as u8} 353 var e: *u8=clk_slot(organs,n); var j: i64=0; while j<CLK_NAMEW-1 { if og[j]==(0 as u8){e[j]=0 as u8;j=CLK_NAMEW} else {e[j]=og[j];j=j+1} } if j==CLK_NAMEW-1 {e[j]=0 as u8} 354 if iv<1 { iv=1 } 355 intervals[n]=iv; next_due[n]=base_tick+iv; np[0]=n+1; added=added+1 356 } } 357 } } 358 } 359 ls=i+1 360 } 361 i=i+1 362 } 363 return added 364} 365 366// ============================================================================================ 367// EDF / WALL-CLOCK SCHEDULING (2026-08-04, debt 1785872141 -- operator: "get the clock to sota") 368// 369// THE MEASURED DEFECT clk_run_tickless has BY CONSTRUCTION: its logical tick T advances ONLY by the 370// sleep amount (T = minnd), while REAL time also advances by however long the dispatched children 371// took (clk_dispatch_run forks and sys_wait4s EVERY job, serially, inside the beat). So logical time 372// drifts behind wall-clock time in proportion to dispatch load, and EVERY job's period silently 373// STRETCHES: measured 2026-08-04, evidencebeat (interval 21600 = "6 hours") fired ONCE in 21 HOURS 374// while the clock was demonstrably alive (tick current, window-end a=30 b=132). Even 60s netobs 375// slipped to 243s. The fast jobs pay the drift too, but a 6h job pays it 360x over. 376// u2605u2605u2605u2605u2605u2605 A SCHEDULER THAT ADVANCES ITS OWN CLOCK BY WHAT IT SLEPT -- NOT BY WHAT ELAPSED -- 377// MEASURES ITS OWN IDLENESS AND CALLS IT TIME. Every deadline it derives is then a lie under load. 378// 379// THE SOTA SHAPE (what real schedulers do; cron/systemd-timers/EDF literature all agree): 380// 1. deadlines are ABSOLUTE WALL-CLOCK instants, never a self-advanced counter 381// 2. re-read the clock AFTER every dispatch, so job runtime cannot be lost 382// 3. dispatch EARLIEST-DEADLINE-FIRST so the most-overdue job goes first (EDF is optimal for 383// meeting deadlines on one resource -- and it is exactly what stops slow-job starvation) 384// 4. catch-up without runaway: re-arm past now by WHOLE intervals, and REPORT missed periods 385// instead of pretending they happened 386// The decision math is factored into PURE functions below precisely so a gate can prove a 6-hour 387// period behaves correctly in MILLISECONDS with fabricated clock values -- a scheduler you can only 388// test by waiting 6 hours is a scheduler nobody tests. 389 390// deadlines below this are LEGACY LOGICAL TICKS (the old counter ran ~3e6; epochs are ~1.78e9), so the 391// one-time migration is unambiguous and needs no flag day. NEVER compare an epoch against a tick. 392const CLK_EPOCH_FLOOR: i64 = 1000000000 393 394// PURE: index of the overdue job with the EARLIEST deadline (EDF), or -1 if nothing is due. 395// Ties break toward the lower index = stable, so a tie can never rotate two jobs into each other's slot. 396func clk_edf_pick(deadlines: *i64, n: i64, now: i64) -> i64 { 397 var best: i64 = 0 - 1 398 var bestd: i64 = 0 399 var i: i64 = 0 400 while i < n { 401 if deadlines[i] <= now { 402 if best < 0 { best = i; bestd = deadlines[i] } 403 else { if deadlines[i] < bestd { best = i; bestd = deadlines[i] } } 404 } 405 i = i + 1 406 } 407 return best 408} 409 410// PURE: soonest deadline across all jobs (what a tickless sleeper must sleep until). -1 if n==0. 411func clk_edf_next(deadlines: *i64, n: i64) -> i64 { 412 if n <= 0 { return 0 - 1 } 413 var m: i64 = deadlines[0] 414 var i: i64 = 1 415 while i < n { if deadlines[i] < m { m = deadlines[i] } i = i + 1 } 416 return m 417} 418 419// PURE: how many whole periods job i has MISSED at `now` (0 = on time / early). This is the honest 420// starvation measure -- a job 3 periods late is a 3, not a "fired". 421func clk_edf_missed(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 { 422 if deadlines[i] > now { return 0 } 423 var iv: i64 = intervals[i] 424 if iv < 1 { iv = 1 } 425 return (now - deadlines[i]) / iv 426} 427 428// PURE: re-arm job i past `now` by WHOLE intervals (catch-up safe: never fires twice for one missed 429// window, never drifts off-phase). Returns the periods skipped so the caller can REPORT them. 430func clk_edf_rearm(deadlines: *i64, intervals: *i64, i: i64, now: i64) -> i64 { 431 var iv: i64 = intervals[i] 432 if iv < 1 { iv = 1 } 433 var skipped: i64 = 0 434 while deadlines[i] <= now { deadlines[i] = deadlines[i] + iv; skipped = skipped + 1 } 435 if skipped > 0 { skipped = skipped - 1 } // the first advance is the fire itself, not a miss 436 return skipped 437} 438 439// PURE: one-time migration of legacy logical-tick deadlines to wall-clock instants. A tick value can 440// never be a valid epoch, so this is exact. Jobs arm at now+interval (their author's intent) rather 441// than firing a thundering herd at now. Returns how many rows were converted. 442func clk_edf_migrate(deadlines: *i64, intervals: *i64, n: i64, now: i64) -> i64 { 443 var c: i64 = 0 444 var i: i64 = 0 445 while i < n { 446 if deadlines[i] < CLK_EPOCH_FLOOR { 447 var iv: i64 = intervals[i] 448 if iv < 1 { iv = 1 } 449 deadlines[i] = now + iv 450 c = c + 1 451 } 452 i = i + 1 453 } 454 return c 455} 456 457// THE EDF WINDOW: wall-clock anchored, EDF-ordered, starvation-reporting. Runs until `maxdispatch` 458// jobs have been dispatched or `budget_secs` of REAL time is gone, sleeping only when nothing is due. 459// out[0]=dispatched out[1]=slept_secs out[2]=final_now out[3]=starved_jobs out[4]=max_lateness_secs 460// out[5]=exec_secs (real time inside children -- the number the old design silently threw away). 461func clk_run_edf(organs: *u8, intervals: *i64, deadlines: *i64, n: i64, maxdispatch: i64, budget_secs: i64, tick_ms: i64, out: *i64) -> i64 { 462 let fired: *i64 = sys_mmap(CLK_MAXJOBS*8) as *i64 463 let t0: i64 = sys_now_realtime_sec() 464 var disp: i64 = 0 465 var slept: i64 = 0 466 var starved: i64 = 0 467 var maxlate: i64 = 0 468 var execs: i64 = 0 469 var run: i64 = 1 470 while run == 1 { 471 // RE-READ THE CLOCK EVERY ITERATION -- this single line is the fix: child runtime is now 472 // observed, not assumed away. 473 let now: i64 = sys_now_realtime_sec() 474 if now - t0 >= budget_secs { run = 0 } 475 if disp >= maxdispatch { run = 0 } 476 if run == 1 { 477 let k: i64 = clk_edf_pick(deadlines, n, now) 478 if k >= 0 { 479 let missed: i64 = clk_edf_missed(deadlines, intervals, k, now) 480 let late: i64 = now - deadlines[k] 481 if late > maxlate { maxlate = late } 482 if missed > 0 { starved = starved + 1 } 483 clk_edf_rearm(deadlines, intervals, k, now) 484 let e0: i64 = now 485 let ran: i64 = clk_dispatch_one(organs, k) 486 let e1: i64 = sys_now_realtime_sec() 487 execs = execs + (e1 - e0) 488 if ran == 1 { disp = disp + 1; fired[k] = 1 } 489 } else { 490 let nd: i64 = clk_edf_next(deadlines, n) 491 if nd < 0 { run = 0 } else { 492 var wait: i64 = nd - now 493 if wait < 1 { wait = 1 } 494 let left: i64 = budget_secs - (now - t0) 495 if wait > left { wait = left } 496 if wait < 1 { run = 0 } else { 497 let ts: *i64 = sys_mmap(16) as *i64 498 ts[0] = wait 499 ts[1] = 0 500 __syscall(35, ts as i64, 0, 0, 0, 0, 0) 501 slept = slept + wait 502 } 503 } 504 } 505 } 506 } 507 out[0] = disp; out[1] = slept; out[2] = sys_now_realtime_sec() 508 out[3] = starved; out[4] = maxlate; out[5] = execs 509 return disp 510} 511 512// dispatch EXACTLY ONE job by index (the EDF loop picks the victim; this just runs it). Split out of 513// clk_dispatch_run so ordering policy and exec mechanics are separable -- and so the EDF loop can 514// re-read the clock between jobs. Returns 1 if the organ really ran, 0 if exec failed (127). 515func clk_dispatch_one(organs: *u8, i: i64) -> i64 { 516 let path: *u8 = clk_slot(organs, i) 517 __syscall(90, path as i64, 0x1ed, 0, 0, 0, 0) // chmod +x: recv-shipped organs aren't reliably executable 518 let pid: i64 = sys_fork() 519 if pid == 0 { 520 let argv: *i64 = sys_mmap(32) as *i64; argv[0] = path as i64; argv[1] = 0 521 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 522 sys_execve(path, argv, envp) 523 sys_exit(127) 524 } 525 let st: *i64 = sys_mmap(16) as *i64; st[0] = 0 526 sys_wait4(pid, st, 0) 527 let raw: i64 = st[0]; let sig: i64 = raw & 0x7f; let code: i64 = (raw >> 8) & 0xff 528 if sig != 0 { return 1 } 529 if code != 127 { return 1 } 530 return 0 531} 532 533// load the registry into the arrays. returns #jobs (also written to np[0]). 534func clk_load(path: *u8, names: *u8, organs: *u8, intervals: *i64, next_due: *i64, np: *i64) -> i64 { 535 np[0] = 0 536 let lenp: *i64 = sys_mmap(8) as *i64 537 let data: *u8 = sys_read_file(path, lenp) 538 if (data as i64) == 0 { return 0 } 539 let dn: i64 = lenp[0]; var i: i64=0; var ls: i64=0; var n: i64=0 540 while i < dn { 541 if data[i] == (10 as u8) { 542 if i > ls { if n < CLK_MAXJOBS { 543 let line: *u8 = ((data as i64)+ls) as *u8; let ll: i64 = i-ls 544 let d: *u8 = clk_slot(names, n); let e: *u8 = clk_slot(organs, n) 545 var f: i64=0; var p: i64=0; var iv: i64=0; var nd: i64=0; var w: i64=0; var g: i64=0 // f: 0=name 1=interval 2=next_due 3=organ 546 while p < ll { 547 if line[p] == (9 as u8) { f = f + 1 } 548 else { 549 if f == 0 { if w < CLK_NAMEW-1 { d[w]=line[p]; w=w+1 } } 550 else { if f == 3 { if g < CLK_NAMEW-1 { e[g]=line[p]; g=g+1 } } 551 else { if line[p] >= (48 as u8) { if line[p] <= (57 as u8) { 552 let dig: i64 = (line[p]-(48 as u8)) as i64 553 if f == 1 { iv = iv*10 + dig } else { nd = nd*10 + dig } 554 } } } } 555 } 556 p = p + 1 557 } 558 d[w] = 0 as u8; e[g] = 0 as u8 559 intervals[n]=iv; next_due[n]=nd; n=n+1 560 } } 561 ls = i + 1 562 } 563 i = i + 1 564 } 565 np[0] = n 566 return n 567}