From 4a2fc2041efd4429a13601b3fe8b451ae64623aa Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Fri, 14 Aug 2026 23:41:13 +0200 Subject: [PATCH] fix: release the argv container (executable plan, Task 3) - program mode built the entry's `multi Text` of arguments and never freed it: the entry only BORROWS a parameter (never a `take`, and the drop tables never drop one), so the runtime that built the container owns it - dropped after the entry returns and after a trap alike -- the container outlives the unwind; `multi_free` recurses, so the argument strings go with it - one site covers both invocation shapes: `self_rc` picks the argv offset, it does not build a second container - measured: watch, run and the full MCP mix now report ZERO leaks under ASan -- the clean baseline the soak (Task 6) reads against - gates: oop-accept ALL CRITERIA MET, oop-e2e 71/0, wovm-test green, log-watcher 6/0 Co-Authored-By: Claude Opus 5 (1M context) --- docs/00-status.md | 8 +++++--- .../compiler/2026-08-14-logwatcher-executable.md | 15 +++++++++++---- runtime/src/main.c | 8 ++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/00-status.md b/docs/00-status.md index c9853f9..1acd0da 100644 --- a/docs/00-status.md +++ b/docs/00-status.md @@ -45,9 +45,11 @@ running", and every item below came from a measurement on the sample itself: **2 112 B → 64 B** and flat from 8 s to 20 s, the full MCP mix **21 312 B / 63 → 64 B / 1**, every handler flat from 2 to 6 requests. The 64 bytes left are item 3, on every path. -3. **The runtime leaks its own argv container** — 64 bytes in 1 allocation on - every run, `main.c`'s `multi Text` of arguments. It is now the ONLY leak the - sample reports in any mode. +3. ~~The runtime leaks its own argv container~~ — **done 2026-08-14**. The + entry only borrows its arguments, so `main.c` releases the container it + built, after the entry returns and after a trap alike. **All three modes + now report ZERO leaks under ASan** — `watch`, `run`, and the full MCP mix — + which is the clean baseline item 6's soak needs to read against. 4. **A stopping program does not stop** — `env.stopping()` sets a flag, but `net.accept`/`net.read` restart on `EINTR`, so a server parked in `accept` ignores SIGTERM and needs `kill -9`. diff --git a/docs/plan/compiler/2026-08-14-logwatcher-executable.md b/docs/plan/compiler/2026-08-14-logwatcher-executable.md index b9c7791..a281369 100644 --- a/docs/plan/compiler/2026-08-14-logwatcher-executable.md +++ b/docs/plan/compiler/2026-08-14-logwatcher-executable.md @@ -144,7 +144,7 @@ of the projection. `just log-watcher` 6/0. The image grew 35 893 → 46 137 bytes — the drops themselves. -### Task 3: The runtime's argv container has no owner +### Task 3 ✅: The runtime's argv container has no owner **Concept & reason:** program mode builds the `multi Text` of arguments in `runtime/src/main.c` and hands it to the entry method, which borrows it. Nobody @@ -155,9 +155,16 @@ its own allocation, and it pollutes every future ASan reading of the sample. The runtime owns that container and must release it after the entry returns, before the heap is torn down. -- [ ] Drop the argument container once the entry method has returned (both the - plain `wovm image.wob …` path and the single-binary path). -- [ ] `watch` under ASan reports **zero** leaks for a clean exit. +- [x] Drop the argument container once the entry method has returned — one + site covers both invocation shapes (`self_rc` only picks the argv + offset, it does not build a second container), and it runs after a trap + too: the container outlives the unwind. `multi_free` recurses, so the + argument strings go with it. +- [x] **All three modes report ZERO leaks under ASan**: `watch` and `run` over + eight seconds with a clean SIGTERM exit, and the full MCP mix (four + tools, twice each) with a clean exit. Gates: `just oop-accept` ALL + CRITERIA MET, `just oop-e2e` 71/0, `just wovm-test` green, + `just log-watcher` 6/0. ### Task 4: A stopping program must actually stop diff --git a/runtime/src/main.c b/runtime/src/main.c index 6d46888..3cf2e19 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -195,6 +195,14 @@ int main(int argc, char **argv) { /* the entry's return value IS the exit code (docs/plan/oop-vm/ * 08-builtin-surface.md's "Program entry"): 0..255, a trap is 1 */ int exit_code = rc == 0 ? (int)((uint64_t)ret & 0xFF) : 1; + /* the entry only BORROWS its arguments -- a parameter is never a `take`, + * and the drop tables never drop one -- so the runtime that built the + * container is the one that releases it, elements included. Without this + * the workload reported a leak on every path, in every mode, which is + * exactly the noise a soak measurement cannot afford. It runs before the + * heap is torn down, and after a trap too: the container outlives the + * unwind. */ + if (argv_val) wo_drop_kind(&VM.rt, WO_K_MULTI, argv_val); gc_pump(&VM); wo_vm_destroy(&VM); wo_module_free(&mod);