From 2800d17a40f93198186f1bf7dabe93b74f3efea8 Mon Sep 17 00:00:00 2001 From: martin veillette Date: Mon, 10 Aug 2026 23:33:40 -0400 Subject: [PATCH] Fix memory-leak test timeout by early-exiting GC loop for multi-ref case --- tests/memory-leak.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/memory-leak.test.ts b/tests/memory-leak.test.ts index 535b034..1a22614 100644 --- a/tests/memory-leak.test.ts +++ b/tests/memory-leak.test.ts @@ -16,18 +16,21 @@ import { VideoPlaybackModel } from "../src/track-lab/model/VideoPlaybackModel.js import { PlaybackControlsNode } from "../src/track-lab/view/PlaybackControlsNode.js"; /** - * Force garbage collection with multiple passes. When `earlyExitRef` is supplied - * the loop bails as soon as the object is confirmed collected. The setTimeout(0) - * yield after a live deref() avoids the WeakRef macrotask-liveness pin. + * Force garbage collection with multiple passes. When `earlyExitRefs` is supplied + * the loop bails as soon as every referenced object is confirmed collected. The + * setTimeout(0) yield after a live deref() avoids the WeakRef macrotask-liveness pin. + * Without early-exit refs the loop always runs all passes, which on a slow `gc()` + * can exceed the Vitest testTimeout — always pass refs when you have them. */ -async function forceGC(earlyExitRef?: WeakRef): Promise { +async function forceGC(earlyExitRefs?: WeakRef | readonly WeakRef[]): Promise { + const refs = earlyExitRefs === undefined ? [] : Array.isArray(earlyExitRefs) ? earlyExitRefs : [earlyExitRefs]; for (let i = 0; i < 15; i++) { globalThis.gc?.(); await new Promise((r) => setTimeout(r, 50)); - if (earlyExitRef !== undefined && earlyExitRef.deref() === undefined) { + if (refs.length > 0 && refs.every((ref) => ref.deref() === undefined)) { return; } - if (earlyExitRef !== undefined) { + if (refs.length > 0) { await new Promise((r) => setTimeout(r, 0)); } } @@ -151,7 +154,7 @@ describe("Memory leak regression", () => { for (let i = 0; i < 10; i++) { refs.push(createAndDisposeGraphControlsPanel()); } - await forceGC(); + await forceGC(refs); const survivors = refs.filter((r) => r.deref() !== undefined).length; expect(survivors).toBe(0); });