nx_tools_api_serve.nx source
↩ module page · 347 lines · 25023 B
1// nx_tools_api_serve.nx -- standalone loopback HTTP server for the R0 agent-facing API (nx_tools_api).
2// Composes nx_http_server (bind/listen/accept, loopback-default) + nx_tools_api (ta_handle). ADDITIVE + ISOLATED:
3// a NEW service on a caller-chosen port that touches NOTHING existing -> never-brick by construction.
4// nx_tools_api_serve serve [port] -- bootstrap-register a few real tools (idempotent), then accept-loop
5// nx_tools_api_serve probe <port> <p> -- sovereign plain-HTTP GET client (self-test the live round-trip)
6// license_tier: ORIGINAL
7import "nx_http_server.nx"
8import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
9import "nx_api_edge.nx" // s-class edge: CORS + security-headers + rate-limit + problem+json; transitively nx_tools_api (ta_handle + ta_cat/ta_catn/ta_slen + tool_register + tr_atoi)
10const TSV_MAGIC_2048: i64 = 2048
11
12const TSV_PORT_DEFAULT: i64 = 8799
13// TSV_REQ_CAP is now the FLOOR of the request reserve, not the ceiling (root fix 2026-08-23, LANE K).
14// WAS: the whole request was read with a SINGLE sys_read into this 64KiB buffer -- so a body that
15// arrived in more than one TCP/TLS segment was parsed as a FRAGMENT (measured: a 131072 B write landed
16// as 42045 B, and trailing argv elements silently vanished), and a body >= 64KiB was refused. Both are
17// one defect: no Content-Length read loop + a guessed cap. The ceiling is now EDGE_REQ_MAX
18// (nx_api_edge.nx), DERIVED from the sites edge reverse-proxy plain buffer (NX_SD2_PLAINCAP) -- a request
19// larger than the edge can forward can never arrive, so it is the true ceiling, not a guess. The
20// per-deploy tunable limit is knowledge/tools_req_reserve.conf, clamped to [TSV_REQ_CAP, EDGE_REQ_MAX].
21const TSV_REQ_CAP: i64 = 65536
22const TSV_RESP_CAP: i64 = 1048576
23const TSV_RESERVE_CONF: *u8 = "knowledge/tools_req_reserve.conf" as *u8
24
25func tsv_puts(s: *u8) -> i64 { sys_write(1, s, ta_slen(s)); return 0 }
26func tsv_atoi_cstr(s: *u8) -> i64 { return tr_atoi(s, 0, ta_slen(s)) }
27
28// bootstrap the production registry with a few REAL, honestly-statused tools so /api/tools is non-empty on first
29// run (idempotent -- reg_put dedups by name; real services register themselves at their own startup).
30func tsv_seed() -> i64 {
31 tool_register("nx_tools_api" as *u8, "sovereign agent-facing tools API: registry over HTTP" as *u8, "GET /api/tools | POST /mcp (JSON-RPC 2.0)" as *u8, "gate-5/5" as *u8)
32 tool_register("nx_mgmt_client" as *u8, "drive the sovereign control-plane over TLS-1.3" as *u8, "nx_mgmt_client <url> login|call ..." as *u8, "live-proven-off-lan" as *u8)
33 tool_register("nx_https_get" as *u8, "sovereign HTTPS GET (TLS-1.3, own trust store)" as *u8, "nx_https_get(url, entropy, key, store, now, out, cap)" as *u8, "shipped" as *u8)
34 tool_register("nx_aw_hostctl" as *u8, "run one allowlisted nx_hostctl sub on the NAS over sovereign SSH" as *u8, "nx_aw_hostctl <sub>" as *u8, "shipped" as *u8)
35 // SOTA-CRITIC surface (2026-07-04): expose the ecosystem's own grader via MCP so ANY engaging LLM
36 // (Claude/Mythos/...) is HELD to the measured-not-asserted rubric, not trusted to be SOTA. The critic
37 // grades depth x breadth toward S-class from the sovereign seg_store, triangulated (2+ independent sources).
38 tool_register("nx_ecosystem_maturity_rollup" as *u8, "THE Nishi SOTA critic: measured (not asserted) ecosystem maturity toward S-class, per-domain current->bar + next rung, triangulated + liar-killed" as *u8, "nx_ecosystem_maturity_rollup [store] [autonomy_log] [out_log] -> overall permil + per-domain grades + GREEN/RED" as *u8, "gate-GREEN" as *u8)
39 // OPS-VIA-MCP (2026-07-08): READ-ONLY control surfaces, executed by fork-pinning nx_hostctl to a FIXED sub
40 // (tool_allowlist.conf 4th field) so a cap for nx_status runs `nx_hostctl status` and NEVER a destructive sub.
41 tool_register("nx_status" as *u8, "sovereign supervisor status: live services + guard state on the NAS (read-only)" as *u8, "nx_status -> supervisor snapshot (pinned nx_hostctl status)" as *u8, "live-2026-07-08" as *u8)
42 tool_register("nx_torstat" as *u8, "torrent stack status: peers, pieces, seeds (read-only)" as *u8, "nx_torstat -> torrent snapshot (pinned nx_hostctl torstat)" as *u8, "live-2026-07-08" as *u8)
43 // MGMT CONTROL PLANE over MCP (2026-07-08): nx_mgmt_call mints a fresh admin session (nx_session_mint_lib, from
44 // the NAS key bundle) per call and drives the never-brick mgmt API (:18098). Reads are pinned (fixed method+path);
45 // nx_mgmt is the general driver -- ADMIN cap only. Retires SSH for deploy/reconcile/restart.
46 tool_register("nx_services" as *u8, "mgmt API: live service inventory + per-service state (read-only)" as *u8, "nx_services -> /api/services JSON" as *u8, "live-2026-07-08" as *u8)
47 tool_register("nx_health" as *u8, "mgmt API: overall health rollup -- degraded/down services (read-only)" as *u8, "nx_health -> /api/health JSON" as *u8, "live-2026-07-08" as *u8)
48 tool_register("nx_mgmt" as *u8, "mgmt control-plane driver: deploy/reconcile/restart/... over the never-brick mgmt API (ADMIN cap required; mints a fresh admin session per call)" as *u8, "nx_mgmt <METHOD> <path> [json_body] -> mgmt API response" as *u8, "live-2026-07-08" as *u8)
49 // VERIFICATION SURFACE (2026-07-15): rule-11 magic gate + capability triage + SOTA racing lap, read-only.
50 // Graded 10/10 vs ESLint no-magic-numbers (external oracle -- NOT self-certified). tools/call proven live.
51 tool_register("nx_verify" as *u8, "Sovereign verification. VERBS: magic = rule-11 magic-number gate (allowlists casts/hex/mmap+syscall/radix-10/const; exempts data tables; graded 10/10 vs ESLint no-magic-numbers external oracle); triage = capability gap analysis; lap = racing-crew where-was/is/to-go vs SOTA. Read-only." as *u8, "over /mcp tools/call name=nx_verify arguments={argv:[VERB,...]}. e.g. {argv:[\"magic\",\"/path/file.nx\"]} -> VERIFY-MAGIC .. PASS|FAIL|EXEMPT" as *u8, "GREEN (10/10 vs ESLint; ratchet live in pre-commit; lap AT-SOTA; tools/call live over /mcp)" as *u8)
52 return 0
53}
54
55// SOTA concurrency: FORK-PER-REQUEST. The parent only accept()s + fork()s; each request is served in
56// its OWN child process, so a long-running tool (e.g. a bulk ingest) holds exactly ONE child while the
57// parent keeps serving everything else -- no more single-call starvation of the plane. Process isolation
58// means a crashing tool cannot take the daemon down. Bounded worker cap (no fork-bomb) + non-blocking
59// reaping (no zombies) + inline backpressure at cap (never drop a request).
60// v1 TRADEOFF (documented, intentional): nx_api_edge's per-client rate-limit + idempotency tables are
61// per-PROCESS static; after fork each child gets a private COW copy, so those reset per request. Flood
62// protection is instead the bounded TSV_MAXKIDS concurrency + backpressure. SOTA-v2 = relocate those
63// tables to sha_mmap_shared (MAP_SHARED survives fork) to restore true cross-request state.
64const TSV_MAXKIDS: i64 = 16
65const TSV_WNOHANG: i64 = 1
66// LISTEN BACKLOG, DERIVED NOT GUESSED (2026-08-27, 503 lane): every inbound connection arrives through
67// ONE child of the sites.elf front door, whose concurrency cap is NX_SD2_MAX_CHILDREN = 64
68// (nx_sites_daemon_v2.nx -- the OWNER of that number; not importable from here, so the derivation is
69// stated at both ends: if the edge cap moves, move this with it). A backlog smaller than the edge cap
70// SYN-drops the tail of a legitimate edge-side burst -- the connection never reaches accept(), the
71// edge reads no bytes in its window, and the caller sees a 503 with no actlog row. The old literal 16
72// absorbed a quarter of what the edge is sized to send.
73const TSV_BACKLOG: i64 = 64
74
75// Per-deploy request reserve: read ONCE at startup (like ta_load_cap_secret), never per request.
76// Reads knowledge/tools_req_reserve.conf line `req_reserve_bytes=<N>`; absent conf -> the EDGE_REQ_MAX
77// ceiling. Clamped to [TSV_REQ_CAP, EDGE_REQ_MAX] so the conf can only LOWER the accept limit, never
78// widen past what the edge can forward. One owner of the number; the digit scan uses a flag, not a
79// clobbered cursor.
80func tsv_load_reserve() -> i64 {
81 let sz: *i64 = sys_mmap(16) as *i64
82 let buf: *u8 = sys_read_file(TSV_RESERVE_CONF, sz)
83 var v: i64 = EDGE_REQ_MAX
84 if (buf as i64) != 0 { if sz[0] > 0 {
85 let key: *u8 = "req_reserve_bytes=" as *u8
86 let p: i64 = ta_indexof(buf, sz[0], key)
87 if p >= 0 {
88 let vs: i64 = p + ta_slen(key)
89 var vlen: i64 = 0
90 var go: i64 = 1
91 while go == 1 {
92 let idx: i64 = vs + vlen
93 if idx >= sz[0] { go = 0 } else {
94 let c: i64 = buf[idx] as i64
95 if c >= 48 { if c <= 57 { vlen = vlen + 1 } else { go = 0 } } else { go = 0 }
96 }
97 }
98 if vlen > 0 { v = tr_atoi(buf, vs, vlen) }
99 }
100 } }
101 if v < TSV_REQ_CAP { v = TSV_REQ_CAP }
102 if v > EDGE_REQ_MAX { v = EDGE_REQ_MAX }
103 return v
104}
105
106// ANNOUNCE, never silently truncate: a request whose declared body exceeds the reserve is refused with a
107// 413 that NAMES the limit, the received size, and the fix. Composes edge_problem (one problem+json
108// ruler), so the shape matches every other edge error.
109func tsv_emit_413(out: *u8, limit: i64, received: i64) -> i64 {
110 let d: *u8 = sys_mmap(256)
111 var o: i64 = ta_cat(d, 0, "request body " as *u8)
112 o = ta_catn(d, o, received)
113 o = ta_cat(d, o, " B exceeds the tools_req_reserve.conf limit " as *u8)
114 o = ta_catn(d, o, limit)
115 o = ta_cat(d, o, " B -- raise req_reserve_bytes (up to the edge ceiling) or split the request" as *u8)
116 d[o] = 0 as u8
117 return edge_problem(out, "413 Payload Too Large" as *u8, "about:blank" as *u8, "Payload Too Large" as *u8, 413, d)
118}
119
120// serve exactly one accepted connection to completion (read WHOLE body -> edge_handle -> send+close).
121// ROOT FIX (LANE K 2026-08-23): compose nx_http_server_read_request, which the estate ALREADY built to
122// loop sys_read until the full Content-Length body is present (bounded by `reserve`) -- the tools daemon
123// simply never used it. A declared body over the reserve is announced as 413, never parsed as a fragment.
124func tsv_serve_one(cfd: i64, req: *u8, out: *u8, reserve: i64) -> i64 {
125 let om: *i64 = sys_mmap(16) as *i64
126 let opo: *i64 = sys_mmap(16) as *i64
127 let opl: *i64 = sys_mmap(16) as *i64
128 let ocl: *i64 = sys_mmap(16) as *i64
129 let obo: *i64 = sys_mmap(16) as *i64
130 let orn: *i64 = sys_mmap(16) as *i64
131 ta_timing_begin()
132 let rc: i64 = nx_http_server_read_request(cfd, req, reserve, om, opo, opl, ocl, obo, orn)
133 ta_timing_read_done()
134 // NXS_SHORT_BODY (named in the lib the same day, by the sibling lane fixing the same defect): the
135 // declared body did not arrive whole -- larger than the reserve (the lib stops at the buffer) or cut
136 // short by the peer. Both are ANSWERED, never closed on in silence: over the reserve is the 413 below,
137 // short of it is a 400 naming got/need. A silent close here would be the {} defect wearing a new shape.
138 let declared: i64 = obo[0] + ocl[0]
139 if rc == NXS_SHORT_BODY { if declared <= reserve {
140 let rn400: i64 = edge_problem_short_body(out, orn[0], declared)
141 nx_http_server_send_response(cfd, out, rn400) // sends + closes cfd
142 return 0
143 } }
144 if rc != NXS_OK { if rc != NXS_SHORT_BODY { sys_close(cfd); return 0 } }
145 if declared > reserve {
146 let rn413: i64 = tsv_emit_413(out, reserve, declared)
147 nx_http_server_send_response(cfd, out, rn413) // sends + closes cfd
148 } else {
149 // THE DEFERRAL CONTRACT (debt 1787505278): a promoted tools/call hands back its JOB-STARTED reply at
150 // once and parks the drainer; we send FIRST, then finish. No second reply is ever built: finish
151 // only forks the drainer, reaps the middle child and writes the actlog row.
152 ta_defer_enable()
153 ta_timing_handle_begin()
154 let rn: i64 = nx_api_edge_handle(req, orn[0], out)
155 ta_timing_handle_done()
156 ta_timing_write_begin()
157 let send_rc: i64 = nx_http_server_send_response(cfd, out, rn)
158 ta_timing_reply_prepare(send_rc)
159 ta_promote_finish()
160 ta_async_finish()
161 ta_timing_reply_log(rn) // after existing deferred work; write acceptance is not client receipt
162 }
163 return 0
164}
165
166// Request descendants must not retain the parent's listening socket.
167func tsv_serve_child(lfd: i64, cfd: i64, req: *u8, out: *u8, reserve: i64) -> i64 {
168 sys_close(lfd)
169 tsv_serve_one(cfd, req, out, reserve)
170 sys_exit_group(0)
171 return 0
172}
173
174func tsv_serve(port: i64) -> i64 {
175 // Survive a client that walks away mid-response. Without this the kernel
176 // TERMINATES this daemon on the first write to a peer that closed early --
177 // and a marathon tools/call whose caller (or the edge, at its read timeout)
178 // gives up is exactly that write. The daemon then dies holding a healthy
179 // listening socket, so the guard reports a crash-loop with no cause while
180 // the work itself completed (the seq1261/seq1126 outage class; proven both
181 // ways by nx_sigpipe_gate). Writers see -EPIPE and handle it as any other
182 // failed write; nothing else changes.
183 sys_ignore_sigpipe()
184 // ⛔⛔tsv_seed() USED TO RUN HERE, BEFORE THE BIND, AND THAT MADE THE HOT RESTART BELOW UNREACHABLE
185 // BY CONSTRUCTION. MEASURED 2026-08-16 during a live /api/deploy that blacked out every sovereign
186 // tool for ~8 minutes: tsv_seed -> tool_register takes a process-lifetime WRITE flock on
187 // knowledge/toolreg-plock, which the OUTGOING instance still holds. So the INCOMING instance slept in
188 // locks_lock_inode_wait (proven from /proc/<pid>/wchan, with fd 3 -> toolreg-plock, and /proc/locks
189 // naming holder+waiter on that inode) and NEVER REACHED nx_http_server_listen_hot at all.
190 // ★★★★★★A HANDOFF MECHANISM PLACED AFTER A BLOCKING CALL THAT THE OUTGOING PROCESS GATES CAN NEVER
191 // RUN -- THE NEW INSTANCE WAITS ON A LOCK HELD BY THE VERY PROCESS IT IS MEANT TO RELIEVE.
192 // ⚠AND IT LOOKED LIKE A BAD BINARY: the first probe printed LISTEN-FAIL verdict=2 and I was one step
193 // from indicting the new build. The CONTROL refuted it -- the OLD, known-good binary hangs IDENTICALLY
194 // when a second instance holds that lock. ★RUN THE CONTROL BEFORE NAMING A CAUSE.
195 // ORDER IS THE WHOLE FIX: bind FIRST (below), seed AFTER. Once the socket is bound, SO_REUSEPORT lets
196 // this instance co-bind while the old one drains, and any request that arrives during the seed WAITS IN
197 // THE BACKLOG instead of being refused -- nothing is dropped, because accept() runs after the seed.
198 // ⚠RESIDUAL, NAMED NOT HIDDEN: the seed wait is still UNBOUNDED. If an outgoing instance wedges while
199 // holding the lock, this one holds a bound socket and never accepts, so requests queue rather than fail
200 // fast. Bounding it belongs in tool_register (the lock's owner, imported by other callers), not here.
201 let addr: *u8 = sys_mmap(16)
202 nx_http_server_addr_loopback(addr, port)
203 let v: *i64 = sys_mmap(16) as *i64
204 // R5 ADOPTION: hot listener (SO_REUSEPORT). This daemon serves EVERY sovereign MCP tool, so restarting it
205 // darkens all tooling for every seat -- and it is the one service with no sovereign recovery path (seq1373),
206 // because nx_mgmt itself is served through here. Letting a new instance bind :18096 while the old still
207 // answers is what turns that restart from a blackout into a handoff.
208 // ⚠ BOTH sides need the option: the first restart after this ships still cannot hand off. Gate: nx_hotlisten_gate 3/3.
209 let lfd: i64 = nx_http_server_listen_hot(addr, TSV_BACKLOG, v)
210 // ⚠THE MESSAGE NAMED THE WRONG STAGE. verdict=2 is NXS_BIND_ERR, not a listen failure, and reading
211 // "LISTEN-FAIL" sent me hunting a listen bug during a live outage. ★A GUARD'S MESSAGE THAT NAMES THE
212 // WRONG SUBJECT COSTS THE READER THE INVESTIGATION IT WAS WRITTEN TO SAVE THEM -- print the stage.
213 if lfd < 0 {
214 tsv_puts("SERVE-FAIL stage=" as *u8)
215 if v[0] == NXS_SOCKET_ERR { tsv_puts("socket" as *u8) }
216 if v[0] == NXS_BIND_ERR { tsv_puts("bind(port in use, or an old instance bound WITHOUT SO_REUSEPORT)" as *u8) }
217 if v[0] == NXS_LISTEN_ERR { tsv_puts("listen" as *u8) }
218 if v[0] == NXS_BAD_ARG { tsv_puts("bad-arg" as *u8) }
219 tsv_puts(" verdict=" as *u8); let mb: *u8 = sys_mmap(32); let mo: i64 = ta_catn(mb, 0, v[0]); sys_write(1, mb, mo)
220 tsv_puts(" port=" as *u8); let pb: *u8 = sys_mmap(32); let po: i64 = ta_catn(pb, 0, port); sys_write(1, pb, po)
221 tsv_puts("\n" as *u8); sys_exit(1); return 1
222 }
223 let bnr: *u8 = sys_mmap(96); var bo: i64 = ta_cat(bnr, 0, "nx_tools_api_serve LIVE (fork-per-request cap=" as *u8); bo = ta_catn(bnr, bo, TSV_MAXKIDS); bo = ta_cat(bnr, bo, ") on 127.0.0.1:" as *u8); bo = ta_catn(bnr, bo, port); bnr[bo] = 10 as u8; sys_write(1, bnr, bo + 1)
224 // ★★★★★★SEED IN A FORKED CHILD SO accept() STARTS IMMEDIATELY. Binding before the seed (above) was
225 // necessary but NOT sufficient: MEASURED 2026-08-16, tsv_seed writes ~27 MB of fsync'd registry
226 // updates, and on an IO-saturated array (5 torrent fetchers in D-state, load 16+, swap 804 permil)
227 // that ran for MINUTES in btrfs wait_current_trans. The socket was bound and queuing -- but the
228 // daemon never reached accept(), so the supervisor's health probe failed, it KILLED the instance, and
229 // the respawn queued behind the same lock. Two supervisors were doing it at once.
230 // ★★★★★★ANY STARTUP STEP ON THE CRITICAL PATH TO accept() IS A STEP THAT CAN COST YOU THE SERVICE,
231 // AND ITS COST IS SET BY THE BUSIEST THING ON THE BOX, NOT BY YOUR CODE.
232 // The seed is PURELY DESCRIPTIVE (registry rows for /api/tools) and idempotent, so nothing served
233 // depends on it having finished. The parent's existing WNOHANG reap loop collects this child, and its
234 // `active` decrement is guarded by `if active > 0`, so reaping a non-request child cannot corrupt the
235 // worker count.
236 // PRIME THE CAP-MODE LATCH IN THE PARENT, BEFORE ANY FORK. ta_capmode is PROCESS-LOCAL mmap memory
237 // and this daemon is FORK-PER-REQUEST, so a latch first set inside a request child is set in a process
238 // that immediately exits -- and the NEXT request starts UNSEEN and announces all over again. MEASURED
239 // 2026-08-21, straight after the deploy that first shipped this announce: capmode.jrnl went 4 -> 21
240 // lines in ~10 minutes of light single-seat use, i.e. ~100 B appended to an UNBOUNDED journal on the
241 // hot path of EVERY sovereign MCP call, plus a stderr write per request.
242 // A ONCE-PER-PROCESS LATCH IN A FORK-PER-REQUEST SERVER IS A ONCE-PER-REQUEST LATCH.
243 // The repeat-announce hazard WAS anticipated by the author -- but only for GET /api/cap/status, which
244 // is why keeping ta_load_cap_secret_from a pure resolver was necessary and NOT sufficient.
245 // Resolving ONCE here makes the announce fire exactly once per daemon start; every child then inherits
246 // the settled latch copy-on-write and correctly no-ops. Placed AFTER the bind above, never before it,
247 // because ANY startup step on the critical path to accept() is a step that can cost the service -- and
248 // this one is a single small keyfile read, not the 27 MB registry seed that caused the 08-16 blackout.
249 let cml: *i64 = sys_mmap(16) as *i64
250 ta_load_cap_secret(cml)
251 let spid: i64 = sys_fork()
252 if spid == 0 { sys_close(lfd); tsv_seed(); sys_exit(0) }
253 let reserve: i64 = tsv_load_reserve()
254 let rvb: *u8 = sys_mmap(96); var rvo: i64 = ta_cat(rvb, 0, "nx_tools_api_serve REQUEST-RESERVE=" as *u8); rvo = ta_catn(rvb, rvo, reserve); rvo = ta_cat(rvb, rvo, " B (floor=" as *u8); rvo = ta_catn(rvb, rvo, TSV_REQ_CAP); rvo = ta_cat(rvb, rvo, " ceiling=" as *u8); rvo = ta_catn(rvb, rvo, EDGE_REQ_MAX); rvb[rvo] = 41 as u8; rvb[rvo + 1] = 10 as u8; sys_write(1, rvb, rvo + 2)
255 let req: *u8 = sys_mmap(reserve)
256 let out: *u8 = sys_mmap(TSV_RESP_CAP)
257 let st: *i64 = sys_mmap(16) as *i64
258 var active: i64 = 0
259 var run: i64 = 1
260 while run == 1 {
261 // non-blocking reap of any finished children so they never become zombies + free worker slots
262 var rp: i64 = sys_wait4(0 - 1, st, TSV_WNOHANG)
263 while rp > 0 { if active > 0 { active = active - 1 } rp = sys_wait4(0 - 1, st, TSV_WNOHANG) }
264 let cfd: i64 = nx_http_server_accept_one(lfd, v)
265 if cfd >= 0 {
266 if active < TSV_MAXKIDS {
267 let pid: i64 = sys_fork()
268 if pid == 0 {
269 // CHILD: release listener ownership before serving this accepted connection.
270 tsv_serve_child(lfd, cfd, req, out, reserve)
271 } else {
272 // PARENT: child owns cfd -> close our copy + keep accepting. fork<0 -> serve inline (never drop).
273 if pid > 0 { sys_close(cfd); active = active + 1 } else { tsv_serve_one(cfd, req, out, reserve) }
274 }
275 } else {
276 // BACKPRESSURE v2 (2026-08-27, 503 lane). The old path served the overflow request INLINE
277 // in the parent -- and while the parent serves, accept() is STOPPED, so every queued
278 // connection burns its edge window behind one call that can legally run the whole sync
279 // budget. That is the no-actlog-row 503 class: the request dies at the edge having never
280 // reached a handler. Instead, BLOCK until ONE child exits -- the wait is bounded by the
281 // SHORTEST remaining child, not by a whole fresh request -- then fork this request onto
282 // the freed slot. Nothing is dropped; the loop resumes as fast as any child can finish.
283 // A blocking reap can also collect the startup seed child; the active>0 guard keeps the
284 // count sane exactly as the WNOHANG loop above does. wait4 returning <=0 cannot happen
285 // while children are running, but is handled rather than assumed: it falls back to the
286 // old inline serve, so the worst case IS the prior behavior, never worse.
287 let bp: i64 = sys_wait4(0 - 1, st, 0)
288 if bp > 0 {
289 if active > 0 { active = active - 1 }
290 let pid2: i64 = sys_fork()
291 if pid2 == 0 {
292 tsv_serve_child(lfd, cfd, req, out, reserve)
293 } else {
294 if pid2 > 0 { sys_close(cfd); active = active + 1 } else { tsv_serve_one(cfd, req, out, reserve) }
295 }
296 } else {
297 tsv_serve_one(cfd, req, out, reserve)
298 }
299 }
300 }
301 }
302 return 0
303}
304
305// sovereign plain-HTTP GET client -> connect 127.0.0.1:port, GET path, print the raw response. Proves the LIVE
306// socket round-trip against our own server (no curl, no TLS).
307func tsv_probe(port: i64, path: *u8) -> i64 {
308 let addr: *u8 = sys_mmap(16)
309 nx_http_server_addr_loopback(addr, port)
310 let fd: i64 = sys_socket(2, 1, 0) // AF_INET, SOCK_STREAM
311 if fd < 0 { tsv_puts("PROBE socket-fail\n" as *u8); sys_exit(1); return 1 }
312 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { tsv_puts("PROBE connect-fail\n" as *u8); sys_close(fd); sys_exit(1); return 1 }
313 let req: *u8 = sys_mmap(TSV_MAGIC_2048)
314 var o: i64 = ta_cat(req, 0, "GET " as *u8)
315 o = ta_cat(req, o, path)
316 o = ta_cat(req, o, " HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n" as *u8)
317 sys_write(fd, req, o)
318 let buf: *u8 = sys_mmap(TSV_RESP_CAP)
319 var total: i64 = 0
320 var run: i64 = 1
321 while run == 1 {
322 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, TSV_RESP_CAP - total)
323 if r <= 0 { run = 0 } else { total = total + r }
324 }
325 sys_close(fd)
326 sys_write(1, buf, total)
327 tsv_puts("\n" as *u8)
328 return 0
329}
330
331func main(argc: i64, argv: *i64) -> i64 {
332 if argc < 2 { tsv_puts("usage: nx_tools_api_serve serve [port] | probe <port> <path>\n" as *u8); sys_exit(2); return 2 }
333 let mode: *u8 = argv[1] as *u8
334 if ta_streq_n(mode, ta_slen(mode), "serve" as *u8) == 1 {
335 var port: i64 = TSV_PORT_DEFAULT
336 if argc >= 3 { port = tsv_atoi_cstr(argv[2] as *u8) }
337 return tsv_serve(port)
338 }
339 if ta_streq_n(mode, ta_slen(mode), "probe" as *u8) == 1 {
340 if argc < 4 { tsv_puts("usage: nx_tools_api_serve probe <port> <path>\n" as *u8); sys_exit(2); return 2 }
341 let port: i64 = tsv_atoi_cstr(argv[2] as *u8)
342 return tsv_probe(port, argv[3] as *u8)
343 }
344 tsv_puts("unknown mode (serve|probe)\n" as *u8)
345 sys_exit(2)
346 return 2
347}