Repository navigation
Conversation
When Bun.serve({ http3: true, tls }) successfully binds the TCP listen
socket but the subsequent UDP bind for QUIC fails, the error path called
deinit() which destroys the uws App without running TemplatedApp::close().
The TCP listen socket was still linked into the socket group at that point,
tripping the head_listen_sockets == NULL assertion in us_socket_group_deinit
(and leaking the fd in release builds).
The retry path already closed the listener; extend that to the final
failure path so the socket is unlinked before the app is destroyed.
|
Updated 11:33 AM PT - Jun 20th, 2026
❌ @robobun, your commit d101c63 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 32539That installs a local version of the PR into your bun-32539 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe error-handling path in ChangesH3 UDP Bind Failure: Cleanup Fix and Test
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/bun/http/serve-http3.test.ts`:
- Around line 1095-1100: The success path where console.log("unexpected
success") is called does not actually fail the test or exit, which allows a live
server to remain and hang until timeout. Add a process.exit(1) call or throw an
error immediately after the console.log("unexpected success") statement to
terminate the test deterministically with a non-zero exit code instead of
allowing execution to continue.
🪄 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: df7bc14a-927f-444b-8436-195e52163592
📒 Files selected for processing (2)
src/runtime/server/mod.rstest/js/bun/http/serve-http3.test.ts
There was a problem hiding this comment.
LGTM — minimal error-path reorder so the TCP listener is closed before deinit when the H3 UDP bind fails.
Extended reasoning...
Overview
This PR reorders a single if nesting in NewServer::listen() (src/runtime/server/mod.rs, ~6 effective lines). Previously, when the H3 UDP bind failed, the already-bound TCP listen socket was only take()'d and close()'d on the retry path (attempt < max_attempts); on the final-failure path it was left linked into the uws socket group, tripping a debug assertion in us_socket_group_deinit and leaking the fd in release. The fix hoists the listener.take() + close() above the retry guard so it runs unconditionally, then gates only the continue on attempt < max_attempts. Retry semantics are preserved exactly (still requires both Some(ls) and attempt < max_attempts). A new subprocess regression test in serve-http3.test.ts pre-binds a UDP port and asserts Bun.serve({ http3: true }) throws cleanly with "Failed to listen on UDP port" and exits 0.
Security risks
None. This is pure resource cleanup on an error path. No auth, crypto, input parsing, or user-controlled data flow is touched. The success path is unchanged.
Level of scrutiny
Low. The change is confined to the H3-UDP-bind-failure branch, which is already a throw-then-deinit error path. The reorder is mechanical and easy to verify by inspection: the only behavioral delta is that the listener is now closed when attempt >= max_attempts, which is exactly the stated fix. The http1: false case (where listener is already None) falls through unchanged.
Other factors
The bug-hunting system found no issues. CodeRabbit's only suggestion (fail-fast on unexpected test success) was applied in d30d299 and confirmed resolved. No CODEOWNERS rules cover these files. The test follows existing conventions in the file (subprocess via bunExe() -e, combined {stdout, stderr, exitCode} assertion). The robobun build failures on 6c101f9 span every platform including build-cpp (which this PR doesn't touch), pointing to an infra/base-commit issue rather than this change; CI will gate the merge regardless.
|
CI failures across both runs are unrelated darwin-aarch64 flakes that also appear on other recent builds:
284/286 jobs passed on both runs, including all 20 ASAN shards. |
|
Stale PR review: keep open, rework. The bug is real and still on main. When The test is not valid as written, so the PR needs a rework before it can merge:
#32543 fixes the same crash one layer down in |
|
Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-06-20, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main. |
Fuzzilli found an assertion failure in
us_socket_group_deinit:Root cause
When
Bun.serve({ http3: true, tls })successfully binds the TCP listen socket but the UDP bind for QUIC fails on the same port, the error path throws and then callsdeinit(), which destroys the uws App (~TemplatedApp->httpContext->free()->us_socket_group_deinit). That path never runsTemplatedApp::close(), so the TCP listen socket is still linked intogroup->head_listen_socketswhen the group is torn down, tripping the assertion in debug/ASAN builds and leaking the fd + listen socket struct in release.The port-0 retry path already closed the TCP listener; the fix extends that to the final failure path so the socket is unlinked before
deinit()destroys the app.Repro
Fingerprint:
f384476a45a89910