perf(windows): route relay host-capability probes through the async wsl/pwsh twins

A paired web/mobile client resolves `useWindowsTerminalCapabilities` to a local
target (TabBar's `isWebClient` gate, and `useSettingsNavigationMetadata` forces
`{kind:'local'}`), so the new re-probe arms there too. But `window.api.wsl/pwsh`
on a web client is not the ipc/app.ts channel — it is `host.wsl.*`/`host.pwsh.*`
over the runtime RPC, which still ran the sync probes and blocked the desktop
main event loop on `execFileSync('wsl.exe' | 'pwsh.exe')` for up to 5s per call.

Switch those handlers and the relay preflight capability probe to the async
twins added here; they share the same caches, dedupe and backoff, so remote
callers see no behavior change.
This commit is contained in:
Neil
2026-08-07 13:16:18 -07:00
committed by OrcaWin
parent 75fc337141
commit 2845c8cc72
4 changed files with 78 additions and 32 deletions
@@ -3,15 +3,31 @@ import { RpcDispatcher } from '../dispatcher'
import type { RpcRequest } from '../core'
import type { OrcaRuntimeService } from '../../orca-runtime'
const { isPwshAvailable, isWslAvailable, listWslDistros, isGitBashAvailable } = vi.hoisted(() => ({
const {
isPwshAvailable,
isPwshAvailableAsync,
isWslAvailable,
isWslAvailableAsync,
listWslDistros,
listWslDistrosAsync,
isGitBashAvailable
} = vi.hoisted(() => ({
isPwshAvailable: vi.fn(),
isPwshAvailableAsync: vi.fn(),
isWslAvailable: vi.fn(),
isWslAvailableAsync: vi.fn(),
listWslDistros: vi.fn(),
listWslDistrosAsync: vi.fn(),
isGitBashAvailable: vi.fn()
}))
vi.mock('../../../pwsh', () => ({ isPwshAvailable }))
vi.mock('../../../wsl', () => ({ isWslAvailable, listWslDistros }))
vi.mock('../../../pwsh', () => ({ isPwshAvailable, isPwshAvailableAsync }))
vi.mock('../../../wsl', () => ({
isWslAvailable,
isWslAvailableAsync,
listWslDistros,
listWslDistrosAsync
}))
vi.mock('../../../git-bash', () => ({ isGitBashAvailable }))
import { HOST_CAPABILITY_METHODS } from './host-capabilities'
@@ -23,15 +39,18 @@ function makeRequest(method: string, params?: unknown): RpcRequest {
describe('host capability RPC methods', () => {
beforeEach(() => {
isPwshAvailable.mockReset()
isPwshAvailableAsync.mockReset()
isWslAvailable.mockReset()
isWslAvailableAsync.mockReset()
listWslDistros.mockReset()
listWslDistrosAsync.mockReset()
isGitBashAvailable.mockReset()
})
it('reports Windows shell capability probes through explicit methods', async () => {
isPwshAvailable.mockReturnValue(true)
isWslAvailable.mockReturnValue(true)
listWslDistros.mockReturnValue(['Ubuntu'])
isPwshAvailableAsync.mockResolvedValue(true)
isWslAvailableAsync.mockResolvedValue(true)
listWslDistrosAsync.mockResolvedValue(['Ubuntu'])
isGitBashAvailable.mockReturnValue(true)
const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService
const dispatcher = new RpcDispatcher({ runtime, methods: HOST_CAPABILITY_METHODS })
@@ -55,4 +74,25 @@ describe('host capability RPC methods', () => {
result: true
})
})
// Why: web/mobile clients reach these over the relay, so a sync probe would stall the
// desktop main event loop on execFileSync wsl.exe/pwsh.exe for up to 5s per call.
it('answers the Windows shell capability methods without a blocking spawn', async () => {
isPwshAvailableAsync.mockResolvedValue(false)
isWslAvailableAsync.mockResolvedValue(false)
listWslDistrosAsync.mockResolvedValue([])
const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService
const dispatcher = new RpcDispatcher({ runtime, methods: HOST_CAPABILITY_METHODS })
await dispatcher.dispatch(makeRequest('host.wsl.isAvailable'))
await dispatcher.dispatch(makeRequest('host.wsl.listDistros'))
await dispatcher.dispatch(makeRequest('host.pwsh.isAvailable'))
expect(isWslAvailableAsync).toHaveBeenCalledTimes(1)
expect(listWslDistrosAsync).toHaveBeenCalledTimes(1)
expect(isPwshAvailableAsync).toHaveBeenCalledTimes(1)
expect(isWslAvailable).not.toHaveBeenCalled()
expect(listWslDistros).not.toHaveBeenCalled()
expect(isPwshAvailable).not.toHaveBeenCalled()
})
})
@@ -1,6 +1,6 @@
import { defineMethod, type RpcMethod } from '../core'
import { isPwshAvailable } from '../../../pwsh'
import { isWslAvailable, listWslDistros } from '../../../wsl'
import { isPwshAvailableAsync } from '../../../pwsh'
import { isWslAvailableAsync, listWslDistrosAsync } from '../../../wsl'
import { isGitBashAvailable } from '../../../git-bash'
export const HOST_CAPABILITY_METHODS: RpcMethod[] = [
@@ -9,20 +9,22 @@ export const HOST_CAPABILITY_METHODS: RpcMethod[] = [
params: null,
handler: async () => ({ platform: process.platform })
}),
// Why: paired web/mobile clients route capability reads here, so a sync probe would
// execFileSync wsl.exe/pwsh.exe on this host's main event loop for up to 5s per call.
defineMethod({
name: 'host.wsl.isAvailable',
params: null,
handler: async () => isWslAvailable()
handler: async () => isWslAvailableAsync()
}),
defineMethod({
name: 'host.wsl.listDistros',
params: null,
handler: async () => listWslDistros()
handler: async () => listWslDistrosAsync()
}),
defineMethod({
name: 'host.pwsh.isAvailable',
params: null,
handler: async () => isPwshAvailable()
handler: async () => isPwshAvailableAsync()
}),
defineMethod({
name: 'host.gitBash.isAvailable',
+20 -16
View File
@@ -5,13 +5,17 @@ const { execFileAsyncMock } = vi.hoisted(() => ({
execFileAsyncMock: vi.fn()
}))
const { isPwshAvailableMock, isWslAvailableMock, listWslDistrosMock, isGitBashAvailableMock } =
vi.hoisted(() => ({
isPwshAvailableMock: vi.fn(),
isWslAvailableMock: vi.fn(),
listWslDistrosMock: vi.fn(),
isGitBashAvailableMock: vi.fn()
}))
const {
isPwshAvailableAsyncMock,
isWslAvailableAsyncMock,
listWslDistrosAsyncMock,
isGitBashAvailableMock
} = vi.hoisted(() => ({
isPwshAvailableAsyncMock: vi.fn(),
isWslAvailableAsyncMock: vi.fn(),
listWslDistrosAsyncMock: vi.fn(),
isGitBashAvailableMock: vi.fn()
}))
vi.mock('child_process', () => {
const execFileWithPromisify = Object.assign(vi.fn(), {
@@ -20,10 +24,10 @@ vi.mock('child_process', () => {
return { execFile: execFileWithPromisify }
})
vi.mock('../main/pwsh', () => ({ isPwshAvailable: isPwshAvailableMock }))
vi.mock('../main/pwsh', () => ({ isPwshAvailableAsync: isPwshAvailableAsyncMock }))
vi.mock('../main/wsl', () => ({
isWslAvailable: isWslAvailableMock,
listWslDistros: listWslDistrosMock
isWslAvailableAsync: isWslAvailableAsyncMock,
listWslDistrosAsync: listWslDistrosAsyncMock
}))
vi.mock('../main/git-bash', () => ({ isGitBashAvailable: isGitBashAvailableMock }))
@@ -61,9 +65,9 @@ function fishLookupArgs(command: string): string[] {
beforeEach(() => {
execFileAsyncMock.mockReset()
isPwshAvailableMock.mockReset()
isWslAvailableMock.mockReset()
listWslDistrosMock.mockReset()
isPwshAvailableAsyncMock.mockReset()
isWslAvailableAsyncMock.mockReset()
listWslDistrosAsyncMock.mockReset()
isGitBashAvailableMock.mockReset()
})
@@ -319,9 +323,9 @@ describe('PreflightHandler', () => {
configurable: true,
value: 'win32'
})
isWslAvailableMock.mockReturnValue(true)
listWslDistrosMock.mockReturnValue(['Ubuntu'])
isPwshAvailableMock.mockReturnValue(true)
isWslAvailableAsyncMock.mockResolvedValue(true)
listWslDistrosAsyncMock.mockResolvedValue(['Ubuntu'])
isPwshAvailableAsyncMock.mockResolvedValue(true)
isGitBashAvailableMock.mockReturnValue(true)
const requestHandlers = new Map<string, (params: Record<string, unknown>) => Promise<unknown>>()
+5 -5
View File
@@ -4,8 +4,8 @@ import { promisify } from 'node:util'
import path, { win32 } from 'node:path'
import type { RelayDispatcher } from './dispatcher'
import { buildRelayCommandEnv } from './relay-command-env'
import { isPwshAvailable } from '../main/pwsh'
import { isWslAvailable, listWslDistros } from '../main/wsl'
import { isPwshAvailableAsync } from '../main/pwsh'
import { isWslAvailableAsync, listWslDistrosAsync } from '../main/wsl'
import { isGitBashAvailable } from '../main/git-bash'
import { buildPosixCommandPathLookupScript } from '../shared/posix-command-path-lookup'
@@ -101,11 +101,11 @@ export class PreflightHandler {
hostPlatform: NodeJS.Platform | null
}> {
const [wslAvailable, pwshAvailable, gitBashAvailable] = await Promise.all([
Promise.resolve(isWslAvailable()).catch(() => false),
Promise.resolve(isPwshAvailable()).catch(() => false),
isWslAvailableAsync().catch(() => false),
isPwshAvailableAsync().catch(() => false),
Promise.resolve(isGitBashAvailable()).catch(() => false)
])
const wslDistros = wslAvailable ? await Promise.resolve(listWslDistros()).catch(() => []) : []
const wslDistros = wslAvailable ? await listWslDistrosAsync().catch(() => []) : []
return {
wslAvailable,
wslDistros,