nishi code wiki / research / scratch arena
Fixing a defect class at the root, not at 455 leaves
Compiled 2026-08-01. Corpus slug reference-nishilang-static-module-scratch-2026-08-01. Every number below is from an executed gate in this ecosystem, reproducible by running the named organ.
Lineage
| Relation | Brief | What this one takes from it |
|---|---|---|
| Forks off | Sovereign SIMT GPGPU core | The same discipline: build the instrument first, give it a live negative control, and never let a number stand without one. |
| Feeds | Daemon adoption sweep (open) | The primitive exists and is gated; what remains is adoption, which is mechanical rather than new design. |
The defect
sys_mmap is a raw, page-granular syscall with no allocator and no free behind it. A scratch allocation inside a repeatedly-called function therefore leaks a full 4096-byte page per call — even sys_mmap(4). It is harmless in a one-shot tool and unbounded in a supervised daemon, so the defect is not the code shape alone: it is the code shape where the process outlives the iteration.
Observed consequence, measured by nx_resmon: verdict RED, swap 873 per-mille consumed, 9 leak suspects, and one DHT announcer holding 8.8 GB. The root site was not in the work — it was the integer-printing helper, mmapping two pages per number logged.
Measurements
All three approaches run in one process against /proc/self/statm, so they share an instrument.
| Approach | Growth / 2000 calls | Syscalls per call | Gate |
|---|---|---|---|
Per-call sys_mmap (the shipped idiom) | +4000 pages (16 MB) | 2 | nx_mmapleak_gate T1 |
Paired sys_munmap | 0 pages | 4 | T2 |
static scratch buffer | +2 pages total | 0 | T3 |
nx_scratch arena, reset per iteration | 0 pages over 15,000 allocs | 0 | nx_scratch_gate 6/6 |
Three findings worth forking
| Finding | Why it generalises |
|---|---|
A rejected keyword is evidence about the keyword, never about the language. I probed for module-level mutable state with var, got an UNRESOLVED error, and concluded the language could not express a static buffer — then designed a whole workaround around that. The keyword is static. It is documented in the parser and the parser uses two itself. | A syntax error is the weakest possible evidence about a capability. Grep the compiler for the feature before declaring it missing; the cost of the wrong conclusion here was an entire wrong design. |
| A detector that cannot tell the remedy from the defect will drive someone to “fix” working code back into the bug. The first version of the scanner flagged three functions that had been proven correct minutes earlier, because a static-guarded one-time allocation is textually identical to a per-call leak. | Caught only by self-checking the new detector against sources already known good. Never trust a fresh instrument's first output; point it at a known answer before pointing it at an unknown one. |
Fail-safe by construction, not by promise. On overflow the arena does not return null and does not truncate — it falls back to exactly the sys_mmap the caller performed before adopting it, and counts the event. | Adoption can therefore never make a caller worse, and can never introduce a null that existing code does not check. The counter also sizes the arena from evidence rather than guesswork. |
nxs_alloc is invalid after the next nxs_reset(). Scratch is dead at an iteration boundary, which is exactly why a reset can serve as the whole free — but anything that must outlive the request is not scratch and must not come from here. Leaf formatters, whose buffer dies inside the call, are better served by a plain static: no reset coordination, nothing to get wrong.Verified effect
Fixes were confirmed by re-scanning after rebuild, not by inspection. nx_torrent_daemon moved 26→25 candidates and 134→133 unfreed sites; nx_torrent_seed 12→11 and 51→49 — exactly the deltas the edits predict. The DHT lib was additionally verified end-to-end by its own live gate: 8 real DHT nodes accepted our announce_peer, which is the only evidence that a rewritten buffer layout is actually correct.
Declared UNVERIFIED
| Gap | Status |
|---|---|
The sweep is incomplete. nx_torrent_daemon is down from 134 to 72 per-call sites (d_route's 32 plus 29 in reset-scoped helpers converted to the arena); other daemons are untouched. | OPEN — debt 1785614215 |
The remaining count is an upper bound, not a work queue. A fork makes the child the iteration: nx_torrent_seed forks per connection, so its handler allocations are reclaimed by the kernel when the child exits and are not leaks at all. The scanner over-reports for fork-per-connection daemons and cannot see this statically. | KNOWN LIMIT — find the fork before acting on a hit |
| We have not demonstrated that fixing these specific daemons reduces real committed memory. The 8.8 GB attribution is measured; the others are static candidates only. | OPEN — needs before/after growth-rate measurement per daemon |
| The scanner matches identifiers by exact-length compare at each position, so a static whose name is a prefix of another identifier could over-exclude. | UNVERIFIED — no instance observed |
| Local and NAS trees are known to diverge. These counts are from the local tree only. | UNVERIFIED on NAS |
| The arena is single-threaded by assumption, as is every organ currently using this idiom. It is not safe under concurrent use within one process. | DECLARED LIMIT |