From 71f0c74d9e53a0f451d8cd32c4301bcfa2ab367b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 27 Oct 2026 03:29:12 -0700 Subject: [PATCH] fix(wsl): start no running-distro read inside the stop settle window (#34372) * fix(wsl): start no running-distro read inside the stop settle window settledStartedAfter slept with one setTimeout until the settle anchor. Node truncates timer delays to whole ms against a ms loop clock, so the wake can land up to ~2ms before the anchor by performance.now(), and the probe then started inside the window and could still see the stopping distro as running. That is the hook relay stop-settle flake ("expected true to be false"). Sleep until performance.now() has passed the anchor. * test(wsl): spy on setTimeout without a type assertion * test(wsl): fire the spied timer far early so a single sleep always fails --- src/main/wsl-background-distro-gate.test.ts | 27 ++++++++++++++++++++- src/main/wsl-background-distro-gate.ts | 7 +++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/wsl-background-distro-gate.test.ts b/src/main/wsl-background-distro-gate.test.ts index 3da5b338c21..75b226a9034 100644 --- a/src/main/wsl-background-distro-gate.test.ts +++ b/src/main/wsl-background-distro-gate.test.ts @@ -12,7 +12,9 @@ import { deferUntilWslDistroRunning, isWslDistroRunning, isWslDistroRunningForBackgroundWork, - noteWslDistrosRunning + noteWslDistroMayHaveStopped, + noteWslDistrosRunning, + WSL_STOP_SETTLE_MS } from './wsl-background-distro-gate' import { _resetRunningWslDistroCacheForTests, @@ -57,6 +59,29 @@ describe('isWslDistroRunning', () => { }) }) +describe('the stop settle window', () => { + it('starts no running read inside the window, even when a timer fires early', async () => { + // Node timers can wake ~2ms short; firing far earlier makes a single sleep fail every time. + const realSetTimeout = globalThis.setTimeout + vi.spyOn(globalThis, 'setTimeout').mockImplementation((handler, ms, ...args) => + realSetTimeout(handler, Math.max(0, (ms ?? 0) - 100), ...args) + ) + const probeStarts: number[] = [] + wslMocks.listRunningWslDistrosAsync.mockImplementation((options) => + resolveRunningWslDistros(async () => { + probeStarts.push(performance.now()) + return [] + }, options) + ) + const settledAt = performance.now() + WSL_STOP_SETTLE_MS + noteWslDistroMayHaveStopped('Ubuntu') + + await expect(isWslDistroRunning('Ubuntu')).resolves.toBe(false) + expect(probeStarts).toHaveLength(1) + expect(probeStarts[0]).toBeGreaterThanOrEqual(settledAt) + }) +}) + describe('resolveRunningWslDistros startedAfter', () => { it('answers from a confirmed read that started after the anchor, and reads again for a later one', async () => { const probe = vi.fn(async () => ['Ubuntu']) diff --git a/src/main/wsl-background-distro-gate.ts b/src/main/wsl-background-distro-gate.ts index be3dc9b70bd..457f9b6cf3e 100644 --- a/src/main/wsl-background-distro-gate.ts +++ b/src/main/wsl-background-distro-gate.ts @@ -83,9 +83,10 @@ async function settledStartedAfter( woken ?? -Infinity ) const anchor = floor === -Infinity ? startedAfter : Math.max(startedAfter ?? -Infinity, floor) - const waitMs = (anchor ?? -Infinity) - performance.now() - if (waitMs > 0) { - await new Promise((resolve) => setTimeout(resolve, waitMs)) + // Timers truncate to whole ms on a ms clock and can wake ~2ms short; sleep until the anchor has passed. + const settled = anchor ?? -Infinity + while (performance.now() < settled) { + await new Promise((resolve) => setTimeout(resolve, Math.ceil(settled - performance.now()))) } return anchor }