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) <noreply@anthropic.com>
This commit is contained in:
parent
ef74d157b6
commit
4a2fc2041e
3 changed files with 24 additions and 7 deletions
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue