Conversation
…se test The test raced a 308 redirect chain against AbortSignal.timeout(150) in its success case, and a 200ms server delay against the same 150ms timeout in its abort case. Under a debug build the success case alone takes several hundred milliseconds, so the test failed on every run. Split it into two tests with no timers: one awaits the followed redirect, the other aborts once the server has received the redirected request and never answers it, then checks the rejection and that the new connection is closed. Bind the raw server to 127.0.0.1 like the neighbouring tests instead of fetching "[::]".
|
Updated 3:06 PM PT - Aug 12th, 2026
❌ @robobun, your commit eec7c31 has 3 failures in
🧪 To try this PR locally: bunx bun-pr 37913That installs a local version of the PR into your bun-37913 --bun |
|
Warning Review limit reached
Next review available in: 44 seconds Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling 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: reproduced and fixed, test-only change.
The earlier bot comment above is only a review rate-limit notice; nothing to address from it. |
There was a problem hiding this comment.
LGTM — test-only de-flake that swaps a 150 ms wall-clock race for event-driven synchronization.
What was reviewed:
- Confirmed the rewrite preserves the #15623 scenario (308 +
Connection: closeforces a new connection; abort must track the new socket) and strengthens it — abort now provably fires after the hop, and the test additionally asserts the redirected connection is torn down. - Checked for lingering timers/sleeps, hardcoded ports, and resource leaks — none;
await usingon the server andawait closedon the second socket match neighboring raw-server tests in this file. - Verified the
socket.once("close", ...)choice overevents.onceis correct given the ECONNRESET the client abort produces, and thatredirected.resolvepassed bare works withPromise.withResolvers.
Extended reasoning...
Overview
This PR touches only test/js/web/fetch/fetch.test.ts, replacing one flaky test (should allow to follow redirect if connection is closed...) with a two-test describe block. The old test used AbortSignal.timeout(150) on both its success and failure paths and a server-side setTimeout(200), which raced wall-clock time — deterministic-fail on debug builds (250–650 ms per hop) and a flake source on loaded release lanes. The new tests await observable conditions instead: the success case awaits the fetch directly with a never-fired signal attached; the abort case waits for the redirected request to actually arrive at the server (Promise.withResolvers resolved from the connection handler), then aborts and awaits both the rejection and the server-side socket close.
Security risks
None. Test-only change to a local node:net server bound to 127.0.0.1:0. No runtime, auth, crypto, or network-facing production code is touched.
Level of scrutiny
Low-to-moderate. This is a de-flake of an existing test with no production code changes. Per REVIEW.md the key checks for de-flaking are (1) the property the original assertion protected is still asserted, and (2) no new timing dependencies are introduced. Both hold: the 308-with-Connection: close server shape is unchanged, the success path still asserts status/body (plus redirected: true, which is stricter), and the abort path is now stronger than before — it guarantees the abort happens after the redirect hop rather than merely making it likely via a 150 ms/200 ms race, and it additionally verifies the new connection is torn down. The PR description's explanation of why the abort test would fail if the tracker re-registration broke (the fetch promise would never settle) is sound.
Other factors
- The
throwinside the socket error handler (line 2829) predates this PR — it's carried over unchanged from the original test. - The switch from wildcard/
[::]to127.0.0.1matches every other raw-net.createServertest in this file and avoids theHTTP_PROXY/NO_PROXYinteraction the description calls out. await using serveron anet.Serveris the established pattern in this file (four other tests do the same), so async-dispose support is already relied on.- The author verified debug-build pass, release-build pass across 5 runs, and full-file pass — consistent with the repo's test-verification requirements.
- No prior human or bot reviews to reconcile with.
Problem
test/js/web/fetch/fetch.test.ts, "should allow to follow redirect if connection is closed, abort should work even if the socket was closed before the redirect", fails on every run of a debug build:signal: AbortSignal.timeout(150)to a fetch that has to complete a 308 hop plus a second connection against anode:netserver. On a debug build that takes 250 to 650 ms (first use ofnode:netalone is a few hundred ms), so the timeout wins, the fetch rejects, and thecatchbranch hitsexpect.unreachable(). The same budget is a flake source on loaded release lanes;--timeoutscaling does not help because the 150 ms is wall clock inside the test.setTimeoutagainst the 150 ms client timeout.try/catchshape also turned any failed assertion inside thetryintoUnreachableError, hiding the real failure.http://[::]:port, unlike the neighbouring raw-server tests which bind127.0.0.1. In an environment withHTTP_PROXYset,[::]is also not covered by the usualNO_PROXYentries, so the request leaves through the proxy and the test fails for an unrelated reason.Fix
describe("redirect whose response closes the connection")that share alisten()helper; each test starts its own rawnetserver and passes in its own handler for the redirected request. The server shape that matters is unchanged: the 308 carriesConnection: closeand the socket is ended with it, so the client has to open a new connection for the hop (the scenario from fix(fetch) fix redirect + Connection: close #15623).redirected, and body. No deadline.AbortErrorrejection plus the close of the connection the redirect opened. No timers at all, and the abort is guaranteed to happen after the hop, which the old 150 ms timer only made likely.src/http/HTTPThread.rsdrain_queued_shutdowns,src/runtime/webcore/fetch/FetchTasklet.rsabort_task).do_redirectandon_closeunregister the tracker entry (src/http/lib.rs), andon_openon the new connection registers it again. If that re-registration broke, the abort would find no socket, the promise would never settle, and the test would fail.127.0.0.1and fetches that address, matching the tests around it.bun bd test test/js/web/fetch/fetch.test.ts -t "redirect whose response closes the connection": 2 pass (about 440 ms + 90 ms on the debug build).USE_SYSTEM_BUN=1), 5 runs: 2 pass each time, about 10 ms + 3 ms.fetch.test.tson the debug build: both new tests pass in the middle of the file as well.Background
AbortSignal.timeout(ms)rejects the fetch with aTimeoutErroroncemsof wall clock pass; a test that expects the fetch to succeed while such a signal is attached is asserting a speed, not a behaviour.events.once(socket, "close")rejects if the emitter emits"error"first. The client resets the connection on abort, so the server side seesECONNRESETbefore"close"; the test listens for"close"directly for that reason.