nx_pool_ring_gate.nx source
↩ module page · 92 lines · 4314 B
1// nx_pool_ring_gate.nx -- regression witness for the RING ARENA fix
2// in nx_thread_pool (2026-07-08).
3//
4// The task arena was a bump allocator (16384 slots); past that,
5// nx_pool_submit returned -1 without queuing, and a caller that then
6// delta-waited spun to the 2-billion timeout. A long-lived shared
7// pool (the LLM forward submits thousands of tasks per token) hit
8// this and wedged 428s/token. Fix: slot index wraps modulo
9// arena_cap; SAFE because channel depth << arena_cap so a slot is
10// consumed long before the ring wraps back to it.
11//
12// This gate submits N >> arena_cap tasks to ONE pool (forcing several
13// full ring wraps), each atomically incrementing a shared counter,
14// and verifies EVERY task ran exactly once -- the exact scenario that
15// silently dropped tasks before.
16//
17// Checks:
18// 1 shared counter == N (no task lost to a dropped submit)
19// 2 pool completed-counter == N (delta-wait saw them all)
20// 3 a SECOND batch on the same pool also completes (reuse across
21// the wrap boundary)
22//
23// lineage_id: pool_ring_gate_v1
24
25// D001 MIGRATION 2026-08-25 (LR2 lane). This gate hand-rolled its verdict: it
26// printed OK/FAIL and returned 11..15, so /api/gate_run -- which derives GREEN or
27// RED from the EXIT CODE -- could not judge it, nx_gate_green could not read it,
28// and it recorded no harness frame, leaving its flake and erosion invisible. It
29// also meant the gate could not be promoted at all. Migrated onto nx_gate_verdict
30// per-tooth, so declared teeth and executed teeth are the same number BY
31// CONSTRUCTION. Every original check is preserved; none is weakened, and the two
32// wait-guard outcomes that used to be bare early returns are now teeth of their own.
33import "nx_thread_pool.nx"
34import "nx_atom.nx"
35import "nx_gate_verdict.nx"
36
37// > 16384 arena slots, forces ~3 ring wraps.
38const RG_N: i64 = 50000
39const RG_N2: i64 = 20000
40
41func rg_task(ctx: i64) -> i64 {
42 let addr: *i64 = ctx as *i64
43 nx_atom_faa_i64(addr, 1, NX_MO_SEQ_CST)
44 return 0
45}
46
47func rg_kv(k: *u8, v: i64) -> i64 { gv_puts(k); gv_num(v); gv_puts("\n" as *u8); return 0 }
48
49func main() -> i64 {
50 let gc: *i64 = gv_ctr()
51 gv_head("=== NX-POOL-RING-GATE -- ring-arena regression witness for nx_thread_pool ===" as *u8)
52 let pool: *NxThreadPool = nx_pool_new(8, 64)
53 let ctr: *i64 = sys_mmap(8) as *i64
54 ctr[0] = 0
55 rg_kv(" workers=" as *u8, pool.n_workers)
56 rg_kv(" arena_cap=" as *u8, pool.arena_cap)
57 rg_kv(" batch1 tasks=" as *u8, RG_N)
58 rg_kv(" batch2 tasks=" as *u8, RG_N2)
59
60 // ---- batch 1: N >> arena_cap, forcing several full ring wraps ----
61 let done0: i64 = nx_pool_n_completed(pool)
62 var i: i64 = 0
63 while i < RG_N {
64 nx_pool_submit(pool, rg_task, ctr as i64)
65 i = i + 1
66 }
67 let w1: i64 = nx_pool_wait(pool, done0 + RG_N)
68 let seen1: i64 = ctr[0]
69 let comp1: i64 = nx_pool_n_completed(pool)
70 rg_kv(" batch1 shared counter=" as *u8, seen1)
71 rg_kv(" batch1 pool completed delta=" as *u8, comp1 - done0)
72 gv_check("batch1-delta-wait-returns-without-tripping-its-hang-guard" as *u8, ((w1 == 0) as i64), gc)
73 gv_check("batch1-every-task-ran-exactly-once-across-several-ring-wraps" as *u8, ((seen1 == RG_N) as i64) & ((RG_N > 0) as i64), gc)
74 gv_check("batch1-pool-completed-counter-advanced-by-exactly-the-batch" as *u8, ((comp1 == done0 + RG_N) as i64), gc)
75
76 // ---- batch 2: reuse the same pool across further wraps ----
77 let done1: i64 = nx_pool_n_completed(pool)
78 var j: i64 = 0
79 while j < RG_N2 {
80 nx_pool_submit(pool, rg_task, ctr as i64)
81 j = j + 1
82 }
83 let w2: i64 = nx_pool_wait(pool, done1 + RG_N2)
84 let seen2: i64 = ctr[0]
85 rg_kv(" batch2 shared counter=" as *u8, seen2)
86 gv_check("batch2-delta-wait-returns-without-tripping-its-hang-guard" as *u8, ((w2 == 0) as i64), gc)
87 gv_check("batch2-pool-reuse-across-further-wraps-loses-not-one-task" as *u8, ((seen2 == RG_N + RG_N2) as i64) & ((RG_N2 > 0) as i64), gc)
88
89 let sd: i64 = nx_pool_shutdown(pool)
90 gv_check("pool-shuts-down-and-every-worker-exits" as *u8, ((sd == 0) as i64) & ((nx_pool_n_alive(pool) == 0) as i64), gc)
91 return gv_verdict("POOL-RING-GATE" as *u8, gc, "subject nx_thread_pool ring arena -- each check name carries its own strength" as *u8)
92}