Skip to content

web: release each Promise executor so written data can be freed - #150

Open
gjija wants to merge 1 commit into
tailscale:mainfrom
gjija:web-release-promise-executor
Open

gjija wants to merge 1 commit into
tailscale:mainfrom
gjija:web-release-promise-executor

Conversation

@gjija

@gjija gjija commented Oct 13, 2026

Copy link
Copy Markdown

The web demo keeps every chunk it sends in memory until the tab is closed.

makePromise never releases the js.Func it creates for the Promise executor, so syscall/js keeps it alive together with the bytes of each write. This change releases it right after the Promise is created.

TestWriteFreesPayload fails without the change and passes with it. It only builds for js/wasm, so CI and plain go test don't run it:

GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec" ./web/

makePromise registered its executor with js.FuncOf and never released
it, so syscall/js kept every executor, the function it runs, and
whatever that function captured for the life of the page. For a conn's
write, that is a copy of every chunk written. In headless Chrome 155
on macOS, sending 256 MiB from the page in its usual 64 KiB writes
left its WebAssembly memory at about 450 MiB; with this change it
stays under 30 MiB.

The Promise constructor calls the executor once, before it returns,
so release it right after.

The new TestWriteFreesPayload builds only for js/wasm. It fails
without this change and passes with it:

    GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec" ./web/

This branch has not been deployed

No deployments
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.

1 participant