Skip to content

bun test --isolate: deactivate leaked fake timers between files - #36385

Merged
Jarred-Sumner merged 4 commits into
mainfrom
farm/1c5244f9/isolate-fake-timers-reset
Jul 30, 2026
Merged

Jarred-Sumner merged 4 commits into
mainfrom
farm/1c5244f9/isolate-fake-timers-reset

Conversation

@robobun

@robobun robobun commented Jul 29, 2026 •

Copy link
Copy Markdown
Collaborator

Symptom

test/regression/issue/29684.test.ts times out on every test (10 × 90s) in the CI parallel bucket on ~4% of builds, then passes in 66ms on solo retry:

test/regression/issue/29684.test.ts:
✗ perMessageDeflate upgrade header > omits Sec-WebSocket-Extensions when perMessageDeflate is false [90000.07ms]
  ^ this test timed out after 90000ms.
✗ perMessageDeflate upgrade header > still sends Sec-WebSocket-Extensions when perMessageDeflate is unset [90002.03ms]
  ^ this test timed out after 90000ms.
...

Repro

BUN_TEST_PARALLEL_SCALE_MS=999999999 bun test --parallel=2 --timeout=5000 \
  test/js/bun/test/fake-timers/sinonjs/issue-2449.test.ts \
  test/regression/issue/29684.test.ts

(Scale-up gated so one worker runs both files sequentially.)

Cause

vi.useFakeTimers() stores its active flag in the per-thread timer::All, not on the JS global. Under --isolate / --parallel, swap_global_for_test_isolation replaces the global and cancels pending timers but never touches fake_timers.active, so a file that activates fake timers and never restores them leaks the flag into every subsequent file in the same worker.

test/js/bun/test/fake-timers/sinonjs/issue-2449.test.ts (and issue-276.test.ts) have it.failing cases that call FakeTimers.install() and then throw on an assert.exception before reaching clock.uninstall(), so vi.useRealTimers() never runs.

With fake_timers.active == true, All::insert routes new setTimeout calls into the fake heap, which only advances on advanceTimersByTime. net.Server.listen() emits 'listening' via setTimeout(emitListeningNextTick, 1, this) (src/js/node/net.ts), so 29684's server.listen(0, "127.0.0.1", cb) callback never fires and the WebSocket client is never created.

Fix

close_isolation_handles now deactivates fake timers and releases their heap pins before the global swap, matching how it already closes leaked watchers and servers. Each file starts with real timers regardless of what the previous file left behind.

Verification

# fail-before (without src/ change): b-real.test.ts times out
# after: 2 pass, 0 fail
bun bd test test/cli/test/isolation.test.ts -t "leaked vi.useFakeTimers"

Also re-ran the exact 71-file CI batch from build 85266 with --parallel=3 three times; 29684 passes all three.


[review] gate passed · iteration 0 · 3 files touched

fails on main (without fix)
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 (ed45e778d)

