fix(net): destroy socket on native close to prevent server.close() hang#28732
fix(net): destroy socket on native close to prevent server.close() hang#28732
Conversation
There was a problem hiding this comment.
Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.
Once credits are available, push a new commit or reopen this pull request to trigger a review.
|
Updated 10:41 AM PT - Apr 1st, 2026
❌ @autofix-ci[bot], your commit 57c675f has 6 failures in
🧪 To try this PR locally: bunx bun-pr 28732That installs a local version of the PR into your bun-28732 --bun |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughSchedule deferred destruction via Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
@robobun The darwin failures timeouts are not related to this change and seem to be happening on recent prs as well. |
|
@robobun Can you try again ? |
When a native socket closes, the close handlers push null to the readable stream but never call destroy(). If the readable side is paused or the writable was never ended, autoDestroy never fires. The socket stays in a zombie state (_handle=null, destroyed=false), server._connections never decrements, and server.close() hangs forever. Add process.nextTick(destroyNT, self) to all three close handlers (SocketHandlers, ServerHandlers, SocketHandlers2) so the socket always transitions to destroyed=true after native close, matching Node.js behavior.
a66bd4e to
3f6f199
Compare
There was a problem hiding this comment.
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/regression/issue/13184.test.ts`:
- Around line 9-13: Remove the inline explanatory paragraph in the test body of
issue/13184.test.ts (the lines starting "When the native socket closes, ..."
through the explanatory sentences) and leave only the single issue URL/reference
line(s) that point to the bug; i.e., trim the prose while keeping the minimal
issue reference so the test matches the repo regression-test style and contains
no extra context comments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c6554c16-1575-42b6-86bb-8dc0194acea4
📒 Files selected for processing (1)
test/regression/issue/13184.test.ts
3f6f199 to
d64d8b2
Compare
There was a problem hiding this comment.
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/regression/issue/13184.test.ts`:
- Around line 16-20: The test currently verifies socket destruction only
indirectly; add an explicit assertion using the Jest expect helper to validate
socket.destroyed after handler runs: import expect (or ensure the testing
framework's expect is available) at the top of the file and inside the
net.createServer callback add an assertion such as
expect(socket.destroyed).toBeTruthy() (or toBe(true)) alongside the existing
socket.on("close", onServerSocketClose) to fail fast and give clearer
diagnostics; update references in the callback where socket, handler,
onServerReady, and onServerSocketClose are used.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9c4f3b98-2ea0-44bd-8cc9-7bea5bd6047e
📒 Files selected for processing (1)
test/regression/issue/13184.test.ts
d64d8b2 to
2290256
Compare
Problem
When a native socket closes, Bun's close handlers (
SocketHandlers,ServerHandlers,SocketHandlers2) pushnullto the readable stream but never calldestroy(). If the readable side is paused or the writable was never ended,autoDestroynever fires. The socket stays in a zombie state (_handle=null,destroyed=false),server._connectionsnever decrements, andserver.close()hangs forever.Fixes #28731, #13184, #19563, #23648
Root Cause
In
src/js/node/net.ts, all three close handlers rely onautoDestroyto eventually call_destroy(), which decrementsserver._connectionsand emits thecloseevent. ButautoDestroyonly fires after both the readable side ends (endEmitted) and the writable side finishes. When the readable is paused or data hasn't been consumed, the readableendevent never emits, soautoDestroynever triggers.Fix
Add
if (!self.destroyed) process.nextTick(destroyNT, self)at the end of each close handler.destroyNTalready exists (line 94) and simply callsself.destroy(err). Usingprocess.nextTickensures the current close handler finishes before destruction runs, matching Node.js behavior.Verification
USE_SYSTEM_BUN=1 bun test test/regression/issue/13184.test.ts: 2 pass, 6 fail (timeouts)bun bd test test/regression/issue/13184.test.ts: 8 pass, 0 failtest/js/node/net/tests: no new failures (all failures pre-existing)