From 348bf5d4722374f7b3c3fe5ddf8635783cd18b98 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:24:31 -0700 Subject: [PATCH] perf(worktree): keep the retirement backfill off the blocking WSL probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/main/ipc/worktree-logic.ts | 33 ++++++++++++++++++++++++++++++++- src/main/wsl.ts | 6 ++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/ipc/worktree-logic.ts b/src/main/ipc/worktree-logic.ts index a056d657f02..fe61f943d14 100644 --- a/src/main/ipc/worktree-logic.ts +++ b/src/main/ipc/worktree-logic.ts @@ -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 type WorktreeBasePathRepo = Pick @@ -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 { + 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 { + 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)) { diff --git a/src/main/wsl.ts b/src/main/wsl.ts index 8311170faeb..31c1c9a8a5c 100644 --- a/src/main/wsl.ts +++ b/src/main/wsl.ts @@ -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 { if (wslHomeCache.has(distro)) { return wslHomeCache.get(distro)!