fix: Keep debug port connection alive for WebSocket passthrough#6100
Merged
fix: Keep debug port connection alive for WebSocket passthrough#6100
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
danlapid
approved these changes
Feb 18, 2026
Collaborator
danlapid
left a comment
There was a problem hiding this comment.
This is basically caused by deferred proxying optimizing your worker away.
Seems fine to support that.
When a WebSocket is obtained via the debug port and passed through a service binding response (WorkerEntrypoint.fetch()), the WebSocket would become invalid because the debug port TCP connection was being destroyed when the intermediate IoContext finished. This happened because DebugPortConnectionState was stored via context.addObject(), tying its lifetime to the IoContext. When the IoContext that called getEntrypoint() completed, the connection was destroyed, invalidating any WebSockets still using it. Fix: Make DebugPortConnectionState refcounted and ensure that: 1. WorkerdBootstrapSubrequestChannel holds a reference 2. RpcWorkerInterface returned by startRequest() has the connection attached via .attach(kj::addRef(*connectionState)) 3. WorkerdDebugPortClient stores the state as kj::Own (not IoOwn) This ensures the connection lives as long as any active WebSockets.
74fe1cd to
754a2d4
Compare
Open
5 tasks
anonrig
approved these changes
Feb 18, 2026
penalosa
added a commit
to cloudflare/workers-sdk
that referenced
this pull request
Feb 19, 2026
…port RPC Remove the HTTP fallback, eager body buffering, _client GC hack, and WebSocket special-casing from the dev-registry proxy workers. These workarounds existed because the workerd debug port would close connections prematurely when the initial subrequest completed. With the refcounted DebugPortConnectionState fix in workerd (cloudflare/workerd#6100), connections now survive as long as any response body or WebSocket is in use. Key changes: - Replace hasAssets/entryAddress with defaultEntrypointService and userWorkerService fields in WorkerDefinition/RegistryEntry - Route DOs and named entrypoints through userWorkerService (bypassing assets/vite proxy layer) instead of hardcoding core:user: prefix - Validate required registry fields in resolveTarget() to handle stale entries - Inline and delete getWorkerdServiceName() helper - Remove dead entryAddress field and fetchViaHttp code path - Remove DO WebSocket 501 error and DO body buffering workarounds
penalosa
added a commit
to cloudflare/workers-sdk
that referenced
this pull request
Feb 23, 2026
…port RPC Remove the HTTP fallback, eager body buffering, _client GC hack, and WebSocket special-casing from the dev-registry proxy workers. These workarounds existed because the workerd debug port would close connections prematurely when the initial subrequest completed. With the refcounted DebugPortConnectionState fix in workerd (cloudflare/workerd#6100), connections now survive as long as any response body or WebSocket is in use. Key changes: - Replace hasAssets/entryAddress with defaultEntrypointService and userWorkerService fields in WorkerDefinition/RegistryEntry - Route DOs and named entrypoints through userWorkerService (bypassing assets/vite proxy layer) instead of hardcoding core:user: prefix - Validate required registry fields in resolveTarget() to handle stale entries - Inline and delete getWorkerdServiceName() helper - Remove dead entryAddress field and fetchViaHttp code path - Remove DO WebSocket 501 error and DO body buffering workarounds
penalosa
added a commit
to cloudflare/workers-sdk
that referenced
this pull request
Mar 2, 2026
…port RPC Remove the HTTP fallback, eager body buffering, _client GC hack, and WebSocket special-casing from the dev-registry proxy workers. These workarounds existed because the workerd debug port would close connections prematurely when the initial subrequest completed. With the refcounted DebugPortConnectionState fix in workerd (cloudflare/workerd#6100), connections now survive as long as any response body or WebSocket is in use. Key changes: - Replace hasAssets/entryAddress with defaultEntrypointService and userWorkerService fields in WorkerDefinition/RegistryEntry - Route DOs and named entrypoints through userWorkerService (bypassing assets/vite proxy layer) instead of hardcoding core:user: prefix - Validate required registry fields in resolveTarget() to handle stale entries - Inline and delete getWorkerdServiceName() helper - Remove dead entryAddress field and fetchViaHttp code path - Remove DO WebSocket 501 error and DO body buffering workarounds
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.
Problem
When a WebSocket is obtained via the debug port and passed through a service binding response (from a
WorkerEntrypoint.fetch()), the WebSocket becomes invalid because the debug port TCP connection is destroyed when the intermediate IoContext finishes.This affects miniflare's dev-registry, which uses the debug port to proxy service bindings between workers running in different workerd instances.
Root Cause
DebugPortConnectionStatewas stored viacontext.addObject(), tying its lifetime to the IoContext. When the IoContext that calledgetEntrypoint()completed, the connection was destroyed, invalidating any WebSockets still using that connection.Fix
Make
DebugPortConnectionStaterefcounted (kj::Refcounted) and ensure that:WorkerdBootstrapSubrequestChannelholds a reference to the connectionRpcWorkerInterfacereturned bystartRequest()has the connection attached via.attach(kj::addRef(*connectionState))WorkerdDebugPortClientstores the state askj::Own(notIoOwn)This ensures the connection lives as long as any active WebSockets.
Testing
Added a test that:
WorkerEntrypointthat gets a WebSocket via debug port and passes it throughAll existing tests continue to pass.