code wiki / (root) / nx_sig2_txq.nx

nx_sig2_txq.nx source

↩ module page · 121 lines · 6127 B

1// nx_sig2_txq.nx -- FRAME-ATOMIC non-blocking WebSocket send queue for the signaling relay. 2// (four-pillar ADDRESS at the transport layer; root cause: the relay's blocking fan-out writes.) 3// 4// THE TWO DEFECTS THIS CLOSES (source-confirmed 2026-07-09, [[project-nishi-videoops-four-pillars]]): 5// 1. BLOCKING WRITES in a single-threaded fan-out: one slow receiver's full socket buffer STALLED 6// every room on the relay (head-of-line). Writes here use sendto(MSG_DONTWAIT) -- the relay 7// NEVER sleeps on a receiver. (Write-side only: the read path keeps its blocking semantics.) 8// 2. PARTIAL-FRAME POISON: _wss_write_n bailed mid-frame on w<=0, leaving a torn WebSocket frame 9// on the stream -- every later byte misparsed (the field's silent video garbage). Here a frame 10// either goes out WHOLE (possibly across POLLOUT continuations from the queue) or is dropped 11// WHOLE before any byte touches the wire. Torn-frame-unavoidable (partial direct write + queue 12// full) is FATAL by design: the caller must treat the conn as poisoned and let the reap path 13// close it -- fail-stop beats fail-dirty. 14// 15// Per-conn state (caller-allocated, indexed by conn slot ci): 16// txq slab: NX_S2TX_QBYTES per conn -- queued raw wire bytes (whole frames / frame remainders) 17// txh[ci]: queue head offset txl[ci]: queued byte count 18// Drop policy: a frame that cannot fit WHOLE in the queue is dropped whole and counted by the 19// caller (video P-gap -> the client's seq check + kreq recovers in ~1 RTT; JSON control re-sends). 20// license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_websocket_frame.nx" 23 24const NX_S2TX_QBYTES: i64 = 262144 // 256KB/conn ~= 4 worst-case keyframes ~= 250ms drop horizon 25const NX_S2TX_DONTWAIT: i64 = 0x40 // MSG_DONTWAIT: this write NEVER sleeps 26const NX_S2TX_NOSIGNAL: i64 = 0x4000 // MSG_NOSIGNAL: a send to a dead peer returns -EPIPE, never SIGPIPE 27const NX_S2TX_SENDFLAGS: i64 = 0x4040 // DONTWAIT | NOSIGNAL -- self-protecting regardless of signal disposition 28const NX_S2TX_EAGAIN: i64 = 0 - 11 29 30func s2tx_qptr(txq: *u8, ci: i64) -> *u8 { 31 return (txq as i64 + ci * NX_S2TX_QBYTES) as *u8 32} 33 34// non-blocking write of up to n bytes; returns bytes written (>=0), 0 on EAGAIN, -1 fatal. 35// MSG_NOSIGNAL means a write to a closed peer returns -EPIPE (fatal -> caller reaps) and NEVER raises 36// SIGPIPE -- the relay cannot be killed by one dead receiver even if it forgot to ignore the signal. 37func s2tx_wr(fd: i64, p: *u8, n: i64) -> i64 { 38 let w: i64 = sys_sendto(fd, p, n, NX_S2TX_SENDFLAGS, 0 as *u8, 0) 39 if w > 0 { return w } 40 if w == NX_S2TX_EAGAIN { return 0 } 41 return 0 - 1 42} 43 44// Push queued bytes at the socket until empty or EAGAIN. 0 = ok (maybe residue), -1 = fatal. 45func s2tx_drain(txq: *u8, txh: *i64, txl: *i64, ci: i64, fd: i64) -> i64 { 46 let q: *u8 = s2tx_qptr(txq, ci) 47 var run: i64 = 1 48 var rc: i64 = 0 49 while run == 1 { 50 if txl[ci] <= 0 { txh[ci] = 0; txl[ci] = 0; run = 0 } 51 else { 52 let w: i64 = s2tx_wr(fd, (q as i64 + txh[ci]) as *u8, txl[ci]) 53 if w > 0 { txh[ci] = txh[ci] + w; txl[ci] = txl[ci] - w } 54 else { 55 if w == 0 { run = 0 } // EAGAIN: POLLOUT continues later 56 else { rc = 0 - 1; run = 0 } // dead socket: caller reaps the conn 57 } 58 } 59 } 60 return rc 61} 62 63// Send one whole WebSocket frame, frame-atomically, never blocking. 64// tx = [txq, txh, txl, hdr_scratch(>=16B)] finop = fin*256 + opcode 65// Returns 1 = fully sent or queued whole; 0 = dropped whole (queue full, wire untouched); 66// -1 = fatal (socket error, or torn frame unavoidable -> conn must be reaped). 67func s2tx_send(tx: *i64, ci: i64, fd: i64, finop: i64, payload: *u8, plen: i64) -> i64 { 68 let txq: *u8 = (tx[0]) as *u8 69 let txh: *i64 = (tx[1]) as *i64 70 let txl: *i64 = (tx[2]) as *i64 71 let hscr: *u8 = (tx[3]) as *u8 72 let hlen: i64 = ws_build_header(hscr, 16, finop / 256, finop & 0xff, 0, 0, plen) 73 if hlen < 0 { return 0 - 1 } 74 if txl[ci] > 0 { 75 if s2tx_drain(txq, txh, txl, ci, fd) < 0 { return 0 - 1 } 76 } 77 var hoff: i64 = 0 78 var poff: i64 = 0 79 if txl[ci] == 0 { 80 // FAST PATH: queue empty -> write direct as far as the socket takes it, never sleeping 81 var run: i64 = 1 82 while run == 1 { 83 if hoff < hlen { 84 let w: i64 = s2tx_wr(fd, (hscr as i64 + hoff) as *u8, hlen - hoff) 85 if w > 0 { hoff = hoff + w } 86 else { if w == 0 { run = 0 } else { return 0 - 1 } } 87 } else { 88 if poff < plen { 89 let w2: i64 = s2tx_wr(fd, (payload as i64 + poff) as *u8, plen - poff) 90 if w2 > 0 { poff = poff + w2 } 91 else { if w2 == 0 { run = 0 } else { return 0 - 1 } } 92 } else { run = 0 } 93 } 94 } 95 if hoff >= hlen { if poff >= plen { return 1 } } // whole frame on the wire 96 } 97 // Stash the remainder (or the whole frame) -- WHOLE or not at all. 98 let need: i64 = (hlen - hoff) + (plen - poff) 99 let q: *u8 = s2tx_qptr(txq, ci) 100 if txh[ci] > 0 { 101 if txh[ci] + txl[ci] + need > NX_S2TX_QBYTES { // compact once: reclaim the drained head 102 var mi: i64 = 0 103 while mi < txl[ci] { q[mi] = q[txh[ci] + mi]; mi = mi + 1 } 104 txh[ci] = 0 105 } 106 } 107 if txh[ci] + txl[ci] + need > NX_S2TX_QBYTES { 108 if hoff > 0 { return 0 - 1 } // part of THIS frame is already on the wire: 109 if poff > 0 { return 0 - 1 } // dropping now would tear it -> fail-stop 110 return 0 // wire untouched -> drop whole, count it 111 } 112 var wp: i64 = txh[ci] + txl[ci] 113 var k: i64 = hoff 114 while k < hlen { q[wp] = hscr[k]; wp = wp + 1; k = k + 1 } 115 k = poff 116 while k < plen { q[wp] = payload[k]; wp = wp + 1; k = k + 1 } 117 txl[ci] = txl[ci] + need 118 return 1 119} 120 121func main() -> i64 { return 0 }