perf(worktree): keep the retirement backfill off the blocking WSL probe

The backfill runs on composer repo-select, not just at create time, and it
derived the probe path synchronously — which for a WSL repo with a mirrored
workspace dir reaches getWslHome and its blocking execFileSync('wsl.exe').
A stopped distro froze the main process for up to 5s on composer open.

Adds an async twin of computeWorktreePath and uses it for the probe. Resolving
the home there also warms the shared cache, so later sync callers are free.

Also stops memoizing the collision key when the WSL home is still unresolved:
only the success path is cached upstream, so caching the fallback namespace
would strand the repo there for the rest of the session.
This commit is contained in:
Brennan Benson
2026-08-13 18:24:31 -07:00
parent 5052f1ccfa
commit 348bf5d472
2 changed files with 38 additions and 1 deletions
+32 -1
View File
@@ -4,7 +4,7 @@ import { isWindowsAbsolutePathLike, resolveRuntimePath } from '../../shared/cros
import { isWslUncPath } from '../../shared/wsl-paths'
import { splitWorktreeId } from '../../shared/worktree-id'
import { replaceKnownEmojiWithShortcodes } from '../../shared/emoji-shortcode-catalog'
import { getWslHome, parseWslPath } from '../wsl'
import { getWslHome, getWslHomeAsync, parseWslPath } from '../wsl'
type WorktreePathSettings = Pick<GlobalSettings, 'nestWorkspaces' | 'workspaceDir'>
type WorktreeBasePathRepo = Pick<Repo, 'path' | 'worktreeBasePath'>
@@ -112,6 +112,37 @@ export function computeWorktreePath(
return pathOps.join(workspaceRoot, sanitizedName)
}
/** Async twin of computeWorktreePath. Same result; resolves the WSL home without blocking the main
* thread, so callers off the create path never freeze the app on a stopped distro. */
export async function computeWorktreePathAsync(
sanitizedName: string,
repoPath: string,
settings: WorktreePathSettings
): Promise<string> {
const workspaceRoot = await computeWorkspaceRootAsync(repoPath, settings)
const pathOps = getRuntimePathOps(repoPath, workspaceRoot)
if (settings.nestWorkspaces) {
const repoName = pathOps.basename(repoPath).replace(/\.git$/, '')
return pathOps.join(workspaceRoot, repoName, sanitizedName)
}
return pathOps.join(workspaceRoot, sanitizedName)
}
async function computeWorkspaceRootAsync(
repoPath: string,
settings: { workspaceDir: string }
): Promise<string> {
const wsl = parseWslPath(repoPath)
if (wsl && shouldMirrorWorkspaceDirInsideWsl(repoPath, settings.workspaceDir)) {
const wslHome = await getWslHomeAsync(wsl.distro)
if (wslHome) {
return win32.join(wslHome, 'orca', 'workspaces')
}
}
return resolveWorkspaceDirForRepo(repoPath, settings.workspaceDir)
}
export function computeWorkspaceRoot(repoPath: string, settings: { workspaceDir: string }): string {
const wsl = parseWslPath(repoPath)
if (wsl && shouldMirrorWorkspaceDirInsideWsl(repoPath, settings.workspaceDir)) {
+6
View File
@@ -303,6 +303,12 @@ export function getWslHome(distro: string): string | null {
}
}
/** Pure cache lookup — never probes. Lets callers that memoize a derived value avoid caching one
* built from the unresolved fallback, since only the success path is cached above. */
export function hasCachedWslHome(distro: string): boolean {
return wslHomeCache.has(distro)
}
export async function getWslHomeAsync(distro: string): Promise<string | null> {
if (wslHomeCache.has(distro)) {
return wslHomeCache.get(distro)!