mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 08:03:33 +00:00
fix(windows): drop a stale WSL availability failure once a distro list succeeds
The distro-list and availability caches expire independently, and getWslRepairReason checks availability first. So a definitive availability failure (numeric exit status or ENOENT) held for 10-30min would keep reporting `wsl-unavailable` even after `wsl --list --quiet` successfully returned a distro — i.e. over a WSL that demonstrably just answered. That is the same latch class this branch fixes, surviving in the gap between the two caches. A non-empty distro list proves wsl.exe ran, so drop the negative availability cache and let the next call re-probe. Scoped to non-empty lists only: those are cached for the process lifetime, so this cannot re-spawn the blocking 5s probe more than once. An empty list keeps its failure cache, since it re-probes on a 15s-to-5min schedule and would otherwise pay the blocking probe far too often.
This commit is contained in:
@@ -532,6 +532,59 @@ describe('WSL availability cache', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Why: the caches expire independently and `getWslRepairReason` checks availability
|
||||
// first, so a definitive failure held for 10min would report `wsl-unavailable` over a
|
||||
// WSL that just listed a distro. Finding a distro must drop the stale failure.
|
||||
it.each([
|
||||
['a definitive failure', { status: 1 }],
|
||||
['a timeout', { code: 'ETIMEDOUT', status: null, signal: 'SIGTERM' }]
|
||||
])('re-probes availability once a distro list succeeds after %s', (_label, errorShape) => {
|
||||
vi.useFakeTimers()
|
||||
execFileSyncMock.mockImplementationOnce(() => {
|
||||
throw Object.assign(new Error('probe failed'), errorShape)
|
||||
})
|
||||
|
||||
try {
|
||||
withPlatform('win32', () => {
|
||||
expect(isWslAvailable()).toBe(false)
|
||||
|
||||
// A distro turns up (WSL finished provisioning / was repaired mid-session).
|
||||
execFileSyncMock.mockReturnValueOnce('Ubuntu\n')
|
||||
expect(listWslDistros()).toEqual(['Ubuntu'])
|
||||
|
||||
// Without dropping the stale failure this would stay false for 10min.
|
||||
execFileSyncMock.mockReturnValueOnce('')
|
||||
expect(isWslAvailable()).toBe(true)
|
||||
})
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
// Why: an empty list re-probes on a 15s-to-5min schedule, so clearing the availability
|
||||
// failure on every empty probe would re-spawn the blocking 5s probe far too often.
|
||||
it('does not drop an availability failure for an empty distro list', () => {
|
||||
vi.useFakeTimers()
|
||||
execFileSyncMock.mockImplementationOnce(() => {
|
||||
throw Object.assign(new Error('probe failed'), { status: 1 })
|
||||
})
|
||||
|
||||
try {
|
||||
withPlatform('win32', () => {
|
||||
expect(isWslAvailable()).toBe(false)
|
||||
execFileSyncMock.mockReturnValueOnce('')
|
||||
expect(listWslDistros()).toEqual([])
|
||||
expect(getCachedWslAvailability()).toBe(false)
|
||||
// Still inside the definitive window, so no re-probe was paid.
|
||||
expect(execFileSyncMock).toHaveBeenCalledTimes(2)
|
||||
expect(isWslAvailable()).toBe(false)
|
||||
expect(execFileSyncMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps reporting unavailable off Windows without probing', () => {
|
||||
withPlatform('darwin', () => {
|
||||
expect(isWslAvailable()).toBe(false)
|
||||
|
||||
@@ -137,6 +137,9 @@ function cacheWslDistroList(rawDistros: string[]): string[] {
|
||||
if (userDistros.length === 0 && wslDistroCache !== null && wslDistroCache.length > 0) {
|
||||
return wslDistroCache
|
||||
}
|
||||
if (userDistros.length > 0) {
|
||||
dropStaleWslAvailabilityFailure()
|
||||
}
|
||||
wslDistroCache = userDistros
|
||||
if (wslDistroCache.length === 0) {
|
||||
const now = Date.now()
|
||||
@@ -324,6 +327,17 @@ function isRetryableWslProbeFailure(error: unknown): boolean {
|
||||
return failure?.code !== 'ENOENT'
|
||||
}
|
||||
|
||||
// Why: the two caches expire independently, and `getWslRepairReason` checks availability
|
||||
// first — so a definitive failure held for 10-30min would report `wsl-unavailable` over a
|
||||
// WSL that just listed a distro for us. A non-empty list proves wsl.exe ran, so drop the
|
||||
// stale failure and let the next call re-probe. Non-empty lists are cached for the process
|
||||
// lifetime, so this cannot re-spawn the blocking probe more than once.
|
||||
function dropStaleWslAvailabilityFailure(): void {
|
||||
if (wslAvailableCache && !wslAvailableCache.available && !('unsupported' in wslAvailableCache)) {
|
||||
wslAvailableCache = null
|
||||
}
|
||||
}
|
||||
|
||||
function isWslAvailabilityCacheFresh(cache: WslAvailabilityCache): boolean {
|
||||
if (isPermanentWslAvailabilityCache(cache)) {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user