test/cli/test/isolation.test.ts:
(pass) bun test --isolate > without --isolate, leaked global is visible to next file [454.84ms]
(pass) bun test --isolate > with --isolate, each file gets a fresh global [449.22ms]
(pass) bun test --isolate > without --isolate, --preload still runs once (regression) [528.79ms]
(pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [580.56ms]
(pass) bun test --isolate > with --isolate, module state is not shared between files [430.53ms]
(pass) bun test --isolate > with --isolate, leaked outbound socket is closed before next file [2237.93ms]
(pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [2989.95ms]
(pass) bun test --isolate > with --isolate, leaked fs.watch is closed before next file [2734.81ms]
(pass) bun test --isolate > leaked subprocesses are killed for every isolated file, not just the first [2979.88ms]
(pass) --isolate: SourceProvider cache covers Comm
... (truncated)

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

test/cli/test/isolation.test.ts:
(pass) bun test --isolate > without --isolate, --preload still runs once (regression) [146.69ms]
(pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [164.72ms]
(pass) bun test --isolate > without --isolate, leaked global is visible to next file [168.60ms]
(pass) bun test --isolate > with --isolate, each file gets a fresh global [167.83ms]
(pass) --isolate: SourceProvider cache covers node_modules .mjs and type:commonjs packages [110.94ms]
(pass) --isolate: collects globals pinned by leaked handles > long setTimeout/setInterval left pending [93.81ms]
(pass) bun test --isolate > with --isolate, module state is not shared between files [180.26ms]
(pass) --isolate: cached module_info handles `import * as ns; export { ns }` as a Namespace export [123.67ms]
(pass) --isolate: delete require.cache evicts the SourceProvider cache [162.79ms]
(pass) --isolate: SourceProvider cache covers CommonJS modules [155.08ms]
(pass) --isolate: cached SourceProvider's module_info rebuilds correct exports [140.30ms]
(pass) bun test --isolate > with --isolate, leaked outbound socket 
... (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/cli/test/isolation.test.ts
bun test v1.4.0 (ed45e778d)

test/cli/test/isolation.test.ts:
(pass) bun test --isolate > without --isolate, leaked global is visible to next file [430.55ms]
(pass) bun test --isolate > with --isolate, each file gets a fresh global [424.98ms]
(pass) bun test --isolate > without --isolate, --preload still runs once (regression) [511.23ms]
(pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [564.86ms]
(pass) bun test --isolate > with --isolate, module state is not shared between files [444.53ms]
(pass) bun test --isolate > with --isolate, leaked outbound socket is closed before next file [2260.41ms]
(pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [3051.82ms]
(pass) bun test --isolate > with --isolate, leaked fs.watch is closed before next file [2720.75ms]
(pass) bun test --isolate > with --isolate, leaked vi.useFakeTimers() is deactivated before next file [2807.48ms]
(pass) bun test --isolate > leaked subprocesses are
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 720ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/22] cxx obj/src/jsc/bindings/napi.cpp.o
[2/22] cxx obj/codegen/JSSink.cpp.o
[3/22] cxx obj/unified/UnifiedSource-src_jsc_bindings_node-0.cpp.o
[4/22] cxx obj/unified/UnifiedSource-src_jsc_bindings_webcore-3.cpp.o
[5/22] cxx obj/unified/UnifiedSource-src_jsc_bindings_webcore-4.cpp.o
[6/22] cxx obj/unified/UnifiedSource-src_jsc_modules-0.cpp.o
[7/22] cxx obj/src/jsc/bindings/bindings.cpp.o
[8/22] cxx obj/src/jsc/bindings/BunObject.cpp.o
[9/22] cxx obj/codegen/ZigGeneratedClasses.cpp.o
[10/22] cxx obj/unified/UnifiedSource-src_jsc_bindings-4.cpp.o
[11/22] cxx obj/unified/UnifiedSource-src_jsc_bindings-2.cpp.o
[12/22] cxx obj/unified/UnifiedSource-src_jsc_bindings-3.cpp.o
[13/22] gen cpp.rs (cppbind)
[14/22] cxx obj/src/jsc/bindings/ZigGlobalObject.cpp.o
[15/22] cxx obj/unified/UnifiedSource-src_jsc_bindings-1.cpp.o
[16/22] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 239 extern-C blocks audited
[16/22] cargo bun_bin → libbun_rust.a (--target x
... (truncated)
diff hotspot
src/runtime/jsc_hooks.rs                     | 18 +++++++++
 src/runtime/test_runner/timers/FakeTimers.rs | 10 +++++
 test/cli/test/isolation.test.ts              | 60 ++++++++++++++++++++++++++++
 3 files changed, 88 insertions(+)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                          reads  edits  tests
src/runtime/jsc_hooks.rs                          7      5      0
src/runtime/test_runner/timers/FakeTimers.rs      8      9      0
test/cli/test/isolation.test.ts                   3      3      0

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

The change deactivates leaked fake timers before isolation handles close, adds cleanup for abort-signal timeout references, and introduces serial and parallel regression tests covering real timers and server activity across isolated test files.

Fake timer isolation cleanup

Layer / File(s) Summary
Fake timer heap cleanup
src/runtime/test_runner/timers/FakeTimers.rs
FakeTimers::deactivate is crate-visible, and heap cleanup now handles both TimeoutObject and AbortSignalTimeout entries.
Isolation teardown integration
src/runtime/jsc_hooks.rs, test/cli/test/isolation.test.ts
Isolation teardown deactivates active fake timers and releases heap pins before closing handles; regression tests cover serial and parallel isolated runs.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ 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 and concisely summarizes the main change: deactivating leaked fake timers between isolated test files.
Description check ✅ Passed The description covers the symptom, cause, fix, and verification, though it uses custom headings instead of the template's exact section names.

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

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Reset system time for each test file #13173 - This PR's FakeTimers::deactivate() call in close_isolation_handles() clears CURRENT_TIME (the setSystemTime override) between files, directly fulfilling the request to reset system time per test file in --isolate/--parallel mode.

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #13173

🤖 Generated with Claude Code

Comment thread src/runtime/jsc_hooks.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
Comment thread src/runtime/server/DirectoryRoute.rs Outdated
vi.useFakeTimers() stores its active flag in the per-thread timer::All,
not on the JS global, so a test file that activates fake timers and
never restores them (an it.failing that throws before useRealTimers(),
for instance) left fake_timers.active = true across the isolation
global swap. Every setTimeout in subsequent files was then routed into
the never-driven fake heap.

net.Server.listen() emits 'listening' via setTimeout, so any later
file that listens on a port hung until its per-test timeout. In the
CI parallel bucket this showed up as test/regression/issue/29684.test.ts
timing out on every test whenever it landed in the same worker after
test/js/bun/test/fake-timers/sinonjs/issue-2449.test.ts (or issue-276).

close_isolation_handles now deactivates fake timers (and releases the
heap pins) before the global swap, so each file starts with real
timers regardless of what the previous file left behind.
@robobun
robobun force-pushed the farm/1c5244f9/isolate-fake-timers-reset branch from 597a9bb to 2fafc35 Compare July 29, 2026 15:48
Comment thread src/runtime/jsc_hooks.rs Outdated

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

Additional findings (outside current diff — PR may have been updated during review):

  • 🔴 src/runtime/jsc_hooks.rs:1656-1670 — Draining the fake heap here via deactivate() → clear() unlinks AbortSignalTimeout nodes without releasing the +1 they hold on the C++ AbortSignal, so the cycle-breaking pass in cancel_all_timeout_objects (which the subsequent swap_global_for_test_isolation still calls) now walks an empty fake heap and never sees them — leaking both the AbortSignal and its boxed Timeout for the process lifetime. Before this PR the isolation swap freed these correctly. Extend FakeTimers::clear() to null (*t).signal and unref() for AbortSignalTimeout entries the same way cancel_all_timeout_objects does at mod.rs:1252-1265 (which also fixes the pre-existing use_real_timers/clearAllTimers hole).

    Extended reasoning...

    What leaks

    AbortSignal.timeout(ms) boxes a Rust Timeout and forms a two-way refcount cycle with the C++ AbortSignal: the C++ side holds m_timeout (raw *mut Timeout) and takes signal->ref() on behalf of the Rust box (AbortSignal.cpp:71), while the Rust Timeout stores that +1'd pointer in .signal. The only free paths for the box are (a) the timer fires and the signal aborts, or (b) something breaks the cycle by nulling .signal and unref()'ing so ~AbortSignal → cancelTimer() → AbortSignal__Timeout__deinit can run.

    Code path

    EventLoopTimerTag::allow_fake_timers() falls through to _ => true for AbortSignalTimeout (EventLoopTimer.rs:212-222), so under vi.useFakeTimers() an AbortSignal.timeout(...) is routed into fake_timers.timers by All::insert (mod.rs:679-685).

    Pre-PR: close_isolation_handles did not touch fake timers. swap_global_for_test_isolation → hooks.cancel_all_timers → All::cancel_all_timeout_objects walked both heap roots — [(*this).timers.0.root, (*this).fake_timers.timers.0.root] at mod.rs:1193 — collected each AbortSignalTimeout node, and at mod.rs:1252-1265 removed it from the heap, nulled (*t).signal, and called unref() on the C++ signal. That drops the cycle's +1; when the JS wrapper is collected, ~AbortSignal → cancelTimer() frees the box.

    Post-PR: close_isolation_handles runs before the swap at both call sites (test_command.rs:3125-3126, parallel/runner.rs:550-551) and now calls fake_timers.deactivate() → clear(). clear() delete_min()s every node, sets in_heap = None / state = CANCELLED, but only branches on tag == TimeoutObject — AbortSignalTimeout nodes are silently unlinked with .signal still holding the +1. By the time cancel_all_timeout_objects runs, fake_timers.timers.0.root is null and those boxes are unreachable from either heap root, so signal_timeouts stays empty and the cycle-break pass never runs.

    Why nothing else catches it

    The only other release of the Timeout's +1 is AbortSignal::eventListenersDidChange(), which requires an observer add/remove to fire — a bare AbortSignal.timeout(n) (or one used only as fetch(url, { signal: AbortSignal.timeout(n) })) never triggers it. GC of the JS wrapper drops the wrapper's ref (2→1), but the Timeout's +1 keeps the AbortSignal refcount at ≥1 forever, so ~AbortSignal never runs and cancelTimer() never frees the box.

    Step-by-step

    Isolated file A under leaked fake timers:

    vi.useFakeTimers();
    const s = AbortSignal.timeout(100);   // box in fake heap; AbortSignal refcount = 2 (wrapper +1, Timeout +1)
    // file ends without vi.useRealTimers()
    1. close_isolation_handles → deactivate() → clear(): node popped, in_heap=None, state=CANCELLED. tag == AbortSignalTimeout ≠ TimeoutObject → not pushed to pinned. .signal still non-null, +1 still held.
    2. swap_global_for_test_isolation → cancel_all_timeout_objects: fake_timers.timers.0.root == null, real-heap root has no such node → signal_timeouts empty → cycle-break loop skipped.
    3. Global swap + GC: wrapper collected → AbortSignal refcount 2→1. Stuck at 1 forever.
    4. ~AbortSignal never runs → cancelTimer() never runs → boxed Timeout and C++ AbortSignal both leak for the process lifetime.

    Repeat once per isolated file that hits this pattern across a --isolate/--parallel run.

    Impact & fix

    This is a regression in exactly the scenario the PR targets (leaked fake timers under --isolate): the pre-PR path freed these; the post-PR path does not. Per REVIEW.md, refcounts must be provably balanced on every terminal path, and re-ordering a release before a fallible/consuming call requires re-auditing everything it pre-empts.

    Fix in FakeTimers::clear(): for EventLoopTimerTag::AbortSignalTimeout, recover the parent via AbortSignalTimeout::from_timer_ptr(timer), null .signal, and AbortSignal::opaque_ref(signal).unref() — mirroring cancel_all_timeout_objects. That also closes the identical pre-existing hole in vi.useRealTimers() / vi.clearAllTimers(), which share clear() ("fix the whole class in the same PR").

clear() previously only released the heap pin for TimeoutObject nodes.
AbortSignalTimeout nodes were unlinked with .signal still holding its
+1 on the C++ AbortSignal, leaving the AbortSignal <-> Timeout refcount
cycle intact so neither side ever freed.

This was a pre-existing leak in vi.useRealTimers()/vi.clearAllTimers()
and became a regression for the --isolate path once close_isolation_handles
started draining the fake heap before cancel_all_timeout_objects (which
would otherwise have walked the fake heap and broken the cycle).

Mirror the cycle-break from All::cancel_all_timeout_objects: null .signal
and unref() so ~AbortSignal -> cancelTimer() can free the box.
@robobun

robobun commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Good catch on the AbortSignalTimeout cycle. Fixed in 44c0e26: FakeTimers::clear() now nulls .signal and unref()s for AbortSignalTimeout entries, mirroring All::cancel_all_timeout_objects. That closes both the regression this PR would have introduced on the isolation path and the pre-existing hole in vi.useRealTimers() / vi.clearAllTimers(). Added AbortSignal.timeout() under fake timers to the isolation test fixture so the drain path is exercised.

Comment thread src/runtime/test_runner/timers/FakeTimers.rs Outdated

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/test_runner/timers/FakeTimers.rs`:
- Around line 157-166: Add a regression test covering the
EventLoopTimerTag::AbortSignalTimeout cleanup path that observes the AbortSignal
allocation/refcount being released after fake timers are deactivated. Ensure the
test fails if AbortSignalTimeout::signal is not cleared or
AbortSignal::opaque_ref(signal).unref() is omitted, while preserving the
existing isolation behavior.

In `@test/cli/test/isolation.test.ts`:
- Around line 330-334: Update the promise in the server setup around
server.listen so it rejects on the server's error event by attaching
server.once("error", reject), while retaining callback resolution for successful
binding. Ensure bind failures surface immediately instead of leaving the promise
pending.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: da21b9ad-5c41-46fd-8c40-cbebd994779b

📥 Commits

Reviewing files that changed from the base of the PR and between 8afcd4b and 44c0e26.

📒 Files selected for processing (3)
  • src/runtime/jsc_hooks.rs
  • src/runtime/test_runner/timers/FakeTimers.rs
  • test/cli/test/isolation.test.ts

Comment thread src/runtime/test_runner/timers/FakeTimers.rs Outdated
Comment thread test/cli/test/isolation.test.ts
Comment thread src/runtime/jsc_hooks.rs
Comment thread src/runtime/test_runner/timers/FakeTimers.rs Outdated
…rain

Reverts the AbortSignalTimeout unref in FakeTimers::clear() from 44c0e26.
That unref is unsafe on the mid-test useRealTimers()/clearAllTimers() path:
it leaves C++ m_timeout non-null, so a later removeEventListener reaches
eventListenersDidChange() and deref()s the same +1 again, freeing the
AbortSignal under its live JS wrapper (ASAN heap-use-after-free in
JSAbortSignal visitChildren, reproduced with Malloc=1).

Instead, the --isolate file boundary now calls a new reset_for_isolation()
that only flips active=false and clears CURRENT_TIME. The fake heap is
left populated so swap_global_for_test_isolation -> cancel_all_timeout_objects
(which already walks both heaps and breaks the AbortSignal cycle, and runs
at a point where no user JS can touch the outgoing signals) drains it.

clear()/deactivate() are restored to their pre-PR form.
Comment thread src/runtime/jsc_hooks.rs
Comment thread src/runtime/test_runner/timers/FakeTimers.rs

@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 final reset_for_isolation approach is the minimal safe fix.

Checked that both call sites (test_command.rs:3125, parallel/runner.rs:550) run close_isolation_handles immediately before swap_global_for_test_isolation, whose cancel_all_timers hook (jsc_hooks.rs:1601 → All::cancel_all_timeout_objects) still walks the un-drained fake heap and releases both TimeoutObject pins and AbortSignalTimeout cycle refs. CURRENT_TIME.clear also resets vm.overridden_performance_now; the setTimeout.clock marker doesn't need clearing since the global is swapped. The two earlier findings (AbortSignal cycle leak, mid-test clear() UAF) are both resolved by ed45e77 reverting to flag-only reset.

Extended reasoning...

Overview

Fixes a CI flake where vi.useFakeTimers() state (stored on the per-thread timer::All, not the JS global) leaks across the --isolate / --parallel file boundary, causing the next file's setTimeout-driven callbacks (e.g. net.Server 'listening') to route into the never-driven fake heap and hang. Net change: a 2-line FakeTimers::reset_for_isolation() that flips active = false and clears CURRENT_TIME, called from close_isolation_handles before the global swap; plus a 60-line isolation test covering both --isolate and --parallel.

Security risks

None. Test-runner isolation boundary only; no user-facing API, parsing, or network surface.

Level of scrutiny

Medium — touches per-thread timer state and the isolation teardown path, both of which have refcount/GC-lifetime implications. This PR went through two revision cycles on exactly those concerns: my first review caught that draining the fake heap in close_isolation_handles would bypass cancel_all_timeout_objects' AbortSignal cycle-break; the second caught that adding the cycle-break to FakeTimers::clear() (reachable mid-test via vi.useRealTimers()) would double-deref() the C++ AbortSignal when eventListenersDidChange later fires. The author confirmed the UAF under ASAN and landed the minimal fix in ed45e77: reset mode only, leave the heap intact for the existing swap-time drain. The final diff touches no refcounts and no heap nodes.

Other factors

  • Verified the ordering contract: both callers run close_isolation_handles → swap_global_for_test_isolation back-to-back, and the swap's cancel_all_timers hook walks fake_timers.timers.0.root (timer/mod.rs:1193), so leaving the heap populated is correct.
  • The redundant !all.is_null() guard is harmless (state is already checked non-null at function entry).
  • Test follows the neighboring chdir isolation test's exact structure (serial --isolate + gated --parallel=2), wires server.once('error', reject), and bounds failure with --timeout=5000. Evidence block shows fail-before (ASAN) / pass-after on both debug and release.
  • All CodeRabbit and comment-cop threads are resolved.

@robobun

robobun commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Opened #36398 which resets at the end of TestCommand::run instead of close_isolation_handles, so the same hook covers plain sequential runs (no --isolate) and standalone setSystemTime() as well. If that approach looks right this one can close in its favor.

@robobun

robobun commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Build 85401 failures are all pre-existing flakes unrelated to this diff (parallel.test.ts dots ordering, security-scanner TTY, fetch-tls-abortsignal timing, 08965 on Windows, quic RTT, fastutf8stream-reopen, bun-install-registry, spawn-stdin-destroy, spawn-streaming-stdout). isolation.test.ts, 29684.test.ts, and the fake-timers suite all pass on every lane. Ready for review.

@Jarred-Sumner
Jarred-Sumner merged commit fb03d39 into main Jul 30, 2026
55 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/1c5244f9/isolate-fake-timers-reset branch July 30, 2026 02:00
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.

2 participants