mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 00:27:39 +00:00
`computeWorkspaceRoot` resolves a WSL repo's mirror root through `getWslHome`,
which is a synchronous `execFileSync('wsl.exe', ...)` with a 5s timeout. Two
worktree preparation paths ran it on the Electron main thread:
`prepareLocalWorktreeRootForRepo` (repo registration, clone completion, repo
update, project host setup, folder->git upgrade) and `prepareWorktreeCreateForRepo`
(the speculative checkout started while the create composer is open). On a stopped
or cold distro that froze every window for up to 5s. Being fire-and-forget did not
help: only 3 of the 16 `prepareLocalWorktreeRootForRepo` call sites are `void`-ed,
the other 13 are awaited inside IPC handlers, and the sync probe blocks the main
thread either way. `prepareLocalWorktreeRootsForRepos` runs the same probe for
every repo from the settings-save handler.
Adopt the existing `computeWorkspaceRootAsync` (now exported) at those two call
sites, and give the two resolvers a shared mirror-distro decision and shared
root-from-home layout so they cannot drift apart.
Also thread the mirror distro into the prepare-side path settings.
`createLocalWorktree` passes `getWorktreeMirrorDistro(store, repo)` and
`prepareWorktreeCreateForRepo` did not, so a `C:\` repo on a WSL project runtime
prepared under `C:\workspaces` while the create click looked under the mirrored
WSL root: the keys never matched and every prepared checkout was discarded, after
paying for a full checkout that sat until the 5min TTL. Pre-existing on main;
included because it is the same line and the same resolver.
Scope of the win, stated precisely: only those two preparation paths stop
blocking. On a reachable distro `getWslHome` caches on success, so before this
change the first repo paid one blocking probe and the rest were cache hits -- the
change makes that one probe non-blocking, it does not remove N probes. Failed
probes are never cached, so on a stopped distro N repos did pay N sequential 5s
blocking probes and now share one in-flight async probe.
Costs: five sync `computeWorkspaceRoot` callers remain (allowed-roots resolution,
the create click in worktree-remote, CLI create, watch targets, worktree trash),
and `getWslHome` reads only `wslHomeCache` -- it cannot join an in-flight async
probe. The guaranteed synchronous cache warm-up therefore becomes a window in
which one of those callers can still block and can spawn a second concurrent
`wsl.exe`. Concretely: opening the create composer and clicking Create within a
few hundred ms on a cold distro now pays the freeze on the click instead of on the
background prep. Separately, the mirror-distro fix makes prepare spawn an async
`wsl.exe` home probe for `C:\` repos on a WSL runtime, where it previously spawned
none.
No race added: `prepareWorktreeCreateForRepo` computes the preparation key and
inserts the registry entry in one synchronous run after the await, so two
concurrent creates still dedupe to a single prepared checkout.
`worktree-create-preparation-wsl-root.test.ts` runs the real resolver through
prepare and then claims the entry with the production consume-side call shape
(including the mirror distro), so a divergence between the two resolvers fails a
test instead of silently discarding every prepared checkout.
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
import { mkdir } from 'node:fs/promises'
|
|
import type { GlobalSettings } from '../shared/global-settings-types'
|
|
import type { Repo } from '../shared/repo-types'
|
|
import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../shared/execution-host'
|
|
import { isFolderRepo } from '../shared/repo-kind'
|
|
import { computeWorkspaceRootAsync, getWorktreePathSettings } from './ipc/worktree-logic'
|
|
import { getWorktreeMirrorDistro } from './project-runtime-git-options'
|
|
import type { ProjectRuntimeResolutionStore } from './local-project-runtime-resolution'
|
|
|
|
// `localWindowsRuntimeDefault` is listed because the mirror distro is resolved
|
|
// from it plus the project catalog: a store that omits it prepares a different
|
|
// root than the create path uses.
|
|
type WorktreeRootPreparationSettings = Pick<GlobalSettings, 'workspaceDir' | 'nestWorkspaces'> &
|
|
Partial<Pick<GlobalSettings, 'localWindowsRuntimeDefault'>>
|
|
type WorktreeRootPreparationStore = {
|
|
getSettings: () => WorktreeRootPreparationSettings
|
|
getRepos: () => Repo[]
|
|
getProjects?: ProjectRuntimeResolutionStore['getProjects']
|
|
}
|
|
|
|
export async function prepareLocalWorktreeRootForRepo(
|
|
store: Pick<WorktreeRootPreparationStore, 'getSettings' | 'getProjects'>,
|
|
repo: Repo
|
|
): Promise<void> {
|
|
if (getRepoExecutionHostId(repo) !== LOCAL_EXECUTION_HOST_ID || isFolderRepo(repo)) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const root = await computeWorkspaceRootAsync(
|
|
repo.path,
|
|
getWorktreePathSettings(repo, store.getSettings(), getWorktreeMirrorDistro(store, repo))
|
|
)
|
|
// Why: mkdir touches the current root to preflight macOS TCC, while
|
|
// access remains scoped by recomputed settings instead of a permanent grant.
|
|
await mkdir(root, { recursive: true })
|
|
} catch (error) {
|
|
console.warn(`[worktree-root] failed to prepare worktree root for ${repo.path}:`, error)
|
|
}
|
|
}
|
|
|
|
export async function prepareLocalWorktreeRootsForRepos(
|
|
store: WorktreeRootPreparationStore
|
|
): Promise<void> {
|
|
await Promise.all(store.getRepos().map((repo) => prepareLocalWorktreeRootForRepo(store, repo)))
|
|
}
|