Repository navigation
Fix #743's leak: a client that goes closes the runtime's connection whole - #794
Merged
Merged
Conversation
…on whole A client's end done, the front door only half-closed the runtime's. workerd keeps a WebSocket connection whose client closed (half-closed, or with a Close frame), so each such pass waited on the runtime for good: a thread and two sockets per closed socket, the shell's own watch among them. A repro of reloads, each closing two WebSockets, held 28,207 threads and 56,413 fds in ten minutes, and then connections were reset (module loads failed ERR_CONNECTION_RESET). Now, the client gone, the runtime's connection is closed whole, as a proxy does once its client goes. Test: a client that goes, from a runtime that keeps a 101'd connection open and silent after it: the pass ends (before: it never did). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alexlwn123
approved these changes
Oct 31, 2026
alexlwn123
left a comment
Collaborator
There was a problem hiding this comment.
Pass at a233fcd64fb4d0eee0cf3eb690b60c8f0baa4a0c.
The full runtime shutdown after client EOF or failure wakes the downstream copy and lets the pass finish even when workerd keeps its WebSocket open. The change is appropriately small, and the half-close tradeoff is explicit; I found no repository caller relying on that behavior and no blocking regression.
Validation: cargo test -p fragment-devstack --lib passed all 29 tests, including the new disconnect regression. GitHub check and all four e2e shards are still pending; full e2e was not run locally. The referenced external engineering-style file is unavailable on this machine.
futurepaul
added a commit
that referenced
this pull request
Oct 31, 2026
… into the front door's request log Its pass keeps the log's framing; the runtime's end is closed whole once the client's is done, and #794's test runs through this pass, its log beside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes a leak #743 brought in.
What leaked: each connection a client closed while the user runtime kept its end open: a pass thread and two sockets, held for good, in the node process. workerd keeps a WebSocket connection open after its client closes, whether the client half-closes or sends a Close frame (checked straight against workerd; it closes at once after a plain answer, and after a stream's end). #743's front door relayed the client's close as a half-close of the runtime's connection, then waited on the runtime, which never closed. So every WebSocket a page closed leaked: the shell's own
/api/fragments/watchon each page close or reload, and the chats' sockets.How fast: in a repro of page reloads through the front door, each load closing two WebSockets (4 Chromes, about 12 loads a second), the node process held 28,207 threads and 56,413 fds after about ten minutes, and workerd 58,484 fds. From then on, connections were reset: module loads failed
net::ERR_CONNECTION_RESET, so pages never started. An e2e shard leaks far fewer (one or two per page load), but everyxtask devsession and e2e run has accumulated them since #743.Fix: when the client's end is done (closed, or failed), the front door closes the runtime's connection whole (
Shutdown::Both), as a proxy does once its client goes. The pass then ends and lets both sockets go. The trade-off, stated beside it: a client that half-closes and waits for its answer gets none. No client here does (noShutdown::Writeanywhere in our code outside front.rs).Test:
a_client_that_goes_ends_its_pass_though_the_runtime_stays. A runtime answers a 101, then keeps the connection open and silent after the client goes (as workerd does). The client takes the 101 and closes. The pass must end; its wait is bounded only so a failure reports instead of hanging.Also run: devstack's lib tests and clippy with
-D warnings, and--test debug_terminal(a real node, started and reloaded through the front door).🤖 Generated with Claude Code