Skip to content

fix: close connection after async chunked response with Connection: close#28390

Open
bilby91 wants to merge 4 commits into
oven-sh:mainfrom
crunchloop:claude/fix-async-chunked-connection-close
Open

fix: close connection after async chunked response with Connection: close#28390
bilby91 wants to merge 4 commits into
oven-sh:mainfrom
crunchloop:claude/fix-async-chunked-connection-close

Conversation

@bilby91

@bilby91 bilby91 commented Mar 21, 2026

Copy link
Copy Markdown

Summary

  • Fixes a bug where http.Server never closes the TCP connection after an async chunked response when Connection: close is set
  • The bug affects any server that responds asynchronously (setTimeout, proxy, database query, etc.) with chunked transfer encoding — the response body is sent fully (including the 0\r\n\r\n terminator) but the socket FIN is never sent
  • Sync responses (handler responds before returning) are not affected

Root cause

In uWebSockets' HttpResponse::internalEnd(), when writeHead() is called outside the initial HTTP request handler (async), it corks the socket. When end() later runs:

  1. The terminating chunk is written to the cork buffer
  2. markDone() clears HTTP_RESPONSE_PENDING
  3. isCorked() returns true → takes the else branch → calls uncork() to flush data
  4. Returns without checking HTTP_CONNECTION_CLOSE — never calls shutdown() + close()

In the sync case, HttpContext's onData handler performs the close check after uncorking. But in the async case, that handler already returned before the response was sent.

Fix

Added a connection-close check after uncork() in both the chunked and non-chunked paths of internalEnd(). After uncorking flushes the data, we check HTTP_CONNECTION_CLOSE, !HTTP_RESPONSE_PENDING, and getBufferedAmount() == 0 — if all conditions are met, we call shutdown() + close().

Test plan

  • New test: async chunked response with Connection: close (setTimeout pattern)
  • New test: proxy with createConnection piping chunked upstream response
  • Both tests fail with USE_SYSTEM_BUN=1, pass with bun bd test
  • Existing tests pass: serve.test.ts (190 pass), 7471.test.ts (13 pass), transfer-encoding tests, backpressure tests

🤖 Generated with Claude Code

…lose

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 04300f48-c0db-4a1f-9400-1a99a83b738a

📥 Commits

Reviewing files that changed from the base of the PR and between 1792019 and eee3974.

📒 Files selected for processing (1)
  • test/js/node/http/node-http-async-chunked-close.test.ts

Walkthrough

Perform immediate connection-close handling after uncorking in both chunked and full-body response end paths; add an integration test verifying an HTTP/1.1 server sending delayed chunked responses closes the TCP connection when a client sends Connection: close.

Changes

Cohort / File(s) Summary
HTTP response end logic
packages/bun-uws/src/HttpResponse.h
Add identical post-uncork() connection-close checks in HttpResponse<SSL>::internalEnd for both chunked/terminating-chunk and full-body paths: if HTTP_CONNECTION_CLOSE is set, HTTP_RESPONSE_PENDING not set, and getBufferedAmount() == 0, call shutdown() and close(); in the non-chunked path return immediately after close() to avoid further socket use.
Integration test: async chunked close
test/js/node/http/node-http-async-chunked-close.test.ts
Add a test that starts a node:http server which writes a delayed chunked response, connects with a raw node:net socket sending Connection: close, collects data until end, asserts received chunk contents and successful completion, and always closes the server.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main bug fix: closing connections after async chunked responses with Connection: close header.
Description check ✅ Passed The description comprehensively covers the Summary, Root Cause, Fix, and Test Plan sections, exceeding the template requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/js/node/http/node-http-async-chunked-close.test.ts`:
- Around line 44-116: The test "proxy with createConnection closes socket after
chunked upstream response" currently calls backend.close() and proxy.close()
after the assertions, which can leak servers if an assertion or await throws;
wrap the execution that starts/listens and the socket request/response logic in
a try/finally and move backend.close() and proxy.close() into the finally block
(referencing the backend and proxy server variables) so both servers are always
closed even on failure.
- Around line 5-42: The test "http.Server closes connection after async chunked
response with Connection: close" may leak the server if an error/timeout occurs
before server.close(); wrap the server lifecycle in a try/finally: call
server.listen(...) and run the client logic inside the try block, and ensure
server.close() is invoked in the finally block (referencing the local variable
server and the test body) so the server is always closed even on exceptions or
timeouts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6476bf28-395a-4949-8543-b691077a9e42

📥 Commits

Reviewing files that changed from the base of the PR and between db2156e and 0ac4cf5.

📒 Files selected for processing (2)
  • packages/bun-uws/src/HttpResponse.h
  • test/js/node/http/node-http-async-chunked-close.test.ts

Comment thread test/js/node/http/node-http-async-chunked-close.test.ts Outdated
Comment thread test/js/node/http/node-http-async-chunked-close.test.ts Outdated
Address CodeRabbit review: wrap server cleanup in try/finally for
exception safety. Remove proxy/createConnection test that depends on
changes from another branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/js/node/http/node-http-async-chunked-close.test.ts`:
- Around line 22-34: The test's socket created by net.createConnection can emit
'error' and currently has no handler, causing hangs; add an error listener on
the socket returned by net.createConnection(...) that resolves the surrounding
promise immediately with pass: false and a body containing the error message
(and any collected chunks), then destroy the socket; ensure this handler lives
alongside the existing socket.on("data"), socket.on("end"), and
socket.setTimeout(...) handlers so connection failures fail fast and clean up
resources.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9e1f8a9d-1608-4f2b-9f41-9e33c5f8b2ce

📥 Commits

Reviewing files that changed from the base of the PR and between 0ac4cf5 and 0b70714.

📒 Files selected for processing (1)
  • test/js/node/http/node-http-async-chunked-close.test.ts

Comment thread test/js/node/http/node-http-async-chunked-close.test.ts
bilby91 and others added 2 commits March 22, 2026 23:23
…r-free

The non-chunked async uncork path called shutdown() + close() but did
not return immediately afterward, allowing execution to fall through
to `return success` where `this` is a dangling pointer. Add `return
true` after close() to match the chunked path pattern.

Found during review of oven-sh#28397 (robobun).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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