perf(wsl-host): start booting only the focused WSL host early at launch (E9, replaces #33141) (#33924)

* perf(wsl-host): start booting the distros the last run had open work on as the app launches

The WSL host connect can only start once the store has loaded, about 1.5-2 s into
startup on a slow PC, and its first command then waits for the distro to boot.
The service now remembers which distros had open work, and the next launch starts
booting just those right after taking the single-instance lock.

* perf(wsl-host): wake only the focused WSL host early at launch (replaces #33141's list)

open work on, which conflicts with E9 (a launch boots only the focused host). Each
focus entry now records just that host's distro for the next launch's early boot,
and focusing a workspace off WSL (reported as a null focus entry), turning WSL hosts
off, or a host that can't run clears it.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(wsl-host): the early-wake list is authoritative: deleted when off, forgotten on Remove

Review of #33924: the early wake runs before settings load, so it can't read the WSL
hosts setting. Turning WSL hosts off now deletes the list synchronously (emptying it
if the delete fails; a missing or unreadable list wakes nothing), and removing a host
drops it from the list. There is no pendingStop field on host records yet (E11); the
existing registered-host filter covers removed records until then.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Neil
2026-10-26 14:35:11 -07:00
committed by GitHub
co-authored by Claude
parent b7fb07c379
commit 2e67ba5a64
9 changed files with 259 additions and 14 deletions
+6
View File
@@ -31,6 +31,8 @@ import { profileStateDesktopRecoveryArgs } from './startup/profile-state-recover
import { ProfileStateRecoveryRequiredError } from './persistence/profile-state/profile-state-recovery-required'
import { getProfileUserDataPath } from './orca-profiles/profile-storage-paths'
import { leaseAppImageGenerationForCurrentProcess } from './cli/appimage-generation-lease'
import { wakeRememberedWslHosts } from './wsl/wsl-host-early-wake'
import { wslHostsGateOpen } from './wsl/wsl-host-gate'
function openMainWindow(options: { revealOnDidFinishLoad?: boolean } = {}): BrowserWindow {
return openMainWindowController(options)
@@ -103,6 +105,10 @@ const preflightReady = runMainProcessPreflight({
// Skip userData writes after losing the single-instance lock.
if (preflightReady) {
void leaseAppImageGenerationForCurrentProcess()
// Why this early: a cold distro takes seconds to boot; starting now overlaps it with app startup.
if (process.platform === 'win32' && wslHostsGateOpen()) {
wakeRememberedWslHosts(app.getPath('userData'))
}
app.on('open-url', (event, url) => {
if (!parseSkillShareId(url)) {
return
+3 -4
View File
@@ -223,9 +223,8 @@ export function registerWslHostHandlers(args: WslHostHandlerArgs): void {
wsl.setVisibleWork(visibleWork.report(windowId, ids))
})
// The user switched to a workspace on this host: the focus entry that may boot its distro.
ipcMain.handle('wslHosts:focusHost', (_event, request: { environmentId: string }) => {
if (typeof request?.environmentId === 'string') {
wsl.focusHost(request.environmentId)
}
ipcMain.handle('wslHosts:focusHost', (_event, request: { environmentId: string | null }) => {
// null: the user is on a workspace that isn't on a WSL host.
wsl.focusHost(typeof request?.environmentId === 'string' ? request.environmentId : null)
})
}
+67
View File
@@ -0,0 +1,67 @@
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { writeWslHostDeployment } from '../../shared/wsl-host-deployment-store'
import {
forgetWslHostToWake,
forgetWslHostsToWake,
rememberWslHostsToWake,
wakeRememberedWslHosts
} from './wsl-host-early-wake'
describe('WSL host early wake', () => {
let userDataPath: string
beforeEach(() => {
userDataPath = mkdtempSync(join(tmpdir(), 'orca-wsl-wake-'))
writeWslHostDeployment(userDataPath, 'wsl:ubuntu', {
kind: 'wsl',
distro: 'Ubuntu',
localPort: 41000,
remotePort: 6768,
environmentId: 'wsl:ubuntu',
createdAt: 1
})
})
afterEach(() => rmSync(userDataPath, { recursive: true, force: true }))
it('wakes only the remembered distros that are still WSL hosts', () => {
rememberWslHostsToWake(userDataPath, ['Ubuntu', 'Removed'])
const spawn = vi.fn()
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual(['Ubuntu'])
expect(spawn).toHaveBeenCalledExactlyOnceWith('Ubuntu')
})
it('wakes nothing once the open work is gone', () => {
rememberWslHostsToWake(userDataPath, ['Ubuntu'])
rememberWslHostsToWake(userDataPath, [])
const spawn = vi.fn()
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
expect(spawn).not.toHaveBeenCalled()
})
it('wakes nothing without a list, or from an unreadable one', () => {
const spawn = vi.fn()
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
writeFileSync(join(userDataPath, 'orca-wsl-host-wake.json'), '{"version":1,"distros":["a b"]}')
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
expect(spawn).not.toHaveBeenCalled()
})
it('deletes the list when WSL hosts are turned off, so the next launch wakes nothing', () => {
rememberWslHostsToWake(userDataPath, ['Ubuntu'])
forgetWslHostsToWake(userDataPath)
expect(existsSync(join(userDataPath, 'orca-wsl-host-wake.json'))).toBe(false)
const spawn = vi.fn()
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
// Nothing to delete is fine too.
expect(() => forgetWslHostsToWake(userDataPath)).not.toThrow()
})
it('forgets a removed host even while its record is still on disk', () => {
rememberWslHostsToWake(userDataPath, ['Ubuntu'])
forgetWslHostToWake(userDataPath, 'ubuntu')
const spawn = vi.fn()
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
})
})
+126
View File
@@ -0,0 +1,126 @@
/**
* Boots the distro of the WSL host the last run had focused, at the very start of the next launch,
* so the boot (seconds on a slow PC) overlaps the app's own startup instead of following it. Only
* the focused host is listed (E9: a launch boots the focused host, never others with saved tabs).
*/
import { rmSync } from 'node:fs'
import { join } from 'node:path'
import { z } from 'zod'
import { spawnProcess } from '@orca/process-host'
import { writeSecureJsonFileWithinLimit } from '../../shared/bounded-secure-json-file'
import { readNodeFileSyncWithinLimit } from '../../shared/node-bounded-file-reader'
import { readWslHostDeployments } from '../../shared/wsl-host-deployment-store'
import { buildWslExecArgs } from '../../shared/wsl-login-shell-command'
import { WslDistroNameSchema } from '../../shared/wsl-host-deployment-link'
import { resolveWslInteropSpawnCwd } from '../wsl-interop-spawn-directory'
import { wslDistroHolderKey } from './wsl-distro-holder-record'
import { resolveWslExecutablePath } from './wsl-executable-path'
const WAKE_FILE = 'orca-wsl-host-wake.json'
const MAX_WAKE_FILE_BYTES = 16 * 1024
const WakeListSchema = z.object({ version: z.literal(1), distros: z.array(WslDistroNameSchema) })
function wakeFile(userDataPath: string): string {
return join(userDataPath, WAKE_FILE)
}
function readWakeList(userDataPath: string): string[] {
try {
const { buffer } = readNodeFileSyncWithinLimit(wakeFile(userDataPath), MAX_WAKE_FILE_BYTES)
return WakeListSchema.parse(JSON.parse(buffer.toString('utf8'))).distros
} catch {
return []
}
}
/** Saves the distro of the host the user is focused on (or none); called on each focus entry. */
export function rememberWslHostsToWake(userDataPath: string, distros: readonly string[]): void {
const next = [...new Set(distros)].sort()
const current = readWakeList(userDataPath)
if (current.length === next.length && current.every((distro, i) => distro === next[i])) {
return
}
try {
writeSecureJsonFileWithinLimit(
wakeFile(userDataPath),
{ version: 1, distros: next },
MAX_WAKE_FILE_BYTES
)
} catch (error) {
console.warn('[wsl-hosts] Could not save the distros to wake at next launch:', error)
}
}
/**
* WSL hosts turned off: the file is the only thing the next launch reads (before settings load),
* so it goes now, synchronously. If it can't be deleted it is emptied instead, and if that fails
* too the failure is logged; a missing or unreadable file wakes nothing.
*/
export function forgetWslHostsToWake(userDataPath: string): void {
try {
rmSync(wakeFile(userDataPath), { force: true })
} catch (error) {
console.warn('[wsl-hosts] Could not delete the early-wake list; emptying it:', error)
rememberWslHostsToWake(userDataPath, [])
}
}
/** A removed host is never woken early, even before its record is gone. */
export function forgetWslHostToWake(userDataPath: string, distro: string): void {
const key = wslDistroHolderKey(distro)
const remembered = readWakeList(userDataPath)
if (remembered.some((entry) => wslDistroHolderKey(entry) === key)) {
rememberWslHostsToWake(
userDataPath,
remembered.filter((entry) => wslDistroHolderKey(entry) !== key)
)
}
}
export type WslWakeSpawn = (distro: string) => void
function spawnWake(distro: string): void {
const child = spawnProcess({
program: resolveWslExecutablePath(),
args: buildWslExecArgs(distro, ['true']),
cwd: resolveWslInteropSpawnCwd(),
env: { ...process.env, WSL_UTF8: '1' },
stdio: 'ignore'
})
child.on('error', () => {})
child.unref()
}
/**
* Starts booting each remembered distro that is still a registered WSL host. Fire and forget: the
* connect that follows does the real work and reports any failure.
*/
export function wakeRememberedWslHosts(
userDataPath: string,
spawn: WslWakeSpawn = spawnWake
): string[] {
const remembered = readWakeList(userDataPath)
if (remembered.length === 0) {
return []
}
let registered: Set<string>
try {
registered = new Set(
readWslHostDeployments(userDataPath).deployments.map((record) =>
wslDistroHolderKey(record.distro)
)
)
} catch {
return []
}
const woken = remembered.filter((distro) => registered.has(wslDistroHolderKey(distro)))
for (const distro of woken) {
try {
spawn(distro)
} catch (error) {
console.warn(`[wsl-hosts] Could not start waking ${distro}:`, error)
}
}
return woken
}
+31
View File
@@ -14,6 +14,7 @@ import type { WslHostServerStop, WslHostStatus } from '../../shared/wsl-host-typ
import { OrcadHostUnsupportedError } from '../orcad/orcad-host-unavailable'
import type { WslHostDistroCandidate } from './wsl-host-distro-list'
import { WslHostService, type WslHostConnector } from './wsl-host-service'
import { wakeRememberedWslHosts } from './wsl-host-early-wake'
import { WslOrcadLaunchDeferredError } from './wsl-orcad-connection-errors'
import type { WslOrcadConnection } from './wsl-orcad-connection'
@@ -670,6 +671,36 @@ describe('WslHostService', () => {
expect(knownWhileAdding).toBe(true)
})
it('remembers only the focused host for an early boot next launch (E9)', async () => {
const connector = makeConnector([UBUNTU])
const service = makeService(connector)
await service.add({ distro: 'Ubuntu' })
const spawn = vi.fn()
// Open work alone is not remembered.
service.setVisibleWork(['wsl:ubuntu'])
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
service.focusHost('wsl:ubuntu')
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual(['Ubuntu'])
service.focusHost(null)
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
service.focusHost('wsl:ubuntu')
enabled = false
await service.enabledChanged(false)
expect(wakeRememberedWslHosts(userDataPath, spawn)).toEqual([])
expect(spawn).toHaveBeenCalledOnce()
})
it('forgets the early-wake host when it is removed', async () => {
const connector = makeConnector([UBUNTU])
const service = makeService(connector)
await service.add({ distro: 'Ubuntu' })
service.focusHost('wsl:ubuntu')
await service.remove('wsl:ubuntu')
// Re-registering the distro must not bring the old early wake back.
await service.add({ distro: 'Ubuntu' })
expect(wakeRememberedWslHosts(userDataPath, vi.fn())).toEqual([])
})
it('adopts holders at startup only when a WSL host exists', async () => {
const connector = makeConnector([UBUNTU])
const service = makeService(connector)
+16 -5
View File
@@ -29,6 +29,11 @@ import {
import type { WslOrcadConnection } from './wsl-orcad-connection'
import { registerWslHost } from './wsl-host-registration'
import type { WslOrcadConnectIntent, WslOrcadConnectionStatus } from './wsl-orcad-connection-status'
import {
forgetWslHostToWake,
forgetWslHostsToWake,
rememberWslHostsToWake
} from './wsl-host-early-wake'
/** `stopped` and `unreachable` rest on a confirmed running list; `unavailable` could not read it. */
export type WslHostReachability = 'available' | 'unreachable' | 'stopped' | 'unavailable'
@@ -188,6 +193,7 @@ export class WslHostService {
await connector.releaseHolder(record.distro)
}
removeWslHostEnvironment(this.deps.userDataPath(), environmentId)
forgetWslHostToWake(this.deps.userDataPath(), record.distro)
this.visible.delete(environmentId)
this.parked.delete(environmentId)
} finally {
@@ -211,6 +217,7 @@ export class WslHostService {
return
}
this.disableGeneration += 1
forgetWslHostsToWake(this.deps.userDataPath())
for (const record of this.records()) {
await connector.disconnect(record.distro)
await connector.releaseHolder(record.distro)
@@ -276,14 +283,18 @@ export class WslHostService {
* The user switched to a workspace on this host (or launch restored it focused): the one
* non-explicit act that may boot its distro, and it lifts a park.
*/
focusHost(environmentId: string): void {
const record = this.recordForEnvironment(environmentId)
if (!record || !this.connector || !this.canRun() || this.removing.has(environmentId)) {
focusHost(environmentId: string | null): void {
const record = environmentId ? this.recordForEnvironment(environmentId) : null
const runnable =
record && this.connector && this.canRun() && !this.removing.has(record.environmentId)
// The next launch starts booting only this distro, early (none when off WSL or not runnable).
rememberWslHostsToWake(this.deps.userDataPath(), runnable ? [record.distro] : [])
if (!record || !runnable || !this.connector) {
return
}
this.parked.delete(environmentId)
this.parked.delete(record.environmentId)
void this.connector
.reachability(record.distro, environmentId)
.reachability(record.distro, record.environmentId)
.then((reachability) =>
reachability === 'available' ? undefined : this.connector?.connect(record.distro)
)
+1 -1
View File
@@ -23,7 +23,7 @@ export type WslHostsPreloadApi = {
/** The WSL hosts an open project or terminal needs; only these may boot a stopped distro. */
setVisibleWork: (args: { environmentIds: string[] }) => Promise<void>
/** The user switched to a workspace on this host; may boot its distro (E9). */
focusHost: (args: { environmentId: string }) => Promise<void>
focusHost: (args: { environmentId: string | null }) => Promise<void>
}
const wslHostsApi: WslHostsPreloadApi = {
@@ -19,7 +19,7 @@ const api = {
onStatusChanged: vi.fn(() => () => {}),
getStatuses: vi.fn(async () => []),
setVisibleWork: vi.fn(async (_args: { environmentIds: string[] }) => {}),
focusHost: vi.fn(async (_args: { environmentId: string }) => {})
focusHost: vi.fn(async (_args: { environmentId: string | null }) => {})
}
function seed(experimentalWslHosts: boolean, activeWorktreeId: string): void {
@@ -70,7 +70,12 @@ describe('WSL host focus entries (E9)', () => {
// Away and back: the visible set never changes (its tabs stay), but the return is an act.
useAppStore.setState({ activeWorktreeId: LOCAL_WORKTREE })
useAppStore.setState({ activeWorktreeId: WSL_WORKTREE })
expect(api.focusHost).toHaveBeenCalledTimes(2)
expect(api.focusHost.mock.calls).toEqual([
[{ environmentId: WSL_ENV }],
// Off WSL: the next launch has no host to start early.
[{ environmentId: null }],
[{ environmentId: WSL_ENV }]
])
expect(api.setVisibleWork).toHaveBeenCalledTimes(1)
unsubs.forEach((unsub) => unsub())
})
@@ -102,8 +102,8 @@ export function registerWslHostIpcBridge(unsubs: (() => void)[]): void {
// Switching to a workspace on a WSL host (or launch restoring one) is the user acting on it.
if (entry !== focusEntry) {
focusEntry = entry
if (focusedHost && typeof api.focusHost === 'function') {
void api.focusHost({ environmentId: focusedHost }).catch((error) => {
if (typeof api.focusHost === 'function') {
void api.focusHost({ environmentId: focusedHost ?? null }).catch((error) => {
console.error('Failed to report the focused WSL host:', error)
})
}