mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
perf(wsl): hold the distro alongside the read-only context probes (#35037)
* perf(wsl): hold the distro alongside the read-only context probes The kernel probe has already started the distro, so the holder now runs alongside the four read-only context reads instead of before them; it is still held before recovery, which may launch orcad. No new wsl.exe spawns. The WSL 1 check and the parallel hold move to wsl-orcad-held-context.ts. # Conflicts: # src/main/wsl/wsl-orcad-deploy.ts * fix(wsl): stop the context probes once the distro hold fails A failed hold left the context probes running; after the deploy disposes its transport they fall back to fresh wsl.exe execs, which could boot a distro that just went away (E9). The probes now run under a child signal of the connect's, aborted on a hold failure or any other rejection. * test(wsl): cover the context probes running while the hold is pending Moves the ordering check out of the deploy suite, which is at its line cap after main's new refusal tests, into the held-context suite.
This commit is contained in:
@@ -69,7 +69,7 @@ vi.mock('../managed-server/lifecycle/orcad-managed-runtime-context', async (impo
|
||||
probeManagedOrcadReadiness: mocks.probe
|
||||
}))
|
||||
|
||||
import { deployAndLaunchWslOrcad, isWsl1KernelRelease } from './wsl-orcad-deploy'
|
||||
import { deployAndLaunchWslOrcad } from './wsl-orcad-deploy'
|
||||
import { resetOrcadUpdateFailuresForTests } from '../managed-server/lifecycle/orcad-managed-update-failure-memory'
|
||||
|
||||
beforeEach(() => resetOrcadUpdateFailuresForTests())
|
||||
@@ -837,13 +837,3 @@ describe('deployAndLaunchWslOrcad across an app update (E1)', () => {
|
||||
expect(mocks.deploy.mock.calls[0][0].census.liveSessions).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isWsl1KernelRelease', () => {
|
||||
it('tells the WSL 1 syscall layer from a WSL 2 kernel, stock or custom', () => {
|
||||
expect(isWsl1KernelRelease('4.4.0-19041-Microsoft')).toBe(true)
|
||||
expect(isWsl1KernelRelease('4.4.0-22621-Microsoft\n')).toBe(true)
|
||||
expect(isWsl1KernelRelease('5.15.167.4-microsoft-standard-WSL2')).toBe(false)
|
||||
expect(isWsl1KernelRelease('6.6.36.6-microsoft-standard-WSL2+')).toBe(false)
|
||||
expect(isWsl1KernelRelease('6.10.0-custom')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,6 @@ import { pruneWslOrcadVersionsUntilClean } from './wsl-orcad-version-gc-memo'
|
||||
import { wakeAlreadyActiveOrcad } from '../ssh/orcad-already-active-wake'
|
||||
import { OrcadActiveReadinessError } from '../ssh/orcad-active-readiness'
|
||||
import { WslDistroNotRunningError } from './wsl-orcad-connection-errors'
|
||||
import { resolveOrcadRemoteContext } from '../managed-server/install/orcad-remote-context'
|
||||
import { deployOrcad } from '../ssh/orcad-remote-deploy'
|
||||
import { isWslMoveUnderwayOnHost } from '../persistence/moving-wsl-projects/wsl-move-running-moves'
|
||||
import { ORCAD_PID_FILENAME } from '../managed-server/install/orcad-remote-host-support'
|
||||
@@ -37,10 +36,10 @@ import { joinRemotePath } from '../ssh/ssh-remote-platform'
|
||||
import { readLocalFullVersion } from '../managed-server/install/remote-versioned-install'
|
||||
import { createWslDistroHolder, type WslDistroHolder } from './wsl-distro-holder'
|
||||
import { wslDistroHolderKey } from './wsl-distro-holder-record'
|
||||
import { refuseMissingWslDistro, WslDistroVersion1Error } from './wsl-distro-unsupported'
|
||||
import { readWslDistroRegistryIdShared } from './wsl-distro-registry-id'
|
||||
import type { WslOrcadHostTarget } from './wsl-orcad-host-target'
|
||||
import { createWslOrcadHostTransport, type WslOrcadHostTransport } from './wsl-orcad-host-transport'
|
||||
import { holdWslDistroWithContext } from './wsl-orcad-held-context'
|
||||
import { readWslOrcadIncumbent, type WslOrcadIncumbent } from './wsl-orcad-incumbent'
|
||||
import { adoptUnrecordedWslOrcad } from './wsl-orcad-instance-adoption'
|
||||
import {
|
||||
@@ -98,11 +97,6 @@ export function defaultWslOrcadEnvironmentId(distro: string): string {
|
||||
return `wsl:${wslDistroHolderKey(distro)}`
|
||||
}
|
||||
|
||||
/** WSL 1 runs no Linux kernel, so the pinned Node and its pty cannot run there. */
|
||||
export function isWsl1KernelRelease(release: string): boolean {
|
||||
return /^\d+\.\d+\.\d+-\d+-Microsoft$/.test(release.trim())
|
||||
}
|
||||
|
||||
export async function deployAndLaunchWslOrcad(
|
||||
distro: string,
|
||||
options: WslOrcadDeployOptions = {}
|
||||
@@ -134,16 +128,13 @@ async function launchOverTransport(
|
||||
const { signal } = options
|
||||
let ownBuildVersion: string | null = null
|
||||
const conn = withOwnBuildLaunchEnv(transport, () => ownBuildVersion)
|
||||
const kernel = await execCommand(conn, 'uname -r', { signal }).catch(refuseMissingWslDistro(name))
|
||||
if (isWsl1KernelRelease(kernel)) {
|
||||
throw new WslDistroVersion1Error(name)
|
||||
}
|
||||
// Acquire a holder before launch or WSL can idle-stop orcad's distro.
|
||||
const holder = options.holder ?? createWslDistroHolder({ userDataPath: userDataPath() })
|
||||
const held = await holder.ensureHeld(name)
|
||||
// Read GUID alongside deploy; failure leaves the record without one.
|
||||
const distroId = (options.readRegistryId ?? readWslDistroRegistryIdShared)(name).catch(() => null)
|
||||
let context = await resolveOrcadRemoteContext(target, conn, signal)
|
||||
const first = await holdWslDistroWithContext(name, target, conn, holder, signal)
|
||||
const held = first.held
|
||||
let context = first.context
|
||||
const slot = managedOrcadSlot(context, ORCAD_MANAGED_REMOTE_PORT, signal)
|
||||
const recovery = await recoverInterruptedOrcadActivation(slot)
|
||||
if (recovery.outcome === 'pending' || recovery.outcome === 'refused') {
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { OrcadHostTransport } from '../ssh/orcad-host-transport'
|
||||
import type { WslDistroHolder } from './wsl-distro-holder'
|
||||
import type { WslOrcadHostTarget } from './wsl-orcad-host-target'
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const state: { spawned: string[]; refused: string[]; probeStep: (() => void) | null } = {
|
||||
spawned: [],
|
||||
refused: [],
|
||||
probeStep: null
|
||||
}
|
||||
return state
|
||||
})
|
||||
|
||||
// Like the real execCommand: an aborted signal refuses before anything is spawned.
|
||||
vi.mock('../ssh/remote-exec/remote-exec-command', () => ({
|
||||
execCommand: vi.fn(
|
||||
async (_conn: unknown, command: string, options?: { signal?: AbortSignal }) => {
|
||||
if (options?.signal?.aborted) {
|
||||
mocks.refused.push(command)
|
||||
throw options.signal.reason
|
||||
}
|
||||
mocks.spawned.push(command)
|
||||
return command === 'uname -r' ? '5.15.167.4-microsoft-standard-WSL2\n' : ''
|
||||
}
|
||||
)
|
||||
}))
|
||||
// Like the real context resolver: several probes in series, each passed the caller's signal.
|
||||
vi.mock('../managed-server/install/orcad-remote-context', async () => {
|
||||
const { execCommand } = await import('../ssh/remote-exec/remote-exec-command')
|
||||
return {
|
||||
resolveOrcadRemoteContext: async (
|
||||
_target: unknown,
|
||||
conn: OrcadHostTransport,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
for (const command of ['uname -sm', 'echo $HOME', 'ldd --version']) {
|
||||
await new Promise<void>((resolve) => {
|
||||
mocks.probeStep = resolve
|
||||
})
|
||||
await execCommand(conn, command, { signal })
|
||||
}
|
||||
return { remoteHome: '/home/u' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
import { holdWslDistroWithContext, isWsl1KernelRelease } from './wsl-orcad-held-context'
|
||||
|
||||
const target: WslOrcadHostTarget = { kind: 'wsl', id: 'wsl:u', distro: 'U', label: 'U' }
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every host call is mocked at its module.
|
||||
const conn = {} as OrcadHostTransport
|
||||
|
||||
function holderRejectingOn(gate: Promise<void>): WslDistroHolder {
|
||||
return {
|
||||
ensureHeld: vi.fn(async () => {
|
||||
await gate
|
||||
throw new Error('holder exited before the distro started')
|
||||
}),
|
||||
adoptHolders: vi.fn(),
|
||||
release: vi.fn(),
|
||||
releaseAll: vi.fn(),
|
||||
holderState: vi.fn()
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.spawned.length = 0
|
||||
mocks.refused.length = 0
|
||||
mocks.probeStep = null
|
||||
})
|
||||
|
||||
describe('holdWslDistroWithContext', () => {
|
||||
it('probes the context while the hold is pending, and resolves only once the distro is held', async () => {
|
||||
let finishHold: () => void = () => {}
|
||||
const holdDone = new Promise<void>((resolve) => {
|
||||
finishHold = resolve
|
||||
})
|
||||
const hold: WslDistroHolder = {
|
||||
...holderRejectingOn(holdDone),
|
||||
ensureHeld: vi.fn(async (distro: string) => {
|
||||
await holdDone
|
||||
return {
|
||||
outcome: 'spawned' as const,
|
||||
record: { version: 1 as const, distro, pid: 9, creationTimeMs: 1, startedAtMs: 1 }
|
||||
}
|
||||
})
|
||||
}
|
||||
let settled = false
|
||||
const held = holdWslDistroWithContext('U', target, conn, hold, undefined).finally(() => {
|
||||
settled = true
|
||||
})
|
||||
for (let step = 0; step < 3; step++) {
|
||||
await vi.waitFor(() => expect(mocks.probeStep).not.toBeNull())
|
||||
const next = mocks.probeStep
|
||||
mocks.probeStep = null
|
||||
next?.()
|
||||
}
|
||||
await vi.waitFor(() =>
|
||||
expect(mocks.spawned).toEqual(['uname -r', 'uname -sm', 'echo $HOME', 'ldd --version'])
|
||||
)
|
||||
// The deploy awaits this before recovery, which may launch orcad.
|
||||
expect(settled).toBe(false)
|
||||
|
||||
finishHold()
|
||||
|
||||
await expect(held).resolves.toMatchObject({
|
||||
held: { outcome: 'spawned' },
|
||||
context: { remoteHome: '/home/u' }
|
||||
})
|
||||
})
|
||||
|
||||
it('stops the context probes once the hold fails, so nothing can boot the distro again', async () => {
|
||||
let failHold: () => void = () => {}
|
||||
const holdFails = new Promise<void>((resolve) => {
|
||||
failHold = resolve
|
||||
})
|
||||
const held = holdWslDistroWithContext(
|
||||
'U',
|
||||
target,
|
||||
conn,
|
||||
holderRejectingOn(holdFails),
|
||||
undefined
|
||||
)
|
||||
await vi.waitFor(() => expect(mocks.probeStep).not.toBeNull())
|
||||
mocks.probeStep?.()
|
||||
await vi.waitFor(() => expect(mocks.spawned).toEqual(['uname -r', 'uname -sm']))
|
||||
|
||||
failHold()
|
||||
await expect(held).rejects.toThrow('holder exited')
|
||||
mocks.probeStep?.()
|
||||
await vi.waitFor(() => expect(mocks.refused).toEqual(['echo $HOME']))
|
||||
|
||||
expect(mocks.spawned).toEqual(['uname -r', 'uname -sm'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('isWsl1KernelRelease', () => {
|
||||
it('tells the WSL 1 syscall layer from a WSL 2 kernel, stock or custom', () => {
|
||||
expect(isWsl1KernelRelease('4.4.0-19041-Microsoft')).toBe(true)
|
||||
expect(isWsl1KernelRelease('4.4.0-22621-Microsoft\n')).toBe(true)
|
||||
expect(isWsl1KernelRelease('5.15.167.4-microsoft-standard-WSL2')).toBe(false)
|
||||
expect(isWsl1KernelRelease('6.6.36.6-microsoft-standard-WSL2+')).toBe(false)
|
||||
expect(isWsl1KernelRelease('6.10.0-custom')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
import { resolveOrcadRemoteContext } from '../managed-server/install/orcad-remote-context'
|
||||
import type { OrcadHostTransport } from '../ssh/orcad-host-transport'
|
||||
import { execCommand } from '../ssh/remote-exec/remote-exec-command'
|
||||
import type { WslDistroHolder } from './wsl-distro-holder'
|
||||
import { refuseMissingWslDistro, WslDistroVersion1Error } from './wsl-distro-unsupported'
|
||||
import type { WslOrcadHostTarget } from './wsl-orcad-host-target'
|
||||
|
||||
/** WSL 1 runs no Linux kernel, so the pinned Node and its pty cannot run there. */
|
||||
export function isWsl1KernelRelease(release: string): boolean {
|
||||
return /^\d+\.\d+\.\d+-\d+-Microsoft$/.test(release.trim())
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuses WSL 1, then acquires the holder alongside the read-only context probes: the kernel probe
|
||||
* already started the distro, so the holder spawns nothing extra, and it is held before the caller
|
||||
* may launch orcad (recovery included) or WSL could idle-stop its distro.
|
||||
*/
|
||||
export async function holdWslDistroWithContext(
|
||||
name: string,
|
||||
target: WslOrcadHostTarget,
|
||||
conn: OrcadHostTransport,
|
||||
holder: WslDistroHolder,
|
||||
signal: AbortSignal | undefined
|
||||
) {
|
||||
const kernel = await execCommand(conn, 'uname -r', { signal }).catch(refuseMissingWslDistro(name))
|
||||
if (isWsl1KernelRelease(kernel)) {
|
||||
throw new WslDistroVersion1Error(name)
|
||||
}
|
||||
// Once the hold fails, stop the probes: after dispose they fall back to fresh wsl.exe execs,
|
||||
// which would boot a distro that just went away (E9).
|
||||
const probes = new AbortController()
|
||||
if (signal?.aborted) {
|
||||
probes.abort(signal.reason)
|
||||
}
|
||||
const stopProbes = (): void => probes.abort(signal?.reason)
|
||||
signal?.addEventListener('abort', stopProbes, { once: true })
|
||||
try {
|
||||
const [held, context] = await Promise.all([
|
||||
holder.ensureHeld(name).catch((error: unknown) => {
|
||||
probes.abort(error)
|
||||
throw error
|
||||
}),
|
||||
resolveOrcadRemoteContext(target, conn, probes.signal)
|
||||
])
|
||||
return { held, context }
|
||||
} catch (error) {
|
||||
probes.abort(error)
|
||||
throw error
|
||||
} finally {
|
||||
signal?.removeEventListener('abort', stopProbes)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user