code wiki / _hdl_build / nx_poolwidth_lib.nx
nx_poolwidth_lib.nx source
↩ module page · 184 lines · 9453 B
1// nx_poolwidth_lib.nx -- ONE headroom-derived width governor for every pool in the ecosystem (seq1402).
2//
3// THE DEFECT, MEASURED 2026-07-30: nx_torrent_get.nx:717 carries `let MAXP: i64 = 32` -- a hardcoded
4// literal (rule-11) -- and refills every free slot every 2s. That is 16 forks/sec PER INSTANCE. The box
5// measured **143 forks/sec at load 11.72 on 8 threads**, which is ~9 concurrent instances. NOBODY CHOSE
6// 143. It is 32 x N, and N is whatever the operator queued.
7// ★★LAW: A PER-INSTANCE LIMIT IS NOT A LIMIT. N instances multiply it and nobody ever picks N.
8//
9// ★WHAT THIS IS *NOT*: a backoff. nx_torrent_get.nx:711-716 documents constant connection cycling as the
10// BitTorrent throughput strategy (continuous optimistic-unchokes vs fast-burst-then-freeze). Throttling
11// the CYCLING would attack the feature to flatten a graph -- operator rule 25, build intelligence, never
12// strip it. This governs only the WIDTH; inside its budget the pool stays as aggressive as it ever was.
13//
14// The same law already bit the crawl pool (seq1288 saturation-wedge: "pool width must be HEADROOM-DERIVED").
15// Two lanes, one defect => ONE primitive, not two fixes (rule 15).
16// license_tier: ORIGINAL Pure. No hw writes (Rule 26).
17import "nx_syscalls.nx"
18import "nx_hw.nx"
19import "nx_ctxtop_lib.nx"
20import "nx_proc_ctl.nx"
21
22const PW_DIRBUF: i64 = 65536
23const PW_PATHBUF: i64 = 256
24const PW_CMDBUF: i64 = 8192
25const PW_LOADBUF: i64 = 128
26
27// Live /proc/loadavg in centi-load, or -1. Reuses ct_load_centi (rule 15: the parse is already proven
28// by nx_ctxtop_gate T9/T10/T11 against the REAL measured line -- a second copy would be a second bug).
29func pw_load_centi() -> i64 {
30 let b: *u8 = sys_mmap(PW_LOADBUF)
31 let fd: i64 = sys_openat_rd("/proc/loadavg" as *u8)
32 if fd < 0 { sys_munmap(b, PW_LOADBUF); return 0 - 1 }
33 let n: i64 = sys_read(fd, b, PW_LOADBUF - 1)
34 sys_close(fd)
35 if n <= 0 { sys_munmap(b, PW_LOADBUF); return 0 - 1 }
36 let v: i64 = ct_load_centi(b, n)
37 sys_munmap(b, PW_LOADBUF)
38 return v
39}
40
41// How many processes share this budget right now? Counts /proc cmdlines containing `needle`.
42// THIS IS WHAT MAKES THE LIMIT A LIMIT: a per-instance cap cannot bound aggregate load by construction
43// (seq1402 -- 32 x 9 instances = 288 slots against a 32 budget, and nobody ever picks N).
44// Cost is ONE /proc walk, paid ONCE at pool construction -- never per request, never in a loop.
45// Returns at least 1 (this process itself counts), so a caller can always divide by it safely.
46func pw_count_procs(needle: *u8) -> i64 {
47 var nn: i64 = 0
48 while needle[nn] != (0 as u8) { nn = nn + 1 }
49 if nn == 0 { return 1 }
50 let dbuf: *u8 = sys_mmap(PW_DIRBUF)
51 let path: *u8 = sys_mmap(PW_PATHBUF)
52 let cbuf: *u8 = sys_mmap(PW_CMDBUF)
53 var cnt: i64 = 0
54 let fd: i64 = sys_openat_rd("/proc" as *u8)
55 if fd >= 0 {
56 var run: i64 = 1
57 while run == 1 {
58 let dn: i64 = sys_getdents64(fd, dbuf, PW_DIRBUF)
59 if dn <= 0 { run = 0 } else {
60 var off: i64 = 0
61 while off < dn {
62 let rec: *u8 = ((dbuf as i64) + off) as *u8
63 let reclen: i64 = dirent_reclen(rec)
64 if reclen <= 0 { off = dn } else {
65 let nm: *u8 = dirent_name(rec)
66 if nm[0] >= (48 as u8) { if nm[0] <= (57 as u8) {
67 var p: i64 = 0
68 let pre: *u8 = "/proc/" as *u8
69 var a: i64 = 0
70 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
71 a = 0
72 while nm[a] != (0 as u8) { path[p] = nm[a]; p = p + 1; a = a + 1 }
73 let suf: *u8 = "/cmdline" as *u8
74 a = 0
75 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
76 path[p] = 0 as u8
77 let cf: i64 = sys_openat_rd(path)
78 if cf >= 0 {
79 let cn: i64 = sys_read(cf, cbuf, PW_CMDBUF - 1)
80 sys_close(cf)
81 if cn > 0 {
82 var i: i64 = 0
83 var found: i64 = 0
84 while i + nn <= cn {
85 if found == 0 {
86 var j: i64 = 0
87 var m: i64 = 1
88 while j < nn { if cbuf[i + j] != needle[j] { m = 0; j = nn } else { j = j + 1 } }
89 if m == 1 { found = 1 }
90 }
91 i = i + 1
92 }
93 if found == 1 { cnt = cnt + 1 }
94 }
95 }
96 } }
97 off = off + reclen
98 }
99 }
100 }
101 }
102 sys_close(fd)
103 }
104 sys_munmap(dbuf, PW_DIRBUF)
105 sys_munmap(path, PW_PATHBUF)
106 sys_munmap(cbuf, PW_CMDBUF)
107 if cnt < 1 { return 1 }
108 return cnt
109}
110
111const PW_UNKNOWN_LOAD: i64 = 0 - 1
112
113// ---- FIRST ADOPTER of nx_hw_cpu_count (seq1410) ----
114// CENSUS 2026-07-30: nx_hw_cpu_count had FIVE matches in 19,823 files -- 1 definition, 3 self-references,
115// 1 test comment, and ZERO external callers. The hardware-adaptivity primitive was 100pct DARK while
116// nx_torrent_get hardcoded MAXP=32. This function exists so the budget is MEASURED FROM THE MACHINE WE
117// ARE ACTUALLY ON, which is the operator's standing law (feedback-dynamic-hw-sizing-no-hardcoded-thread-counts):
118// Nishi on the NAS, the west server, this laptop or any future box must each use THAT box's capability.
119//
120// ★WHY sched_getaffinity AND NOT /proc/cpuinfo: it returns the CPUs THIS PROCESS MAY RUN ON, so it is
121// correct under cgroups / taskset / containers, where /proc/cpuinfo reports host cores the process can
122// never touch. Counting cpuinfo would size a 64-thread pool inside a --cpus=2 container.
123//
124// slots_per_cpu is POLICY (config-driven, rule 11); the CPU COUNT is MEASURED, never assumed.
125// Note slots_per_cpu=4 reproduces today's MAXP=32 on this 8-thread NAS -- behaviour-preserving HERE
126// while scaling correctly everywhere else, which is what makes this migration safe to land.
127func pw_budget_from_hw(slots_per_cpu: i64, floor: i64) -> i64 {
128 if floor < 1 { return 0 - 1 }
129 if slots_per_cpu < 1 { return floor }
130 let n: i64 = nx_hw_cpu_count()
131 if n < 1 { return floor }
132 let b: i64 = n * slots_per_cpu
133 if b < floor { return floor }
134 return b
135}
136
137// Derive THIS instance's pool width from an ecosystem-wide budget.
138// total_budget : concurrent slots the WHOLE ecosystem may hold (config; headroom-derived upstream)
139// n_instances : how many peers share it right now (>=1; 0/negative treated as 1, never as divide-by-zero)
140// load_centi : live /proc/loadavg x100, or -1 if unreadable
141// max_centi : the load ceiling above which width is squeezed
142// wmin/wmax : hard clamps -- wmin keeps a pool USEFUL (a 0-width pool is a dead feature, not a
143// throttle), wmax keeps one instance from eating the whole budget when it is alone.
144//
145// SQUEEZE IS PROPORTIONAL, not a cliff: above the ceiling, width scales by max/load, so a box at 2x the
146// ceiling gets half the slots. A step function would oscillate (all-or-nothing pools thrash between
147// saturation and idle); proportional control degrades smoothly.
148// FAIL-SAFE: an unreadable load yields wmin. Not knowing the pressure is not permission to grab slots.
149func pw_width(total_budget: i64, n_instances: i64, load_centi: i64,
150 max_centi: i64, wmin: i64, wmax: i64) -> i64 {
151 if wmin < 1 { return 0 - 1 }
152 if wmax < wmin { return 0 - 1 }
153 if total_budget < 1 { return wmin }
154 if max_centi < 1 { return wmin }
155 if load_centi == PW_UNKNOWN_LOAD { return wmin }
156 if load_centi < 0 { return wmin }
157 var n: i64 = n_instances
158 if n < 1 { n = 1 }
159 var w: i64 = total_budget / n
160 if load_centi > max_centi { w = (w * max_centi) / load_centi }
161 if w < wmin { w = wmin }
162 if w > wmax { w = wmax }
163 return w
164}
165
166// The aggregate this policy actually admits -- the number that was never bounded before.
167// A caller can assert this against its budget; per-instance reasoning cannot (that IS the defect).
168func pw_aggregate(width: i64, n_instances: i64) -> i64 {
169 if width < 0 { return 0 - 1 }
170 var n: i64 = n_instances
171 if n < 1 { n = 1 }
172 return width * n
173}
174
175// Does this policy hold the ecosystem inside its budget? 1 = yes, 0 = OVERCOMMITTED.
176// NOTE the wmin floor can legitimately overshoot: keeping N pools USABLE may exceed the budget, and that
177// is a deliberate, VISIBLE trade (a 0-width pool is a broken feature). This predicate exposes it rather
178// than letting it hide -- the caller decides whether to admit fewer INSTANCES instead of thinner pools.
179func pw_within_budget(width: i64, n_instances: i64, total_budget: i64) -> i64 {
180 let agg: i64 = pw_aggregate(width, n_instances)
181 if agg < 0 { return 0 }
182 if agg > total_budget { return 0 }
183 return 1
184}