test: make the #39621 reset fixture report its fd layout and bind its dead port - #39659
test: make the #39621 reset fixture report its fd layout and bind its dead port#39659robobun wants to merge 3 commits into
Conversation
…he reset fixture A dead port taken from a connect() that bound its port automatically can be handed out again as the automatic local port of the connect() that is meant to be refused, which then connects to itself: about once in 13k connects on Linux (9 of 120000 measured, 0 of 120000 with localAddress). Both the fixture from #39621 and deadPort() in proxy-stress-helpers.ts built their port that way. The #39621 fixture only reaches the stale SO_ERROR read when the connect it opens from close() gets the accepted socket's fd number. It now reports that precondition (the number is closed when close() runs, and open again right after Bun.connect()) next to the outcome, so a change in the runtime's fd usage fails the test instead of leaving it passing without covering the bug.
|
Warning Review limit reached
Next review available in: 4 minutes Limit details: You’ve used all 5 included reviews currently available. You can run this review on demand instead of waiting. On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.
How can I continue?Run this review now using the option above, or comment You can also wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Comment |
|
Status: test-only change pushed, waiting for CI. Head is d80854a. The diff is now What it guards: the fd report was checked both ways. With the #39621 guard removed from This PR has no runtime change, so it cannot fail on main by design. Follow-up to #39621 and #39600. |
There was a problem hiding this comment.
LGTM — test-only hardening that fixes a rare self-connect flake and makes the #39621 fixture assert its own fd precondition.
What was reviewed:
Bun.connectacceptslocalAddress(parsed atsrc/runtime/socket/Listener.rs:1122) andnet.connectaccepts it via node compat, so both holders bind explicitly as intended.socket.fdandisWindowsare already used/imported insocket.test.ts; the JSON key insertion order inreportmatchesexpectedReportso the string comparison is stable.- The parenthesization of
??inside the ternary inproxy-stress-helpers.tsis a formatting no-op.
Extended reasoning...
Overview
Test-only follow-up to #39621 touching two files: test/js/bun/http/proxy-stress-helpers.ts (adds localAddress: "127.0.0.1" to the deadPort() holder plus a doc comment; one Prettier-style paren change) and test/js/bun/net/socket.test.ts (adds localAddress to the Bun.connect holder in the reset fixture, and makes the fixture report whether the fd-reuse precondition actually held via fstatSync probes, with the test asserting the full JSON line). No runtime code is touched.
Security risks
None. Changes are confined to test fixtures and a test helper; they add a connect option and read fd state via fstatSync inside a spawned subprocess.
Level of scrutiny
Low-to-medium. It's test infrastructure, but the changes rely on subtle kernel behavior (Linux ephemeral-port pool split between bind() and connect() auto-bind; POSIX lowest-free-fd semantics). I verified the concrete claims against the codebase: Bun.connect reads localAddress from options (Listener.rs:1122), socket.fd is a real property used elsewhere in the same test file, isWindows is already imported at line 13, and the report object's key insertion order (acceptedFdFreeInClose → acceptedFdReusedByConnect → outcome) matches the expectedReport literal so JSON.stringify produces identical strings. The Windows branch correctly skips the fd checks since socket handles there aren't fd numbers.
Other factors
The PR description documents empirical verification: 0 self-connects in 180k with localAddress vs 9-in-120k / 2-in-60k without; the fixture prints the expected fd flags both with and without the #39621 guard; 10/10 passes on main; proxy-stress-errors.test.ts (the deadPort() consumers) still passes. The change follows harness conventions (concurrent describe, tempDir, bunEnv/bunExe, all pipes drained via Promise.all, exit code asserted last). The new fd assertion is intentionally strict — if the runtime later opens an extra fd on that path, the test will fail loudly rather than silently losing coverage, which is the stated goal.
There was a problem hiding this comment.
LGTM — test-only de-flake with tighter precondition assertions.
What was reviewed:
localAddresson the holder connect: confirmedBun.connectreads it (Listener.rs:1122) andnet.connectsupports it natively; the explicit bind avoids the Linux self-connect corner.- fd-report additions:
socket.fdandisWindowsare already used in this file; JSON.stringify key order matches insertion order on both sides so the string compare is stable. - Windows path skips the fd checks (socket handles aren't fd numbers) and asserts only the outcome, matching the described intent.
Extended reasoning...
Overview
Test-only follow-up to #39621 touching two files: test/js/bun/http/proxy-stress-helpers.ts (adds localAddress: "127.0.0.1" to the deadPort() holder connect plus a doc-comment paragraph) and test/js/bun/net/socket.test.ts (same localAddress fix on the fixture's holder, plus fd-layout reporting via fstatSync so the test asserts its own precondition — that the connect opened from close() reuses the accepted socket's fd number). No runtime code is touched.
Security risks
None. Changes are confined to test fixtures and a test helper; no auth, crypto, or user-facing surface.
Level of scrutiny
Low-to-medium. This is a targeted flake fix (~1/13k self-connect on Linux) plus a test-tightening that turns a silently-vacuous test into a loud one if the runtime's fd allocation shifts. I checked that Bun.connect actually honors localAddress (src/runtime/socket/Listener.rs:1122), that socket.fd and isWindows are already imported/used in socket.test.ts, and that the JSON.stringify comparison is order-stable (both sides insert keys in the same sequence: acceptedFdFreeInClose → acceptedFdReusedByConnect → outcome). The Windows branch correctly reduces to { outcome } since checkFds gates both fd writes in the fixture.
Other factors
The PR description documents empirical verification in both directions: passes 10/10 on main, fails on the outcome (with fd flags still true) when the #39621 guard is removed from loop.c, and proxy-stress-errors.test.ts (the deadPort() consumers) still passes. The mechanism (Linux ephemeral-port allocation keeping bind()-assigned and connect()-assigned ports apart) is real and the doc comment explains it so the localAddress line won't be simplified away later. No prior reviewer comments to address.
There was a problem hiding this comment.
LGTM — test-only hardening of the #39621 fixture.
What was reviewed:
localAddressis a realBun.connectoption (src/runtime/socket/Listener.rs:1122); forcing an explicit bind avoids the Linux TCP self-connect flake path.socket.fdandisWindowsare already used/imported in this file; the fd precondition checks are gated off Windows where socket handles aren't fds.- JSON key insertion order in the fixture's
reportmatches theexpectedReportliteral on both platform branches, so the string comparison holds. - The
deadPort()change mentioned in the description was dropped in d80854a (deferred to #35400), so the diff is single-file as shown.
Extended reasoning...
Overview
This PR hardens a single regression fixture in test/js/bun/net/socket.test.ts that was added in #39621. Two independent changes:
- The "dead port" holder now passes
localAddress: "127.0.0.1"toBun.connect, forcing an explicitbind()for the local port. This avoids a ~1/13k Linux flake where the kernel hands the same ephemeral port to the refused connect's automatic bind, producing a TCP self-connect that succeeds instead of being refused. - The fixture now records whether its fd-reuse precondition actually held (
acceptedFdFreeInClose,acceptedFdReusedByConnectviafstatSync) and asserts on the full JSON report. If the runtime later opens an extra fd on this path, the test fails visibly instead of silently passing without exercising the staleSO_ERRORread it was written for.
No runtime code is touched.
Security risks
None. Test-only change to a subprocess fixture that connects to loopback ports it created itself.
Level of scrutiny
Low-to-medium. It's test-only, but the fixture depends on fairly subtle POSIX fd-numbering and Linux ephemeral-port semantics. I checked that localAddress is a supported Bun.connect option (Listener.rs), that socket.fd is already used elsewhere in this test file, that isWindows is already imported, and that the JSON property insertion order in the fixture matches the object literal order in expectedReport on both the Windows and non-Windows branches (so the string-equality assertion holds). The Windows branch correctly skips the fd checks since socket handles are not fds there.
Other factors
- The change directly implements two REVIEW.md test rules: it de-flakes without weakening the assertion (the outcome is still asserted exactly), and it adds a precondition assertion so the test can't pass for the wrong reason.
- The PR description originally covered a parallel
deadPort()fix inproxy-stress-helpers.ts, but the head commit d80854a dropped it and defers it to #35400. The current diff is single-file and self-contained. - The author reports 10/10 passes on main and verified the fixture still fails on the outcome when the #39621 guard is reverted, and that the fd flags flip when the arrangement is deliberately broken — both preconditions and the negative case were exercised.
Problem
SO_ERRORread it was written for when the connect it opens fromclose()gets the accepted socket's fd number. Nothing checked that. If the runtime starts to open one more fd on that path, the test keeps passing without covering the bug (checked: holding one extra fd at the right moment makes the fixture printECONNREFUSEDwith and without the usockets: skip the poll-error close for a socket a handler already closed #39621 guard).connect()that bound its port automatically. On Linux such a port can be handed out again as the automatic local port of the connect that is meant to be refused, which then connects to itself and the fixture seesopen. Measured with the fixture's construction: 9 self-connects in 120000 connects, so about 1 in 13k runs. This is the defect test(proxy-stress): return deadPort()'s bind(0) port, not the connect-autobound one #35400 describes fordeadPort()inproxy-stress-helpers.ts. That helper is left to test(proxy-stress): return deadPort()'s bind(0) port, not the connect-autobound one #35400; an earlier revision of this PR changed it too, and d80854a took that back.Fix
close()runs, and open again right afterBun.connect()returns (fstatSync). The test compares the whole JSON line. On Windows, where socket handles are not fd numbers, the fixture reports only the outcome, and the comment above the describe says so.localAddress: "127.0.0.1", so it binds its port itself. A port thatbind()handed out is not handed out again by a later automatic bind: 0 self-connects in 120000 with this change. This is the same property test(proxy-stress): return deadPort()'s bind(0) port, not the connect-autobound one #35400 uses (it returns abind(0)port). The fixture runs in a child process and cannot import the helper, so it keeps this one line.loop.cthe test prints{"acceptedFdFreeInClose":true,"acceptedFdReusedByConnect":true,"outcome":"ECONNRESET"}and fails on the outcome. With main it passes, 10 of 10 runs.Background
connect()without a local binding gets a port from the kernel, and a laterconnect()may reuse such a port as long as the four-tuple differs. A connect to its own port with the same tuple is a TCP self-connect, which succeeds. Ports thatbind()handed out are skipped by that allocator (test(proxy-stress): return deadPort()'s bind(0) port, not the connect-autobound one #35400 has the kernel details).close()takes the accepted socket's number.Notes
Other places build refused ports by hand as well (
closedPort()intest/js/sql/wire-frames.ts,deadRegistryHref()intest/cli/install/bun-audit.test.ts, bind-then-close in a few tests). A shared helper intest/harness.tswould be the place to carry the rule once #35400 has landed. Not done here.Measurements, release binary on Linux 6.17 (
ip_local_port_range32768 to 60999): holder bound byconnect(): 7 and 2 self-connects in two runs of 60000 (Bun.connectholder), 2 in 60000 (net.connectholder, thedeadPort()construction). Holder bound withlocalAddress: 0 in 60000, 0 in 60000, 0 in 60000. Theconnect()-bound ports seen were all even and thebind()-bound ones all odd, which is how the two allocators stay apart.no test proof · iteration 0 · Platform-specific test-only change; deferring to CI.