Skip to content

fix(proxy): settle the pending read when an upstream socket closes - #4158

Merged
kwakayama merged 1 commit into
mainfrom
fix/proxy-websocket-read-leak
Aug 25, 2026
Merged

kwakayama merged 1 commit into
mainfrom
fix/proxy-websocket-read-leak

Conversation

@kojiwakayama

Copy link
Copy Markdown
Contributor

Description

UpstreamWebSocket#pump awaits reader.read() for the next frame:

while (true) {
  const { done, value } = await reader.read();   // <- stays pending
  if (done) return;
  this.onmessage?.(new MessageEvent("message", { data: value }));
}

Closing the stream resolves closed and fires onclose, but it does not settle a read() that is already awaiting. The read outlives the connection it belongs to.

In the proxy that leaks one pending read per closed socket. In CI it surfaces as an intermittent failure from src/proxy/websocket-client.test.ts under --trace-leaks --parallel:

upstream WebSocket client => ./src/testing/bdd.ts:631:11
error: Leaks detected:
  - An async operation to receive the next message on a WebSocket was started
    in this test, but never completed.
    at op_ws_next_event (ext:core/00_infra.js:256:13)
    at pull (ext:deno_websocket/02_websocketstream.js:253:34)

Why this is worth fixing now rather than filing

It is not confined to the proxy. This exact failure took out coverage shard 8/8 on two separate pull requests that touch neither the proxy nor WebSockets — one changing only src/transforms/import-rewriter/, one changing only src/modules/server/. Every occurrence costs a shard re-run and, worse, invites the reading that the shard is "just flaky" when a real regression eventually lands there.

Fix

Hold the reader and cancel it in #close. Cancelling settles the pending read, #pump unwinds, and releaseLock runs. The #settled guard already prevents re-entry from the #fail path.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Checklist

  • I have added tests that prove my fix is effective

Testing

Chasing the flake by re-running was not going to prove anything, so the regression guard is deterministic instead. It uses the client's existing UpstreamWebSocketStreamFactory seam with a stream whose readable never produces a frame and whose close() only resolves closed, which is precisely what a real WebSocketStream does. The test then asserts the read was settled.

Verified to fail without the fix:

AssertionError: Values are not equal:
  closing must settle the read, or the operation outlives the connection
deno task test:file src/proxy/     51 passed (533 steps) | 0 failed
deno check src/proxy/main.ts
deno task lint:anti-slop / lint:test-semantic-dispositions / lint:sanitizer-baseline
deno fmt --check, deno lint

Found while shepherding the issue-triage PRs (#4147, #4148, #4149, #4150, #4152, #4154), where it twice failed a shard on unrelated changes.

UpstreamWebSocket#pump awaits reader.read() for the next frame. Closing
the stream resolves `closed` and fires onclose, but it does not settle a
read that is already awaiting, so the operation outlived the connection
it belonged to.

In the proxy this leaks one pending read per closed socket. In CI it
surfaces as an intermittent "Leaks detected: an async operation to
receive the next message on a WebSocket was started in this test, but
never completed" from src/proxy/websocket-client.test.ts under
--trace-leaks --parallel. It has failed a coverage shard on pull requests
that touch neither the proxy nor WebSockets.

Hold the reader and cancel it in #close, which settles the pending read
and lets #pump unwind and release its lock.

Claude-Session: https://claude.ai/code/session_01TNbcqUy64goaeCShfjbRmf

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kojiwakayama has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 36 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d2f6e04-43a2-4b62-873e-c52fb31f42e4

📥 Commits

Reviewing files that changed from the base of the PR and between dedea29 and 08aced3.

📒 Files selected for processing (2)
  • src/proxy/websocket-client.test.ts
  • src/proxy/websocket-client.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

📦 Client bundle boundary

Entrypoint Modules Source size Server leaks
src/index.client.ts 330 1972 KiB ✅ 0

A server module in a client graph aborts hydration in the browser. New leaks fail CI; known leaks are tracked in scripts/lint/client-bundle-baseline.json to burn down.

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kwakayama

Copy link
Copy Markdown
Contributor

@codex review

Please review the exact current head . All existing review threads are resolved. Please report any remaining findings against this SHA.

1 similar comment
@kwakayama

Copy link
Copy Markdown
Contributor

@codex review

Please review the exact current head . All existing review threads are resolved. Please report any remaining findings against this SHA.

@kwakayama

Copy link
Copy Markdown
Contributor

@codex review

Please review the exact current head 08aced3. All existing review threads are resolved. Please report any remaining findings against this SHA.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 08aced3b00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kwakayama kwakayama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved exact head 08aced3 after an exact Codex no-findings review and confirmation that all review threads are resolved. Protected checks and the merge queue remain authoritative.

@kwakayama
kwakayama added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit 4e106d6 Aug 25, 2026
59 checks passed
@kwakayama
kwakayama deleted the fix/proxy-websocket-read-leak branch August 25, 2026 14:40
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.

2 participants