code wiki / _hdl_build / nx_acceptq.nx

nx_acceptq.nx source

↩ module page · 156 lines · 6711 B

1// nx_acceptq.nx -- DETECTOR for the ACCEPT-LOOP WEDGE: a daemon that holds its listen socket but has 2// stopped calling accept(). 3// 4// WHY THIS EXISTS (2026-07-31, measured). The mgmt API blocked in wait4 for the entire life of a forked 5// build. Connections completed the TCP handshake -- the KERNEL accepts them into the backlog -- so every 6// port-probe health check reported UP, for six minutes at a time, while no request was served. Three 7// separate lanes filed it as "MGMT :18098 DOWN, ALL SEATS". :18098 was never down. It was blocked. 8// 9// ★★★ THE INSIGHT THIS ORGAN ENCODES: A SUCCESSFUL CONNECT PROVES THE KERNEL ACCEPTED, NOT THE PROCESS. 10// The two are indistinguishable from outside -- unless you read the one number the kernel already keeps: 11// on a LISTEN socket, /proc/net/tcp's rx_queue IS THE COUNT OF COMPLETED-BUT-UNACCEPTED CONNECTIONS. 12// Non-zero means the app is behind. Sustained non-zero means the app is not accepting at all. 13// Measured on the live wedge: rx_queue climbed 8 -> 34 while wedged, and returned to 0 once fixed. 14// 15// This is a pure READER: it opens /proc/net/tcp and nothing else. It cannot perturb the thing it measures, 16// which matters because the failure it hunts is caused by blocking. 17// 18// nx_acceptq -> report every LISTEN socket with a non-zero accept backlog 19// nx_acceptq <port> -> report just that port; exit 1 if it looks wedged 20// expect_exit: 0 license_tier: ORIGINAL 21import "nx_syscalls.nx" 22 23const AQ_CAP: i64 = 1048576 24 25func aq_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 26 27func aq_putn(v: i64) -> i64 { 28 let b: *u8 = sys_mmap(32) 29 if v == 0 { b[0] = 48 as u8; sys_write(1, b, 1); return 0 } 30 var n: i64 = 0 31 var x: i64 = v 32 while x > 0 { b[n] = ((x - (x / 10) * 10) + 48) as u8; n = n + 1; x = x / 10 } 33 let r: *u8 = sys_mmap(32) 34 var i: i64 = 0 35 while i < n { r[i] = b[n - 1 - i]; i = i + 1 } 36 sys_write(1, r, n) 37 return 0 38} 39 40// one hex digit -> value, or -1 41func aq_hexv(c: u8) -> i64 { 42 if c >= (48 as u8) { if c <= (57 as u8) { return (c as i64) - 48 } } 43 if c >= (65 as u8) { if c <= (70 as u8) { return (c as i64) - 55 } } 44 if c >= (97 as u8) { if c <= (102 as u8) { return (c as i64) - 87 } } 45 return 0 - 1 46} 47 48// parse `count` hex chars at off; -1 on any non-hex 49func aq_hex(b: *u8, off: i64, count: i64) -> i64 { 50 var v: i64 = 0 51 var i: i64 = 0 52 while i < count { 53 let d: i64 = aq_hexv(b[off + i]) 54 if d < 0 { return 0 - 1 } 55 v = v * 16 + d 56 i = i + 1 57 } 58 return v 59} 60 61func aq_is_sp(c: u8) -> i64 { if c == (32 as u8) { return 1 } if c == (9 as u8) { return 1 } return 0 } 62 63// advance past spaces, then past one token; returns offset of the NEXT token start 64func aq_next(b: *u8, off: i64, n: i64) -> i64 { 65 var i: i64 = off 66 while i < n { if aq_is_sp(b[i]) == 0 { break } i = i + 1 } 67 while i < n { if aq_is_sp(b[i]) == 1 { break } i = i + 1 } 68 while i < n { if aq_is_sp(b[i]) == 0 { break } i = i + 1 } 69 return i 70} 71 72// Scan /proc/net/tcp. Layout per row (after the header): 73// sl local_address rem_address st tx_queue:rx_queue ... 74// st == 0A is LISTEN. For a LISTEN socket rx_queue is the ACCEPT BACKLOG DEPTH. 75// want_port < 0 means "report all". Returns the number of wedge-suspect listeners found. 76func aq_scan(want_port: i64) -> i64 { 77 let box: *i64 = (sys_mmap(8)) as *i64 78 box[0] = 0 79 let buf: *u8 = sys_read_file("/proc/net/tcp" as *u8, box) 80 if (buf as i64) == 0 { aq_puts("ACCEPTQ: cannot read /proc/net/tcp\n" as *u8); return 0 - 1 } 81 let n: i64 = box[0] 82 var flagged: i64 = 0 83 var shown: i64 = 0 84 var i: i64 = 0 85 // skip the header line 86 while i < n { if buf[i] == (10 as u8) { i = i + 1; break } i = i + 1 } 87 while i < n { 88 let ls: i64 = i 89 var le: i64 = i 90 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 91 if le - ls > 40 { 92 // field 1 = local_address "AAAAAAAA:PPPP"; find the ':' after the sl token 93 let f1: i64 = aq_next(buf, ls, le) 94 var cp: i64 = f1 95 while cp < le { if buf[cp] == (58 as u8) { break } cp = cp + 1 } 96 if cp + 4 < le { 97 let port: i64 = aq_hex(buf, cp + 1, 4) 98 let f2: i64 = aq_next(buf, f1, le) // rem_address 99 let f3: i64 = aq_next(buf, f2, le) // st 100 let st: i64 = aq_hex(buf, f3, 2) 101 let f4: i64 = aq_next(buf, f3, le) // tx_queue:rx_queue 102 var qc: i64 = f4 103 while qc < le { if buf[qc] == (58 as u8) { break } qc = qc + 1 } 104 let rxq: i64 = aq_hex(buf, qc + 1, 8) 105 if st == 10 { // 0x0A = LISTEN 106 var pick: i64 = 0 107 if want_port < 0 { if rxq > 0 { pick = 1 } } 108 if want_port >= 0 { if port == want_port { pick = 1 } } 109 if pick == 1 { 110 shown = shown + 1 111 aq_puts("port=" as *u8); aq_putn(port) 112 aq_puts(" accept_backlog=" as *u8); aq_putn(rxq) 113 if rxq > 0 { 114 flagged = flagged + 1 115 aq_puts(" WEDGE-SUSPECT: connections are COMPLETED but UNACCEPTED -- the port is open and the process is NOT calling accept()\n" as *u8) 116 } else { 117 aq_puts(" ok (listener is draining its backlog)\n" as *u8) 118 } 119 } 120 } 121 } 122 } 123 i = le + 1 124 } 125 if shown == 0 { 126 if want_port < 0 { aq_puts("ACCEPTQ: no listener has a non-zero accept backlog -- nothing is wedged\n" as *u8) } 127 else { aq_puts("ACCEPTQ: no LISTEN socket on that port\n" as *u8) } 128 } 129 return flagged 130} 131 132func main(argc: i64, argv: *i64) -> i64 { 133 var want: i64 = 0 - 1 134 if argc >= 2 { 135 let a: *u8 = argv[1] as *u8 136 var v: i64 = 0 137 var i: i64 = 0 138 var ok: i64 = 1 139 while a[i] != (0 as u8) { 140 if a[i] < (48 as u8) { ok = 0 } 141 if a[i] > (57 as u8) { ok = 0 } 142 if ok == 0 { break } 143 v = v * 10 + ((a[i] as i64) - 48) 144 i = i + 1 145 } 146 if ok == 1 { want = v } 147 } 148 aq_puts("=== nx_acceptq -- accept-loop wedge detector ===\n" as *u8) 149 let f: i64 = aq_scan(want) 150 if f < 0 { return 2 } 151 if f > 0 { 152 aq_puts("verdict=WEDGED listeners_flagged=" as *u8); aq_putn(f); aq_puts("\n" as *u8) 153 return 1 154 } 155 aq_puts("verdict=GREEN\n" as *u8) 156 return 0 157}