Skip to content

test(node:http): measure the EPOLLHUP idle window again when teardown fills it - #44927

Open
robobun wants to merge 2 commits into
mainfrom
robobun/85c27bd9/connect-hangup-test-idle-window
Open

robobun wants to merge 2 commits into
mainfrom
robobun/85c27bd9/connect-hangup-test-idle-window

Conversation

@robobun

@robobun robobun commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • On a debug build this test can fail with an idle loop: "AF_UNIX CONNECT sockets whose peer closes first do not spin the loop on EPOLLHUP". At 71d0d43, 2 of 30 runs printed spun 524ms cpu in 1000ms.
  • The fixture reports a spin at 500 ms of CPU in one 1000 ms window (test/js/node/http/node-http-connect-unix-hangup-fixture.js:31). The hangup closes the 8 server sockets inside it. A debug build spends 370 to 550 ms at 71d0d43, 193 to 228 ms at 5f5799e.

Fix

  • The fixture measures a busy window again, up to 3 windows. The limit is 500 ms, then 250 ms: a later window has no teardown (8 to 39 ms).
  • A spin uses the whole of every window: 970 to 1090 ms with the EPOLLHUP handling removed. 3 of 3 runs fail. Under load, 40 of 40 runs give the verdict of the one window on main (18 or 24 busy loops, 12 cores).
  • Verified: node-http-connect.test.ts -t "AF_UNIX CONNECT", 0 of 20 debug runs fail. An idle first window gives the same result in the same time. Also node-net-allowHalfOpen.test.js.

Background

  • The kernel reports EPOLLHUP for a socket with both directions down, on each epoll_wait while the fd is registered. The loop never sleeps. usockets: close half-open sockets on EPOLLHUP instead of spinning on it #37920 closes such a socket.
  • The fixture holds 8 CONNECT tunnel sockets whose AF_UNIX peers closed. bun closes them on the hangup, Node.js keeps them open.
  • Considered a delay or a longer window: every CI lane pays. With 500 ms in each window, a spin on a loaded machine passes.
Notes

All runs: Linux x64, 12 cores. Only the fixture changes. No file under src/ or packages/ changes.

The false result (debug build of main at 71d0d43)

Results at 5f5799e

Build Result
debug 0 of 20 fail, in 2.8 to 3.2 s. First window 193 to 228 ms, later windows 8 to 39 ms (30 runs)
debug, 64 sockets in place of 8, to get a busy first window The fixture on main: spun in 6 of 6 (715 to 784 ms). This fixture: idle in 6 of 6, and in 6 of 6 with 18 busy loops
debug, EPOLLHUP handling removed 3 of 3 fail: spun 970ms cpu in 1000ms, 3 windows in a row
release (367d939) 5 of 5 pass in 1.1 s, one window
Node.js v26.3.0 same output, idle

A spin on a loaded machine (debug build, EPOLLHUP handling removed)

Each run records 3 windows. Each rule reads the same run. The cells count the runs that report the spin.

Busy loops on 12 cores One window, 500 ms (main) 500 ms in each window 500 ms, then 250 ms (this PR)
18, from the start 14 of 20 3 of 20 14 of 20
24, from the start 14 of 20 1 of 20 14 of 20
24 to 36, started after the first window 9 of 9 0 of 9 9 of 9
48 to 72, started after the first window 6 of 6 0 of 6 1 of 6
  • With the load from the start, the rule of this PR gives the result of the one window in each of the 40 runs. The lowest later window of a spin is 294 ms.
  • The fixture files themselves, with 21 or 24 busy loops from the start: the fixture on main reports the spin in 11 of 12 runs, the first version of this PR in 0 of 12, this version in 9 of 12. The first window is near 500 ms under this load, so the runs differ.

What the limit of 250 ms costs

  • It leaves less room for a slow teardown on a loaded machine. With 128 or 200 sockets (a first window of 1250 to 1780 ms) and 18 or 24 busy loops, 7 of 18 runs report a spin with an idle loop. With 64 sockets (680 to 780 ms), 0 of 9 do. Without load, 0 of 9 do at each size.

Other notes

  • test/js/node/net/node-net-paused-unix-hangup-fixture.js uses the same check and is not changed. Its sockets stay open during the window, so there is no teardown in it: 39 to 61 ms on the debug build of 71d0d43.
  • Other designs: fewer sockets make the cost smaller but leave it in the window, and test less of the batch handling. Shorter windows change the measurement on the lanes that pass today.
  • At 71d0d43 the test took 4.9 to 7.0 s on a debug build, more than the default test timeout of 5 s, as did six other tests of this file. At 5f5799e it takes 2.8 to 3.2 s.
  • Not run again with the limit of 250 ms: a release build with the EPOLLHUP handling removed. At 71d0d43 each of its windows had 910 to 1000 ms, and 8 of 8 runs failed in 3.1 s. Not run at all: the release-asan profile.

