code wiki / (root) / nx_memfloor.nx

nx_memfloor.nx source

↩ module page · 138 lines · 6430 B

1// nx_memfloor.nx -- ADMISSION CONTROL FOR A LARGE ALLOCATION, as a shared library. 2// 3// WHY THIS EXISTS (2026-07-30 incident, second of its class): 4// nx_skullsdf.elf held 27.7 GB of a 36 GB host inside 2m48s, drove swap to 98% and load to 41, and 5// the mgmt API began refusing builds ("host below the memory floor"). Its grid resolution comes 6// straight from argv with no upper bound, and the allocation is O(g^3): G=1500 -> 1501^3*8 = 27.06 GB, 7// which is exactly what was observed. seq1547 was the same shape in nx_ssdf (28 GB in 227s), and that 8// one took mgmt, the tools daemon and sshd down with it. 9// 10// THE POINT: the ecosystem ALREADY knew how to do this. nx_build_admit refuses to fork the compiler 11// below a memory floor, and nx_headroom exposes the floor + load ceiling as a CLI. Neither was 12// reachable from inside an organ about to allocate, so every heavy organ re-decided the question -- 13// and the ones that never asked are the ones that ate the host. This is the adoption gap, not a 14// missing primitive: MIGRATE THE CHOKEPOINT, NOT THE LEAF. 15// 16// CONTRACT: call mf_admit_bytes() with the EXACT byte count you are about to map, BEFORE you map it. 17// Returns 1 = admit, 0 = refuse. On refuse the caller must fail fast and say so -- never allocate 18// anyway, and never silently shrink the request (a caller that asked for a 1500-cell grid and 19// quietly got 160 would emit a wrong answer, which is worse than an honest refusal). 20// 21// Lives in runtime/ so _hdl_build/ organs can import it; the reverse never resolves. 22// license_tier: ORIGINAL 23import "nx_syscalls.nx" 24 25// Reserve left for the control plane + page cache. Derived, not taste: mgmt + tools + supervisor + 26// sshd measured ~1.1 GB resident together, and nx_build_admit already refuses below 1024 MB. 2048 MB 27// keeps a build admissible AFTER the guarded allocation lands, so guarding one organ cannot starve 28// the next one. Override per-host via knowledge/memfloor.conf (rule 11/17: config, not code). 29const MF_DEF_FLOOR_MB: i64 = 2048 30const MF_CONF: *u8 = "knowledge/memfloor.conf" as *u8 31const MF_MEMINFO: *u8 = "/proc/meminfo" as *u8 32const MF_READCAP: i64 = 8192 33const MF_KB_PER_MB: i64 = 1024 34 35func mf_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 36func mf_puts(s: *u8) -> i64 { let n: i64 = mf_slen(s); sys_write(2, s, n); return 0 } 37func mf_putn(v: i64) -> i64 { 38 let b: *u8 = sys_mmap(32) 39 var x: i64 = v 40 var ng: i64 = 0 41 if x < 0 { ng = 1; x = 0 - x } 42 var i: i64 = 31 43 b[i] = 0 as u8 44 var go: i64 = 1 45 while go == 1 { 46 go = 0 47 i = i - 1 48 b[i] = ((x - ((x / 10) * 10)) + 48) as u8 49 x = x / 10 50 if x > 0 { go = 1 } 51 } 52 if ng == 1 { i = i - 1; b[i] = 45 as u8 } 53 sys_write(2, ((b as i64) + i) as *u8, 31 - i) 54 return 0 55} 56 57func mf_read(path: *u8, buf: *u8, cap: i64) -> i64 { 58 let fd: i64 = sys_openat_rd(path) 59 if fd < 0 { return 0 - 1 } 60 var tot: i64 = 0 61 var n: i64 = sys_read(fd, buf, cap) 62 while n > 0 { tot = tot + n; if tot >= cap { n = 0 } else { n = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot) } } 63 sys_close(fd) 64 return tot 65} 66 67// first integer following <key> (meminfo shape: "MemAvailable: 28016123 kB"). -1 if absent. 68func mf_num_after(buf: *u8, n: i64, key: *u8) -> i64 { 69 let kn: i64 = mf_slen(key) 70 var i: i64 = 0 71 while i + kn <= n { 72 var j: i64 = 0 73 var ok: i64 = 1 74 while j < kn { if buf[i+j] != key[j] { ok = 0; j = kn } else { j = j + 1 } } 75 if ok == 1 { 76 var p: i64 = i + kn 77 var sk: i64 = 1 78 while sk == 1 { sk = 0; if p < n { if buf[p] == (32 as u8) { p = p + 1; sk = 1 } else { if buf[p] == (9 as u8) { p = p + 1; sk = 1 } } } } 79 var v: i64 = 0 80 var any: i64 = 0 81 var go: i64 = 1 82 while go == 1 { 83 go = 0 84 if p < n { 85 let c: i64 = buf[p] as i64 86 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; p = p + 1; go = 1 } } 87 } 88 } 89 if any == 1 { return v } 90 return 0 - 1 91 } 92 i = i + 1 93 } 94 return 0 - 1 95} 96 97// MemAvailable in kB, or -1 when /proc/meminfo is unreadable. 98func mf_avail_kb() -> i64 { 99 let buf: *u8 = sys_mmap(MF_READCAP) 100 let n: i64 = mf_read(MF_MEMINFO, buf, MF_READCAP - 1) 101 if n <= 0 { return 0 - 1 } 102 return mf_num_after(buf, n, "MemAvailable:" as *u8) 103} 104 105// Configured floor in MB (knowledge/memfloor.conf, single integer), else the compiled default. 106func mf_floor_mb() -> i64 { 107 let buf: *u8 = sys_mmap(MF_READCAP) 108 let n: i64 = mf_read(MF_CONF, buf, MF_READCAP - 1) 109 if n <= 0 { return MF_DEF_FLOOR_MB } 110 let v: i64 = mf_num_after(buf, n, "floor_mb=" as *u8) 111 if v <= 0 { return MF_DEF_FLOOR_MB } 112 return v 113} 114 115// THE GUARD. 1 = admit, 0 = refuse. `what` names the allocation in the refusal so an operator reading 116// the log knows which knob to turn. 117// 118// FAIL-OPEN ON AN UNREADABLE /proc IS DELIBERATE: if we cannot measure, refusing every allocation would 119// brick every heavy organ on a host whose /proc is restricted. An unmeasurable host is the pre-existing 120// behaviour; a measurable one gets the guard. The refusal is the new capability, not the reading. 121func mf_admit_bytes(what: *u8, need_bytes: i64, floor_mb: i64) -> i64 { 122 if need_bytes <= 0 { return 1 } 123 let avail_kb: i64 = mf_avail_kb() 124 if avail_kb < 0 { return 1 } 125 let need_kb: i64 = need_bytes / MF_KB_PER_MB 126 let floor_kb: i64 = floor_mb * MF_KB_PER_MB 127 if need_kb + floor_kb <= avail_kb { return 1 } 128 mf_puts("{\x22error\x22:\x22memfloor-refused\x22,\x22what\x22:\x22" as *u8) 129 mf_puts(what) 130 mf_puts("\x22,\x22need_mb\x22:" as *u8); mf_putn(need_kb / MF_KB_PER_MB) 131 mf_puts(",\x22available_mb\x22:" as *u8); mf_putn(avail_kb / MF_KB_PER_MB) 132 mf_puts(",\x22floor_mb\x22:" as *u8); mf_putn(floor_mb) 133 mf_puts(",\x22verdict\x22:\x22NOTHING allocated; this request would have driven the host into swap and OOM-reaped the control plane. Lower the size argument, or raise floor_mb in knowledge/memfloor.conf if you know the host can take it.\x22}\n" as *u8) 134 return 0 135} 136 137// Convenience: the common case uses the configured floor. 138func mf_admit(what: *u8, need_bytes: i64) -> i64 { return mf_admit_bytes(what, need_bytes, mf_floor_mb()) }