Skip to content

fix: reclaim isolate host RSS on oneshot user-worker retirement - #722

Open
samvallad33 wants to merge 1 commit into
supabase:mainfrom
samvallad33:fix/edge-runtime-719-retire-rss
Open

fix: reclaim isolate host RSS on oneshot user-worker retirement#722
samvallad33 wants to merge 1 commit into
supabase:mainfrom
samvallad33:fix/edge-runtime-719-retire-rss

Conversation

@samvallad33

@samvallad33 samvallad33 commented Aug 27, 2026

Copy link
Copy Markdown

Oneshot retirement already drives activeUserWorkersCount to 0. Host RSS still grows ~2.5 MiB per isolate.

The worker is gone from the pool; the isolate's host pages are not. V8 leaves committed heap on the pinned worker thread, and that thread's allocator never returns those pages (mi_collect / malloc_trim / malloc_zone_pressure_relief). EdgeRuntime.miCollect() only purges the main worker.

This decommits the isolate on termination and drops host allocator pages in DenoRuntime::drop after JsRuntime dispose, on the same thread that owned the isolate.

test_oneshot_retirement_drops_user_worker checks the pool count hits 0 after oneshot shutdown. It does not measure RSS.

Fixes #719

Retired workers already leave the pool (activeUserWorkersCount hits 0) while V8/host pages stay resident on the worker thread. Decommit the isolate and purge that thread's allocator after JsRuntime drop.
@kallebysantos
kallebysantos self-requested a review August 29, 2026 02:22
@kallebysantos kallebysantos self-assigned this Aug 29, 2026
@samvallad33
samvallad33 marked this pull request as ready for review August 30, 2026 03:03
@da1suk8

da1suk8 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

I built both main (9fcc152) and this branch and measured RSS across oneshot retirements, following the methodology from #719 — a realistic handler that exercises the module graph (npm:@supabase/supabase-js + npm:zod) and makes an outbound fetch (kept hermetic by targeting the runtime's own health endpoint), driven sequentially. As the issue notes, a trivial handler does not reproduce the growth.

Environment: linux/arm64 (Docker Desktop on macOS, 64 GiB VM), container capped at 3 GiB, start --main-service ./examples/main --policy oneshot, memoryLimitMb: 150 per worker (the example main's default). RSS = VmRSS of PID 1 in the container; worker counts from /_internal/metric.

Results after 300 retirements (sampled every 25; activeUserWorkersCount was 0 and the main-worker JS heap stayed flat at ~11 MiB at every sample, matching #719):

RSS @ warm RSS @ 300 retirements growth per retirement
main @ 9fcc152 189.6 MiB 1292.9 MiB +1103.3 MiB ~3.7 MiB
this PR 129.4 MiB 301.1 MiB +171.7 MiB ~0.6 MiB

Tail slope (retirements 150→300): ~837 KiB/retirement on main vs ~201 KiB/retirement with this PR. All 600 requests across both runs returned 200.

Latency cost of the reclaim: 50 sequential requests post-warm-up averaged 93 ms/req (main) vs 97 ms/req (this PR) — within run-to-run noise for a single pass in my setup.

Two observations:

  • Growth per retirement scales with workload weight: with this heavier module graph, main retains ~3.7 MiB/retirement versus the ~2.46 MiB reported in the issue — consistent with its note that cheap handlers don't reproduce it.
  • Some residual growth remains with this PR (~0.6 MiB/retirement overall, ~0.2 MiB at the tail). It may be allocator high-water behavior; per_worker policy (which the issue reports grows ~2× faster) isn't covered by my run or by the PR's test, so it may be worth checking as a follow-up.

Harness (repro function + measurement script) and raw CSVs: https://gist.github.com/da1suk8/53947f7182ab4aa9f4c5799ef4e67bd6 — happy to run other configurations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retired user workers are not fully reclaimed — RSS grows ~2.5 MiB per retirement with activeUserWorkersCount: 0 (v1.74.3, policy = oneshot)

3 participants