no test proof · iteration 4 · no src or test change; test-proof not applicable

@robobun

robobun commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 9:18 AM PT - Oct 15th, 2026

✅ @robobun, your commit f5e9fba0e31eb857f1fe52cda1c94831265f9c34 passed in Build #125943! 🎉


🧪   To try this PR locally:

bunx bun-pr 44927

That installs a local version of the PR into your bun-44927 executable, so you can run:

bun-44927 --bun

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Essentials
  • Run ID: 01459037-6eab-4a71-a0f5-a7267f43c235
📥 Commits

Reviewing files that changed from the base of the PR and between 71d0d43 and b013b3b.

📒 Files selected for processing (1)
  • test/js/node/http/node-http-connect-unix-hangup-fixture.js

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

Walkthrough

The fixture measures idle CPU across up to three consecutive windows. Busy windows trigger another measurement until the limit. It records an idle verdict after a low-CPU window, or records CPU time and the number of windows when measurement ends.

Changes

Idle CPU measurement

Layer / File(s) Summary
CPU window measurement and verdict
test/js/node/http/node-http-connect-unix-hangup-fixture.js
The fixture retries after busy windows, up to three measurement windows. It records an idle verdict for a low-CPU window, or reports CPU time and the number of windows measured.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to b013b

The fixture may spend up to two additional seconds measuring after a busy window, but its passing output remains compatible and no concrete new failure is established. It appears mergeable with normal test checks.

Pre-merge checks: Passed 4
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly identifies the test fixture and the change to remeasure the EPOLLHUP idle window after teardown.
Description check Passed The description explains the problem, fix, background, and verification results. It does not use the exact template headings, but it provides the required information in equivalent sections.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@claude claude 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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread test/js/node/http/node-http-connect-unix-hangup-fixture.js Outdated
… fills it

The fixture of "AF_UNIX CONNECT sockets whose peer closes first do not
spin the loop on EPOLLHUP" reads process.cpuUsage() over one window of
1000 ms and reports a spin at 500 ms or more. The hangup closes the 8
server sockets inside that window. A debug build spends 370 to 550 ms
of CPU on that teardown, so the fixture reported a spin with an idle
loop in 7 of 32 runs.

The fixture now measures a busy window again, for at most 3 windows.
The first idle window gives "idle". A spin burns the whole of every
window, so it still gives "spun". A first window that is idle gives
the same result in the same time as before.
The first window keeps its limit of half a window. A window that the
fixture measures after a busy one has no teardown left in it, so its
limit is now a quarter.

With half in each window, a spin on a loaded machine passed in a later
window. On a debug build with the EPOLLHUP handling removed, with 18 or
24 busy loops on 12 cores, that rule reported a spin in 4 of 40 runs.
One window of the fixture on main reported it in 28. With a quarter in
the later windows, each of the 40 runs gives the result of the one
window.
@robobun
robobun force-pushed the robobun/85c27bd9/connect-hangup-test-idle-window branch from b013b3b to f5e9fba Compare October 15, 2026 13:49

@claude claude 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.

LGTM — the second commit addresses the loaded-machine false-pass I raised on the first version: later windows now hold to WINDOW_MS / 4, so a spin throttled to half a core no longer lands on the idle branch.

What was reviewed:

  • Traced measure(1..3) in test/js/node/http/node-http-connect-unix-hangup-fixture.js: the first window keeps the 500 ms limit, windows 2-3 use 250 ms, the verdict is set exactly once, and report() still waits for both the verdict and N closes before printing.
  • Checked the test in test/js/node/http/node-http-connect.test.ts (lines 1234-1253) still matches: stdout ends in "idle" on pass, and a spin yields "spun ... 3 windows in a row" with a different stdout, so the assertion still fails on the unfixed loop.
  • maybeMeasure() is still entered once (both counters reach N at a single event and no later client/connect event fires), so measurement chains cannot overlap.
  • Cost: an idle first window takes the same 1 s as before; only a busy first window extends the fixture to 3 s.
Extended reasoning...

The PR changes only one spawned test fixture (12 insertions, 2 deletions): it re-measures a busy CPU window up to 3 times, keeping the 500 ms limit for the first window and tightening later windows to 250 ms. It touches no runtime code and no security-sensitive surface. The second commit resolved the only concern from the prior review (later windows could pass a throttled spin), the file is not covered by CODEOWNERS, and the hunt ran dry with no findings. The change is small, self-contained, and its fail-on-bug behavior traces cleanly through the fixed constants.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant