tunnel: add pause/resume flow control and disconnect stream cleanup#19
Merged
tunnel: add pause/resume flow control and disconnect stream cleanup#19
Conversation
Two related fixes for ERR_INCOMPLETE_CHUNKED_ENCODING on large assets: 1. Flow control (pause/resume) When the relay's dataCh buffer fills up it now sends a `pause` message. Handle it by calling socket.pause() on the active TCP socket so the kernel stops receiving from the target server. After the relay drains the buffer it sends `resume`; call socket.resume() to restart reading. This propagates TCP backpressure end-to-end without dropping data. 2. Stream cleanup on WebSocket disconnect (#onDisconnect) Active TCP sockets were not closed when the WebSocket disconnected, leaving them open until the OS GC'd them. Add #closeStreams() call in #onDisconnect() to close all active sockets immediately on disconnect. New types in types.ts: StreamPauseMessage, StreamResumeMessage added to RelayMessage union so the switch in client.ts is exhaustive. New file: tests/chunked-server.mjs — HTTPS test server that serves configurable-size chunked responses (/?size=1mb … 200mb). Used to reproduce and validate the fix end-to-end in a real browser via tunnel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
joelgwebber
approved these changes
Apr 16, 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.
Problem
Large HTTPS assets loaded through the tunnel CONNECT path were being truncated mid-transfer, causing
ERR_INCOMPLETE_CHUNKED_ENCODINGin the browser. Two bugs contributed:Changes
1. Flow control:
pause/resumewire messagesWhen the relay's
dataChbuffer fills, it now sends apausemessage. Handle it by callingsocket.pause()on the active TCP socket to stop the kernel receiving from the target server. When the relay drains the buffer it sendsresume; callsocket.resume()to restart.2. Stream cleanup on WebSocket disconnect
#onDisconnect()was calling#abortInflight()(cancels pending HTTP requests) but not#closeStreams()(closes active TCP sockets). Add the missing call:3. Test harness:
tests/chunked-server.mjsHTTPS test server that serves configurable-size chunked JS responses:
Serves
/?size=<N>(e.g.1mb,50mb,200mb) as chunked transfer encoding with noContent-Length, plus a sentinelwindow.__BUNDLE_LOADED = trueat the end. Used to reproduce and validate the fix end-to-end in a real browser via tunnel.Proof of bug / fix
The Go-side regression test (
TestRelayFlowControlSlowConsumerin the companion mn PR) definitively proves both sides:Before fix (close-on-overflow, dataCh=4 slots):
After fix (pause/resume, dataCh=4 slots, slow 5ms consumer):
The slow consumer (5ms per read) simulates a browser whose TCP receive window is backed up. With the old code only 5 chunks arrive before overflow closes the stream. With flow control all 20 arrive because the client pauses reading from the target server until the relay drains.