Skip to content

node:perf_hooks: make monitorEventLoopDelay() return independent histograms - #33591

Closed
robobun wants to merge 2 commits into
mainfrom
claude/farm/28bf3033/monitor-event-loop-delay-per-instance
Closed

robobun wants to merge 2 commits into
mainfrom
claude/farm/28bf3033/monitor-event-loop-delay-per-instance

Conversation

@robobun

@robobun robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator

Repro

import { monitorEventLoopDelay } from "node:perf_hooks";

const A = monitorEventLoopDelay();
const B = monitorEventLoopDelay();
A === B;       // node: false   bun: true
A.enable();
B.disable();   // node: false (B was never enabled)   bun: true (stops A)
A.disable();   // node: true                          bun: false

Two independent consumers in one process (e.g. prom-client's nodejs_eventloop_lag_* collector plus a health check like @nestjs/terminus EventLoopDelayHealthIndicator) share one histogram and one enabled flag: whichever calls disable() first silently stops the other's monitoring; the second consumer's enable() returns false; one reset() wipes the other's samples.

Cause

src/js/internal/perf_hooks/monitorEventLoopDelay.ts kept module-level let eventLoopDelayHistogram / enabled / resolution and returned the cached histogram on every call. On the native side, All.event_loop_delay was a single EventLoopDelayMonitor embedded by value with one timer and one bare-JSValue histogram reference (kept alive only by the JS module cache).

Fix

  • Each monitorEventLoopDelay() call creates a fresh native histogram.
  • enable() heap-allocates a Box<EventLoopDelayMonitor> (own timer, own resolution, own last_fire_ns), inserts its intrusive timer node into the heap, and stores the pointer on the C++ histogram (m_eventLoopDelayMonitor); it returns false if one is already present.
  • disable() removes that node and frees the box; returns false if none.
  • The monitor now holds the histogram via bun_jsc::strong::Optional, so an enabled histogram stays rooted even if userland drops its reference (resolves the existing TODO about bare-JSValue rooting).
  • enable() no longer calls histogram->reset(); Node preserves samples across disable/enable.
  • Dispatch in __bun_fire_timer is already container-of based, so the EventLoopDelayMonitor arm works unchanged for any heap-allocated instance. The unused All.event_loop_delay singleton field is removed.

Verification

bun bd test test/js/node/perf_hooks/perf_hooks.test.ts
bun bd test test/js/bun/perf_hooks/histogram.test.ts
bun bd test/js/node/test/sequential/test-performance-eventloopdelay.js

All pass. The three new monitorEventLoopDelay tests fail on released Bun 1.4.0:

(fail) monitorEventLoopDelay > each call returns an independent histogram
(fail) monitorEventLoopDelay > disable on one monitor does not stop another
(fail) monitorEventLoopDelay > reset on one monitor does not clear another

Note: #33587 touches the same JS file for the prototype-chain shape and will conflict; both fixes are orthogonal.


[review] gate passed · iteration 2 · 7 files touched

fails on main (without fix)
ASAN without fix: 3 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/perf_hooks/perf_hooks.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (4a4e24894)

test/js/node/perf_hooks/perf_hooks.test.ts:
(pass) stubs [6.05ms]
(pass) doesn't throw [15.56ms]
26 | describe("monitorEventLoopDelay", () => {
27 |   test("each call returns an independent histogram", () => {
28 |     const a = monitorEventLoopDelay({ resolution: 5 });
29 |     const b = monitorEventLoopDelay({ resolution: 5 });
30 |     try {
31 |       expect(a === b).toBe(false);
                           ^
error: expect(received).toBe(expected)

Expected: false
Received: true

      at <anonymous> (/workspace/bun/test/js/node/perf_hooks/perf_hooks.test.ts:31:23)
(fail) monitorEventLoopDelay > each call returns an independent histogram [17.31ms]
62 |     try {
63 |       a.enable();
64 |       await waitFor
... (truncated)

release without fix: 3 FAILED
bun test v1.4.0-canary.1 (1498d7b77)

test/js/node/perf_hooks/perf_hooks.test.ts:
(pass) stubs [0.09ms]
(pass) doesn't throw [0.24ms]
26 | describe("monitorEventLoopDelay", () => {
27 |   test("each call returns an independent histogram", () => {
28 |     const a = monitorEventLoopDelay({ resolution: 5 });
29 |     const b = monitorEventLoopDelay({ resolution: 5 });
30 |     try {
31 |       expect(a === b).toBe(false);
                           ^
error: expect(received).toBe(expected)

Expected: false
Received: true

      at <anonymous> (/workspace/bun/test/js/node/perf_hooks/perf_hooks.test.ts:31:23)
(fail) monitorEventLoopDelay > each call returns an independent histogram [0.55ms]
62 |     try {
63 |       a.enable();
64 |       await waitForCount(a, 1);
65 |       const before = a.count;
66 |       // B was never enabled; disabling it must not stop A
67 |       expect(b.disable()).toBe(false);
                               ^
error: expect(received).toBe(expected)

Expected: false
Received: true

      at <anonymous> (/workspace/bun/test/js/node/perf_hooks/perf_hooks.test.ts:67:27)
(fail) monitorEventLoopDelay > disable on one monitor does not stop another [5.
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/perf_hooks/perf_hooks.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (4a4e24894)

test/js/node/perf_hooks/perf_hooks.test.ts:
(pass) stubs [5.72ms]
(pass) doesn't throw [15.76ms]
(pass) monitorEventLoopDelay > each call returns an independent histogram [16.10ms]
(pass) monitorEventLoopDelay > disable on one monitor does not stop another [21.85ms]
(pass) monitorEventLoopDelay > reset on one monitor does not clear another [11.13ms]
(pass) monitorEventLoopDelay > process exits cleanly with monitors still enabled [647.85ms]

 6 pass
 0 fail
 29 expect() calls
Ran 6 tests across 1 file. [2.97s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     4a4e248943
  features     (none)

22 deps, 106 codegen, 1168 objects in 938ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1231] install /workspace/bun
bun install v1.4.0-canary.1 (1498d7b77)

Checked 124 installs across 170 packages (no changes) [15.00ms]
[2/1231] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (1498d7b77)

Checked 1 install across 2 packages (no changes) [2.00ms]
[3/1231] gen ErrorCode+*.h
[4/1231] gen bindgenv2
[5/1231] fetch zlib
[zlib] up to date
[6/1231] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[7/1231] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (1498d7b77)

Checked 129 installs across 147 packages (no changes) [21.00ms]
[8/1231] fetch picoh
... (truncated)
diff hotspot
.../internal/perf_hooks/monitorEventLoopDelay.ts   | 48 +++--------
 src/jsc/bindings/JSNodePerformanceHooksHistogram.h |  1 +
 .../JSNodePerformanceHooksHistogramPrototype.cpp   | 33 +++++---
 src/runtime/jsc_hooks.rs                           |  6 +-
 src/runtime/timer/EventLoopDelayMonitor.rs         | 34 ++++----
 src/runtime/timer/mod.rs                           | 69 +++++++++++-----
 test/js/node/perf_hooks/perf_hooks.test.ts         | 94 +++++++++++++++++++++-
 7 files changed, 196 insertions(+), 89 deletions(-)

gate history · 1 passed · 0 rejected · iteration 2

evidence per changed file
file                                                      reads  edits  tests
src/js/internal/perf_hooks/monitorEventLoopDelay.ts           1      1      0
src/jsc/bindings/JSNodePerformanceHooksHistogram.h            1      1      0
…c/bindings/JSNodePerformanceHooksHistogramPrototype.cpp      2      3      0
src/runtime/jsc_hooks.rs                                      2      1      0
src/runtime/timer/EventLoopDelayMonitor.rs                    3      6      0
src/runtime/timer/mod.rs                                     10     10      0
test/js/node/perf_hooks/perf_hooks.test.ts                    2      5      0

@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 492a0cb8-f9fb-4c2a-b9d5-4eb8594c656d

📥 Commits

Reviewing files that changed from the base of the PR and between 3057da0 and 4a4e248.

📒 Files selected for processing (7)
  • src/js/internal/perf_hooks/monitorEventLoopDelay.ts
  • src/jsc/bindings/JSNodePerformanceHooksHistogram.h
  • src/jsc/bindings/JSNodePerformanceHooksHistogramPrototype.cpp
  • src/runtime/jsc_hooks.rs
  • src/runtime/timer/EventLoopDelayMonitor.rs
  • src/runtime/timer/mod.rs
  • test/js/node/perf_hooks/perf_hooks.test.ts

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the claude label Jul 7, 2026
@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 10:39 PM PT - Jul 15th, 2026

❌ @robobun, your commit 4a4e248 has 3 failures in Build #73602 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 33591

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

bun-33591 --bun

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. timers: root the event-loop-delay histogram, fix aliased-&mut in timer drain, arm uv timer on the owning VM's loop #31837 - Both fix the unrooted JSValue GC bug in EventLoopDelayMonitor.js_histogram (via different mechanisms: JsCell<JsRef> vs bun_jsc::strong::Optional), causing conflicts in src/runtime/timer/mod.rs

🤖 Generated with Claude Code

Comment thread src/runtime/timer/EventLoopDelayMonitor.rs
Comment thread src/runtime/timer/mod.rs
Comment thread src/runtime/timer/EventLoopDelayMonitor.rs Outdated
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Not a duplicate of #31837. That PR hardens the existing singleton (roots the bare JSValue, adds a Finalized teardown state) but keeps the module-level let eventLoopDelayHistogram cache, so every monitorEventLoopDelay() call still returns the same object and one consumer's disable() still kills every other consumer. This PR removes the singleton so each call gets its own histogram, timer, and enabled flag (the actual Node contract). Both touch EventLoopDelayMonitor.js_histogram for GC rooting, hence the conflict, but the user-observable fixes are orthogonal.

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

Thanks for addressing the earlier feedback — the teardown path and Strong-root lifecycle look right now. Given this reworks per-instance heap allocation, GC rooting, and intrusive timer-heap cleanup across the Rust/C++ FFI boundary, it's worth a human pass before merge.

Extended reasoning...

Overview

This PR changes monitorEventLoopDelay() from returning a module-level singleton to returning a fresh histogram per call, matching Node.js semantics. It touches: the JS builtin (src/js/internal/perf_hooks/monitorEventLoopDelay.ts), the C++ histogram class (adds m_eventLoopDelayMonitor pointer field and a new _clearMonitor extern), the Rust timer subsystem (EventLoopDelayMonitor becomes a per-instance Box with a bun_jsc::strong::Optional root instead of a by-value field of All with a bare JSValue), and cancel_all_timeout_objects (new arm to reclaim enabled monitors at VM teardown). Four new tests cover independence, cross-monitor disable/reset isolation, and clean exit with monitors still enabled.

Prior review

I flagged three items on the first revision: a leak of the new Box + Strong handle at VM teardown (no EventLoopDelayMonitor arm in cancel_all_timeout_objects), a stale section-header comment, and a bun_core::heap convention nit. All three were addressed in 527771d and the threads are resolved. The follow-up bug scan found nothing further.

Security risks

None identified. No untrusted input parsing, auth, or network surface is touched — this is internal timer/histogram plumbing.

Level of scrutiny

High. The change restructures ownership across an FFI boundary: a heap-allocated Rust struct whose raw pointer is stored on a C++ JSCell, rooted back via a Strong handle, linked into an intrusive timer heap, with three release paths (userland disable(), VM teardown walk, and the histogram's m_eventLoopDelayMonitor being nulled). The teardown arm now recovers the container via from_timer_ptr, calls back into C++ to null the owning field, deinits the Strong, unlinks from the heap, and destroys the box — correct ordering matters here for both use-after-free and double-free. This is exactly the class of change CLAUDE.md calls out as most-blocked ("every allocation has exactly one named owner, released exactly once"), and while I believe the current shape is sound, it merits a maintainer's eyes rather than bot approval.

Other factors

  • The new subprocess test asserts expect(stderr).toBe(""), which CLAUDE.md advises against (ASAN/debug builds may emit benign warnings) — minor, not blocking.
  • PR description notes a known conflict with #33587 (prototype-chain shape) and overlap with #31837 (GC rooting of the same field via a different mechanism); a human should confirm the intended landing order.
  • Test coverage for the fix itself is good (independence, disable/reset isolation, teardown).

@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI on 4a4e248 (rebased): 282/286 jobs passed. The four red lanes are known systemic issues already being fixed separately, none touching perf_hooks or the timer code this PR changes:

The new monitorEventLoopDelay tests and upstream test-performance-eventloopdelay.js pass on every lane. Ready for review.

robobun added 2 commits July 16, 2026 03:30
…ograms

Each call now allocates its own native monitor (heap-allocated
EventLoopDelayMonitor with its own timer, resolution, and enabled state)
instead of sharing a single module-level histogram. One consumer's
disable()/reset() no longer affects any other consumer.

The histogram is now rooted with a Strong handle while monitoring is
enabled, replacing the bare JSValue that previously relied on the
module-level cache for GC safety. enable() no longer resets the
histogram, matching Node.js.
Adds an EventLoopDelayMonitor arm to cancel_all_timeout_objects so a
monitor left enabled when a worker or VM shuts down has its timer node
unlinked, its Strong root released, and its heap allocation freed. The
owning histogram's m_eventLoopDelayMonitor pointer is nulled first so a
late disable() call cannot double-free.

Also: use bun_core::heap helpers for the Box/raw round-trip, and update
the stale section header above DateHeaderTimer/EventLoopDelayMonitor.
@robobun
robobun force-pushed the claude/farm/28bf3033/monitor-event-loop-delay-per-instance branch from 527771d to 4a4e248 Compare July 16, 2026 03:36
Comment thread test/js/node/perf_hooks/perf_hooks.test.ts
Jarred-Sumner pushed a commit that referenced this pull request Aug 21, 2026
… retires its realm (#39871)

### Problem
- A file that leaves `monitorEventLoopDelay().enable()` on keeps the
monitor firing after `bun test --isolate` (or `--parallel`) retired its
global. Each fire is a write-after-free in `hdr_record_value` under
`EventLoopDelayMonitor::on_fire`
(`JSNodePerformanceHooksHistogram_recordDelay`). Sentry BUN-41KQ,
Windows, 23 events.
- `All.event_loop_delay` (`src/runtime/timer/mod.rs:429`) is per thread
and held the histogram as a bare `JSValue` that only the retired realm
kept alive. The swap did not touch the monitor, and `enable()` returned
early while enabled, so the next file could not replace the dead
histogram.

### Fix
- The monitor holds the histogram in a passive `bun_jsc::Weak`, and
`on_fire` stops once the cell is gone. A `Strong` would pin the retired
realm instead.
- `stop_active_handles` disables the monitor, next to the fake-timers
reset. It runs at every file swap and in the teardown stop phase, so the
`Weak` goes while the heap is alive and the next file finds the monitor
free.
- `enable()` replaces whatever is registered. `disable()` unlinks the
timer only when it is `ACTIVE`, since `on_fire` calls it with the node
popped.
- Verified: two new cases in `test/cli/test/isolation.test.ts`. The
first fails on stock bun and on an unfixed ASAN build. Also ran the
whole file and the perf_hooks suites.

### Background
- `--isolate` runs each file in a fresh global on one thread. Between
files, `stop_active_handles` closes what the file leaked, then the swap
replaces the global. The per-thread `timer::All` survives. Leaked fake
timers (fb03d39) were the same problem.
- `EventLoopDelayMonitor` is one `EventLoopTimer` embedded in `All`.
Every `resolution` ms it records the delay into the histogram and
re-arms itself.
- `bun_jsc::Weak` wraps a `JSC::Weak`. It reads empty once the GC reaps
the cell, before the sweep runs `~HistogramData` (`hdr_close`). The
handle lives in the JSC heap, and `All` is dropped after `~VM`, so the
stop phase has to release it.

<details><summary>Notes</summary>

- Repro on release 1.4.0 (Linux, 5/5): file A enables a 1 ms monitor in
a test. File B runs `Bun.gc(true)`, calls `createHistogram()`, and idles
150 ms. `bun test --isolate a b` prints `count = 139..140` for B's
histogram: the freed cell is reused by B's histogram and the stale
`record()` calls land in it (`totalCount` lives in the cell). Without
`--isolate` the count stays 0. Windows decommits freed pages instead,
hence the Windows-only Sentry events (23 since 1.3.14, 2 on 1.4.0).
- The first new test asserts two things because the failure mode depends
on the allocator. On release bun, a decoy histogram allocated after
`Bun.gc(true)` receives the stale records (837 in my run). On the ASAN
debug build the cell is not reused, and the failure is that file B's own
`enable()` is a no-op, so its histogram stays empty. Each build fails on
one of the two assertions.
- The second new case is in the "collects globals pinned by leaked
handles" block. It passes before and after this change (a bare `JSValue`
does not pin either). It is there to keep a future `Strong` out of this
field.
- `JSNodePerformanceHooksHistogram_recordDelay` keeps its
`uncheckedDowncast`. The value now comes out of a live `Weak` and was
type checked by `jsFunction_enableEventLoopDelay`. A checked cast cannot
detect a freed cell anyway.
- Probed on the ASAN build with the monitor left enabled: three Worker
terminations and the main thread under `BUN_DESTRUCT_VM_ON_EXIT=1`. No
report. A monitor inside a Worker still records.
- A ShadowRealm that enables the monitor and is then dropped did not
reproduce in a short probe. The `Weak` covers that shape too if such a
realm is collected.
- Suites run against the debug build: all of
`test/cli/test/isolation.test.ts` (28 pass),
`test/js/node/perf_hooks/perf_hooks.test.ts`,
`test/js/node/test/sequential/test-performance-eventloopdelay.js`.
- Related open PRs take other routes and do not handle the file swap.
#31837 roots the histogram with a Strong, which would pin one retired
realm per file. #33591 makes each `monitorEventLoopDelay()` call
independent (a Strong per monitor, no isolation or teardown step). This
PR is the small crash fix on the current design. Either of those needs
the `stop_active_handles` step if it lands later.
</details>

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 0 · 5 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/test/isolation.test.ts
bun test v1.4.0 (6e906e4)

test/cli/test/isolation.test.ts:
(pass) bun test --isolate > without --isolate, leaked global is visible to next file [366.34ms]
(pass) bun test --isolate > with --isolate, each file gets a fresh global [329.40ms]
(pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [447.74ms]
(pass) bun test --isolate > without --isolate, --preload still runs once (regression) [458.95ms]
(pass) bun test --isolate > with --isolate, module state is not shared between files [350.43ms]
(pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [2045.84ms]
(pass) bun test --isolate > with --isolate, leaked outbound socket is closed before next file [1793.79ms]
(pass) bun test --isolate > with --isolate, leaked fs.watch is closed before next file [1802.39ms]
(pass) bun test --isolate > with --isolate, leaked vi.useFakeTimers() is deactivated before next file [2504.91ms]
(pass) bun test --isolate > leaked subprocesses are
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (4653fc9)

test/cli/test/isolation.test.ts:
(pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [30.06ms]
(pass) bun test --isolate > without --isolate, --preload still runs once (regression) [37.98ms]
(pass) bun test --isolate > with --isolate, each file gets a fresh global [41.89ms]
(pass) bun test --isolate > without --isolate, leaked global is visible to next file [44.36ms]
(pass) bun test --isolate > with --isolate, module state is not shared between files [41.27ms]
(pass) --isolate: cached module_info handles `import * as ns; export { ns }` as a Namespace export [24.88ms]
(pass) --isolate: delete require.cache evicts the SourceProvider cache [37.93ms]
(pass) --isolate: SourceProvider cache covers CommonJS modules [34.53ms]
(pass) --isolate: SourceProvider cache covers node_modules .mjs and type:commonjs packages [32.54ms]
(pass) bun test --isolate > leaked subprocesses are killed for every isolated file, not just the first [47.75ms]
(pass) bun test --isolate > module-scope subprocesses are killed for every isolated file, not just the first (--isolate) [53.23ms]
(pass) bun test --isolate > with --
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/test/isolation.test.ts
bun test v1.4.0 (6e906e4)

test/cli/test/isolation.test.ts:
(pass) bun test --isolate > without --isolate, leaked global is visible to next file [444.93ms]
(pass) bun test --isolate > without --isolate, --preload still runs once (regression) [480.41ms]
(pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [654.97ms]
(pass) bun test --isolate > with --isolate, each file gets a fresh global [722.74ms]
(pass) bun test --isolate > with --isolate, module state is not shared between files [498.11ms]
(pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [2523.27ms]
(pass) bun test --isolate > with --isolate, leaked outbound socket is closed before next file [2256.11ms]
(pass) bun test --isolate > with --isolate, leaked fs.watch is closed before next file [2835.55ms]
(pass) bun test --isolate > with --isolate, a leaked monitorEventLoopDelay() is disabled before next file [2686.55ms]
(pass) bun test --isolate > with --isolate, lea
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 951ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/23] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[2/23] gen cpp.rs (cppbind)
[3/23] gen JS modules (bundle-modules)
Preprocess modules (11794ms)
Bundle modules (84ms)
Postprocesss modules (46ms)
Bundle Functions (900ms)
Generate Code (41ms)

[12.89s] Bundled "src/js" for production
  2625 kb
  198 internal modules
  13 native modules
  92 internal functions across 17 files
[3/9] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
.../JSNodePerformanceHooksHistogramPrototype.cpp   |  4 +-
 src/runtime/jsc_hooks.rs                           |  5 ++
 src/runtime/timer/EventLoopDelayMonitor.rs         |  6 +-
 src/runtime/timer/mod.rs                           | 39 ++++++-----
 test/cli/test/isolation.test.ts                    | 79 ++++++++++++++++++++++
 5 files changed, 110 insertions(+), 23 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
…c/bindings/JSNodePerformanceHooksHistogramPrototype.cpp      2      2      0
src/runtime/jsc_hooks.rs                                      4      5      0
src/runtime/timer/EventLoopDelayMonitor.rs                    2      2      0
src/runtime/timer/mod.rs                                      7      8      0
test/cli/test/isolation.test.ts                               1      4      0
```

</details>

<!-- robobun:evidence:end -->
@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-16, 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.

@robobun robobun closed this Sep 13, 2026
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