code wiki / _hdl_build / nx_torrent_backoff_gate.nx
nx_torrent_backoff_gate.nx source
↩ module page · 179 lines · 9588 B
1// nx_torrent_backoff_gate.nx -- does the seeder stop burning a core when nothing it accepts can be served?
2//
3// ★THE DEFECT THIS PROVES (measured live 2026-07-30). `nx_torrent_seed` held ~71.6% of a core with the
4// signature of a hot accept loop. Two independent faults, one symptom:
5// (1) BOTH accept loops read `if afd >= 0 { ... }` with NO else branch. A persistently failing
6// sys_accept (EMFILE / ENFILE / ECONNABORTED) therefore re-looped with ZERO delay -- an
7// unconditional spin at whatever rate the CPU allows.
8// (2) The forked child called `sys_exit(0)` UNCONDITIONALLY, so the parent could not observe that
9// `ts_serve_peer_registry` was returning -2 every single time. /tmp/seed.log showed
10// `served blocks=-2` repeating without end while the parent forked again immediately, forever.
11// ★A LOOP THAT CANNOT SEE ITS OWN FAILURES CANNOT BACK OFF FROM THEM. The exit code was the missing wire.
12//
13// ★WHY A PURE FUNCTION IS THE UNIT UNDER TEST. "every peer handshake fails" is not a host state you can
14// summon on a shared box, so a policy welded into an accept loop beside its own syscalls is untestable in
15// practice -- which is exactly how it survived. Same discipline as `hc_keep_delay` and `ba_verdict`.
16//
17// ★THE TOOTH THAT MATTERS IS T6, the NEG-CONTROL: the pre-fix policy is kept alive as
18// `ts_backoff_ms_old` and asserted to return 0 on the very inputs where the fix backs off. If someone
19// reverts the fix, T2/T3/T4 go green against a spinning loop unless a tooth pins the OLD behaviour to the
20// OLD answer. A gate that cannot fail on the unfixed code proves nothing about the fixed code.
21//
22// usage: nx_torrent_backoff_gate (CWD = the store root)
23// exit 0 = GREEN · 1 = RED
24// license_tier: ORIGINAL expect_exit: 0
25import "nx_syscalls.nx"
26import "nx_torrent_seed.nx"
27import "nx_artifact_root.nx"
28import "nx_gate_verdict.nx"
29
30const TB_SRC: *u8 = "runtime/nx_torrent_seed.nx"
31const TB_CAP: i64 = 2097152
32
33func tb_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
34func tb_p(s: *u8) -> i64 { sys_write(1, s, tb_len(s)); return 0 }
35func tb_pn(v: i64) -> i64 {
36 let t: *u8=sys_mmap(32); var m: i64=v; var k: i64=0
37 if m<0 { sys_write(1,"-" as *u8,1); m=0-m }
38 if m==0 { t[0]=48 as u8; k=1 }
39 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
40 let o: *u8=sys_mmap(32); var i: i64=0
41 while i<k { o[i]=t[k-1-i]; i=i+1 }
42 sys_write(1,o,k); return 0
43}
44func tb_ck(name: *u8, c: i64) -> i64 {
45 if c==1 { tb_p(" PASS " as *u8) } else { tb_p(" FAIL " as *u8) }
46 tb_p(name); tb_p("\n" as *u8); return c
47}
48func tb_read(path: *u8, buf: *u8) -> i64 {
49 let fd: i64 = sys_openat_rd(path)
50 if fd < 0 { return 0 - 1 }
51 var tot: i64 = 0
52 var go: i64 = 1
53 while go == 1 {
54 let rem: i64 = TB_CAP - tot
55 if rem <= 0 { go = 0 }
56 if go == 1 {
57 let r: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, rem)
58 if r <= 0 { go = 0 } else { tot = tot + r }
59 }
60 }
61 sys_close(fd)
62 return tot
63}
64func tb_find(buf: *u8, n: i64, pat: *u8) -> i64 {
65 let pl: i64 = tb_len(pat)
66 if pl <= 0 { return 0 }
67 var i: i64 = 0
68 while i + pl <= n {
69 var k: i64 = 0
70 var hit: i64 = 1
71 while k < pl { if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
72 if hit == 1 { return 1 }
73 i = i + 1
74 }
75 return 0
76}
77
78func main() -> i64 {
79 var pass: i64 = 0
80 var total: i64 = 0
81 tb_p("=== nx_torrent_backoff_gate -- does the seeder back off when every serve is failing? ===\n" as *u8)
82
83 // T1: healthy is FULL SPEED. A backoff that taxes the working case would be traded away in a week.
84 var t1: i64 = 0
85 if ts_backoff_ms(0) == 0 { t1 = 1 }
86 pass = pass + tb_ck("T1 zero failures -> 0ms (a healthy seeder is never slowed)" as *u8, t1); total = total + 1
87
88 // T2: a burst of aborts is TOLERATED. Real peers drop mid-handshake; punishing that would be a
89 // regression dressed as a fix.
90 var t2: i64 = 0
91 if ts_backoff_ms(TS_BK_TOL - 1) == 0 { t2 = 1 }
92 pass = pass + tb_ck("T2 7 consecutive failures -> still 0ms (real peers abort; tolerate the burst)" as *u8, t2); total = total + 1
93
94 // T3: the fix -- sustained total failure engages the brake.
95 var t3: i64 = 0
96 if ts_backoff_ms(TS_BK_TOL) == TS_BK_D1 { t3 = 1 }
97 pass = pass + tb_ck("T3 8 consecutive failures -> 50ms (the spin is broken)" as *u8, t3); total = total + 1
98
99 // T4/T5: it ESCALATES, and it CAPS. Never unbounded -- a seeder that sleeps for an hour is dead.
100 var t4: i64 = 0
101 if ts_backoff_ms(TS_BK_N2) == TS_BK_D2 { t4 = 1 }
102 pass = pass + tb_ck("T4 64 failures -> 250ms (escalates with severity)" as *u8, t4); total = total + 1
103 var t5: i64 = 0
104 if ts_backoff_ms(TS_BK_N3) == TS_BK_D3 { if ts_backoff_ms(1000000) == TS_BK_D3 { t5 = 1 } }
105 pass = pass + tb_ck("T5 512 and 1,000,000 failures -> both 1000ms (CAPPED; still a live seeder)" as *u8, t5); total = total + 1
106
107 // T6 ★NEG-CONTROL / LIAR-KILLER: the OLD policy returns 0 on the SAME inputs where the new one backs
108 // off. This is the tooth that fails if the fix is reverted.
109 var t6: i64 = 0
110 if ts_backoff_ms_old(TS_BK_TOL) == 0 { if ts_backoff_ms_old(TS_BK_N3) == 0 { t6 = 1 } }
111 pass = pass + tb_ck("T6 NEG-CONTROL: pre-fix policy returns 0ms at 8 AND 512 failures (proves the spin was real)" as *u8, t6); total = total + 1
112
113 // T7 ★MONOTONIC: delay must never DECREASE as failures mount. A non-monotonic backoff is a bug that
114 // only shows up under the exact load it was written for.
115 var t7: i64 = 1
116 var prev: i64 = 0
117 var i: i64 = 0
118 while i < 1200 {
119 let cur: i64 = ts_backoff_ms(i)
120 if cur < prev { t7 = 0 }
121 prev = cur
122 i = i + 1
123 }
124 pass = pass + tb_ck("T7 delay is monotonic non-decreasing across 0..1199 failures" as *u8, t7); total = total + 1
125
126 // ---- SOURCE-BACKED TEETH ----
127 // Each distinct failure gets its OWN tooth: unreadable source (T8a) is never reported as drift (T8) or
128 // as a broken scanner (T9). A neg-control that shares a failure guard with the test it controls cannot
129 // discriminate -- learned the hard way on nx_hostctl_keepbackoff_gate the same day.
130 let buf: *u8 = sys_mmap(TB_CAP)
131 let srcp: *u8 = sys_mmap(512)
132 let located: i64 = ar_resolve(TB_SRC, srcp)
133 var n: i64 = 0
134 if located == 1 { n = tb_read(srcp, buf) }
135 tb_p(" source: " as *u8); tb_p(srcp); tb_p(" bytes=" as *u8); tb_pn(n); tb_p("\n" as *u8)
136 var t8a: i64 = 0
137 if n > 0 { t8a = 1 }
138 pass = pass + tb_ck("T8a seeder source LOCATED and non-empty (an unreadable file is its own defect)" as *u8, t8a); total = total + 1
139
140 if t8a == 1 {
141 // T8: the accept loops must actually CONSULT the policy, and the child must carry its outcome in
142 // the exit code. A pure function nothing calls is decoration.
143 var ok: i64 = 1
144 if tb_find(buf, n, "ts_backoff_ms(consec)" as *u8) == 0 { ok = 0 }
145 if tb_find(buf, n, "if afd < 0 { consec = consec + 1 }" as *u8) == 0 { ok = 0 }
146 // ★`n <= 0`, NOT `n < 0` -- and this is the tooth that MEASUREMENT changed. nx_seedchurn_gate read
147 // the live log: useful=11 permille, zero_block=296, failed=691. Because the child exited 0 for any
148 // n >= 0, every zero-block serve RESET the consecutive-failure counter, so with ~30% zero-block
149 // serves interleaved among ~69% failures the counter could never reach 8 and THE BRAKE COULD NEVER
150 // FIRE. A completed handshake that moves no data is not a success. One real serve (blocks>0) still
151 // resets to full speed, so a healthy seeder is never slowed.
152 if tb_find(buf, n, "if n <= 0 { sys_exit(1) }" as *u8) == 0 { ok = 0 }
153 pass = pass + tb_ck("T8 both loops call ts_backoff_ms, branch on afd<0, and the child treats n<=0 (NOT n<0) as failure" as *u8, ok); total = total + 1
154
155 // ★T8b GUARDS THE REGRESSION DIRECTLY. `n < 0` is the exact pre-measurement form; if it ever comes
156 // back, the brake silently stops working again with every other tooth still green. Naming the dead
157 // form is cheaper than re-deriving why the fix stopped mattering.
158 var t8b: i64 = 0
159 if tb_find(buf, n, "if n < 0 { sys_exit(1) }" as *u8) == 0 { t8b = 1 }
160 pass = pass + tb_ck("T8b the superseded `n < 0` exit condition is GONE (it is what made the brake unfireable)" as *u8, t8b); total = total + 1
161
162 var t9: i64 = 0
163 if tb_find(buf, n, "zzz_not_in_torrent_seed_zzz" as *u8) == 0 { t9 = 1 }
164 pass = pass + tb_ck("T9 NEG-CONTROL: source scanner does not report a string that is absent" as *u8, t9); total = total + 1
165 } else {
166 tb_p(" INCONCLUSIVE T8 (adoption) and T9 (scanner sanity): not answerable without the source.\n" as *u8)
167 }
168
169 tb_p("---- nx_torrent_backoff_gate " as *u8); tb_pn(pass); tb_p(" / " as *u8); tb_pn(total); tb_p(" ----\n" as *u8)
170 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
171 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
172 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
173 let ctr__dry: *i64 = gv_ctr()
174 ctr__dry[0] = pass
175 ctr__dry[1] = total
176 let rc__dry: i64 = gv_verdict("TORRENT-BACKOFF-GATE" as *u8, ctr__dry, "the seeder backs off under total failure, tolerates real aborts, caps, and self-heals)" as *u8)
177 sys_exit(rc__dry)
178 return rc__dry
179}