Skip to content

diagnostic, not for merging: module-graph-isolation.test.ts on macOS x64 and Windows - #45816

Closed
dylan-conway wants to merge 1 commit into
mainfrom
claude/diag-isolation-pages
Closed

dylan-conway wants to merge 1 commit into
mainfrom
claude/diag-isolation-pages

Conversation

@dylan-conway

Copy link
Copy Markdown
Member

Not for merging. Runs a copy of the disposed-while-building-pages.mjs fixture 64 times next to the test that uses it, and on a failure prints where it is stuck, or what the heap snapshot says keeps the server and whether it goes later. To be closed once the macOS x64 and Windows jobs have run.

@robobun

robobun commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator
Updated 8:05 PM PT - Oct 17th, 2026

❌ @dylan-conway, your commit 82c4966 has 2 failures in Build #127847 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 45816

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

bun-45816 --bun

dylan-conway added a commit that referenced this pull request Oct 18, 2026
…olation.test.ts (#45821)

### What does this PR do?

Fixes the way "a server whose HTML routes were still being built is
released with it" in
`test/js/bun/module-graph/module-graph-isolation.test.ts` fails on
Windows. The file failed and passed on a retry on the Windows 2019 x64
lane in builds of 8 branches between 127026 and 127507. Of the 4 I read,
3 are this test, each time after 30 s:

```
-   "exitCode": 0,
-   "stdout": "collected",
+   "exitCode": 137,
+   "stdout": "",
```

The fixture asks a server for six HTML routes, and disposes the graph
once it sees a request that is waiting for its build:

```js
await until(() => server.pendingRequests >= 1);
```

`until` looks once per turn of the event loop. #45816 ran a copy of the
fixture that reports where it is after 10 s, and it was there on both
Windows lanes of build 127847:

```
STUCK: use 3: waiting for a pending request, pendingRequests 0, polls 3534993, sockets {"open":6,"data":6,"close":0,"error":0,"rejected":0}
STUCK: use 2: waiting for a pending request, pendingRequests 0, polls 6509681, sockets {"open":6,"data":6,"close":0,"error":0,"rejected":0}
```

All six requests had been answered. They arrived and their builds
finished between two looks, so the count went from 0 to 6 and back
unseen, and it stays 0.

The wait now also ends when every request has an answer. A server that
was not disposed during a build shows nothing, so it does not count
towards the three, and the fixture says so on stderr.

This is not the `kept 1` that the same test gives on the macOS x64 lane.

### How did you verify your code works?

**Windows 11 x64**, 24 cores, with a binary CI built from main's code
(f1fc570). That test alone, main's file and this PR's in alternating
rounds, 48 at a time for 1500 runs of each and 96 at a time for 6000:

| | runs | exit 137, no output | servers whose builds were all done
unseen |
|---|---|---|---|
| main | 7500 | 6 | |
| this PR | 7500 | 0 | 4 |

The four are runs that printed the new line, made another server, and
passed.

**Linux x64**, with the binary CI built for main (47c8687). To miss
the builds on purpose, the third server's wait starts 200 ms late. The
two tests that run this fixture:

| | main | this PR |
|---|---|---|
| "a server whose HTML routes were still being built is released with
it" | exit 137, no output | pass |
| "and the plugins those builds borrowed from it stay the server's" |
exit 137, no output | pass |

With every server's wait 200 ms late, the fixture is killed after 30 s
as before, with "every build was done before a turn saw one" 148 times
in the output.

As it is: the two tests pass 300 of 300 times on that binary and 60 of
60 on the ASAN one (5b468a6), and the whole file passes (413 pass).

**A server that is kept still fails.** With every server the fixture
makes pushed to a global array, both tests fail with `kept 3`.

In CI: the file passed at the first attempt on both lanes in build
127869 (the first commit).
@dylan-conway
dylan-conway deleted the claude/diag-isolation-pages branch October 18, 2026 03:15
@robobun

robobun commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Findings on kept 1 from Linux x64, in case they help here. I did not get a macOS x64 run either, so the cause on that lane is still open.

Measured

  • One turn of the last loop (Bun.gc(true), heapStats(), setImmediate) takes 3.7 to 5.3 ms on Linux x64 release. 2000 turns are 7.5 to 10.5 s. The cases that failed on the macOS x64 lane took 1.9 to 2.3 s in all (builds 126485, 126781, 127229). So the same limit is several times shorter in time on that lane.
  • On an idle machine the loop ends after 1 to 35 turns, 74 to 229 ms (10 runs).
  • The six sockets that use() opens keep the server of that use(). Their handlers close over its scope, which has server. With one more such socket per use() that stays open (to a listener of the host), the fixture prints kept 3 in 3 of 3 runs (my copy stops at 400 turns). So a server is held until its six clients have seen the close. The page builds are not the only thing the loop waits for.

Read, not run

  • A route that builds holds one pending request on the server until finish_building (src/runtime/server/HTMLBundle.rs:330-364). The bundle thread runs queued builds one after another (src/bundler/BundleThread.rs:225-250).
  • A reading of the server and route code found no path that keeps the server for good. No run confirms that.

Not known

Which of these is late on that lane: the builds, the close of the clients, or a real leak. On Linux I got no kept in 75 runs with three arrangements of when the six requests arrive relative to dispose().

What would tell them apart

At kept, print the pendingRequests of each server that is left (through a WeakRef), and how many of its six clients have not closed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants