code wiki / (root) / nx_pool_ring_gate.nx

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}