`emulate()` never disposes the platform proxy, so every build leaks a `workerd`
A pull request for this has already been merged.
- #16754 by @ottomated — merged
- Dominant language
- JavaScript
- Stars
- 20.8k
- Forks
- 2.3k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 156
Description
Describe the bug
Describe the bug
@sveltejs/adapter-cloudflare's emulate() calls wrangler's getPlatformProxy(), which
starts a miniflare workerd child process. The adapter never calls proxy.dispose() — the
string dispose does not appear anywhere in the package.
The adapter returns only { platform } from emulate(), and the proxy is captured in a closure, so nothing downstream can dispose it either — a userland wrapper cannot fix this without reimplementing emulate().
Why the leaked ports are not harmless
A wildcard bind does not reserve a port. The kernel's wildcard ephemeral allocator will hand out 0.0.0.0:N while the leaked workerd still holds 127.0.0.1:N; both binds coexist, and every connection goes to the most specific matching listener. So an unrelated program that binds 0.0.0.0:N and then dials 127.0.0.1:N reaches the leaked workerd instead of itself and blocks.
We ran into this but running scala tests: sbt.ForkTests binds its fork handshake with new ServerSocket(0) (wildcard) and its child dials 127.0.0.1 so on a machine where a SvelteKit app had been built, an unrelated Scala repo's test runner sat in accept() until the leaked workerd's idle timeout.
Why it is invisible
src/utils/fork.js carries the comment:
Runs a task in a subprocess so any dangling stuff gets killed upon completion.
It does not run a subprocess. It runs a node:worker_threads Worker, which shares the process so a child process the task spawned is not killed when the worker finishes, and is not killed when the worker is unref'd and torn down at process exit either. It is inherited by init.
Suggested fix
Emulatorgains an optional teardown.emulate()returns{ platform }and the type has no disposal hook, so an adapter that acquires a resource has no way to release it.
Addingdispose?: () => MaybePromise<void>toEmulatorand awaiting it at the three call sitessrc/core/postbuild/prerender.jsafter prerendering, and the dev and preview servers on close would make this expressible.adapter-cloudflareimplements it, holding thegetPlatformProxy()result and callingproxy.dispose().
Reproduction
Any SvelteKit app on @sveltejs/adapter-cloudflare with export const prerender = true:
npm ci
lsof -c workerd -a -d cwd -Fn | grep -c "$PWD" # 0
npm run build
lsof -c workerd -a -d cwd -Fn | grep -c "$PWD" # 1
The leaked process:
$ ps -o pid=,ppid=,command= -p <pid>
25193 1 .../node_modules/@cloudflare/workerd-darwin-arm64/bin/workerd serve --binary ...
$ lsof -nP -p <pid> -a -iTCP -sTCP:LISTEN
workerd 25193 ... TCP 127.0.0.1:60893 (LISTEN)
workerd 25193 ... TCP 127.0.0.1:60894 (LISTEN)
Bisected against the other steps of the same CI script on a clean clone — vitest run, svelte-kit sync, svelte-check, playwright test --list all leave the count unchanged; vite build is the step that adds one.
Logs
System Info
- `@sveltejs/adapter-cloudflare` 7.2.4 and 7.2.9; also present in `8.0.0-next.6`, which still contains no `dispose`
- `@sveltejs/kit` 2.x
- `vite` 8
- macOS (arm64); the mechanism is not platform-specific
Severity
annoyance
Additional Information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/utils/fork.js and src/core/postbuild/prerender.js, then trace the dev and preview server close paths and adapter-cloudflare's emulate() entry point. Reproduce with a prerendered build and inspect workerd processes before and after. Done means the platform proxy is disposed across build, dev, and preview lifecycles without leaving workerd processes behind.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, vite
- Domain
- build-system, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100