perf(startup): dedupe opencode version probes

This commit is contained in:
777genius 2026-05-26 10:32:46 +03:00
parent b88ca42fe3
commit a8ac52b6f3
2 changed files with 321 additions and 33 deletions

View file

@ -27,6 +27,10 @@ const MAX_TARBALL_BYTES = 250 * 1024 * 1024;
const MAX_BINARY_BYTES = 350 * 1024 * 1024;
const FETCH_TIMEOUT_MS = 60_000;
const VERSION_TIMEOUT_MS = 10_000;
const VERSION_PROBE_SUCCESS_CACHE_TTL_MS = 30_000;
const VERSION_PROBE_FAILURE_CACHE_TTL_MS = 5_000;
const RUNTIME_STATUS_SUCCESS_CACHE_TTL_MS = 30_000;
const RUNTIME_STATUS_FAILURE_CACHE_TTL_MS = 5_000;
interface NpmPackageMetadata {
name?: string;
@ -97,15 +101,8 @@ export async function resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath(): Prom
if (!binaryPath) {
return null;
}
try {
await execCli(binaryPath, ['--version'], {
timeout: VERSION_TIMEOUT_MS,
windowsHide: true,
});
return binaryPath;
} catch {
return null;
}
const version = await probeOpenCodeBinaryVersionCached(binaryPath);
return version.ok ? binaryPath : null;
}
function getExecutableName(): string {
@ -173,6 +170,31 @@ type VerifiedOpenCodeBinaryProbe =
| { ok: true; binaryPath: string; version: string | null }
| { ok: false; firstFailure: { binaryPath: string; error: string } | null };
interface CachedVersionProbe {
result: OpenCodeBinaryVersionProbe;
cachedAt: number;
ttlMs: number;
}
interface CachedPathProbe {
result: VerifiedOpenCodeBinaryProbe;
cachedAt: number;
ttlMs: number;
}
interface CachedRuntimeBinaryResolve {
binaryPath: string | null;
cachedAt: number;
ttlMs: number;
}
const versionProbeCache = new Map<string, CachedVersionProbe>();
const versionProbeInFlight = new Map<string, Promise<OpenCodeBinaryVersionProbe>>();
const pathProbeCache = new Map<string, CachedPathProbe>();
const pathProbeInFlight = new Map<string, Promise<VerifiedOpenCodeBinaryProbe>>();
const runtimeBinaryResolveCache = new Map<string, CachedRuntimeBinaryResolve>();
const runtimeBinaryResolveInFlight = new Map<string, Promise<string | null>>();
async function probeOpenCodeBinaryVersion(binaryPath: string): Promise<OpenCodeBinaryVersionProbe> {
try {
const { stdout } = await execCli(binaryPath, ['--version'], {
@ -190,6 +212,42 @@ function normalizeBinaryCandidateForCompare(binaryPath: string): string {
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
}
function getVersionProbeTtlMs(result: OpenCodeBinaryVersionProbe): number {
return result.ok ? VERSION_PROBE_SUCCESS_CACHE_TTL_MS : VERSION_PROBE_FAILURE_CACHE_TTL_MS;
}
async function probeOpenCodeBinaryVersionCached(
binaryPath: string
): Promise<OpenCodeBinaryVersionProbe> {
const cacheKey = normalizeBinaryCandidateForCompare(binaryPath);
const cached = versionProbeCache.get(cacheKey);
if (cached && Date.now() - cached.cachedAt < cached.ttlMs) {
return cached.result;
}
const inFlight = versionProbeInFlight.get(cacheKey);
if (inFlight) {
return inFlight;
}
const request = probeOpenCodeBinaryVersion(binaryPath)
.then((result) => {
versionProbeCache.set(cacheKey, {
result,
cachedAt: Date.now(),
ttlMs: getVersionProbeTtlMs(result),
});
return result;
})
.finally(() => {
if (versionProbeInFlight.get(cacheKey) === request) {
versionProbeInFlight.delete(cacheKey);
}
});
versionProbeInFlight.set(cacheKey, request);
return request;
}
async function probeFirstWorkingOpenCodeBinaryCandidate(
candidates: string[],
seen: Set<string>,
@ -202,7 +260,7 @@ async function probeFirstWorkingOpenCodeBinaryCandidate(
continue;
}
seen.add(normalized);
const version = await probeOpenCodeBinaryVersion(binaryPath);
const version = await probeOpenCodeBinaryVersionCached(binaryPath);
if (version.ok) {
return { ok: true, binaryPath, version: version.version };
}
@ -217,6 +275,22 @@ interface OpenCodeRuntimeBinaryResolveOptions {
includeShellEnv?: boolean;
}
function getPathProbeCacheKey(options: OpenCodeRuntimeBinaryResolveOptions = {}): string {
if (options.includeShellEnv === false) {
return 'no-shell-env';
}
return `shell-env:${options.shellEnvTimeoutMs ?? RUNTIME_PATH_SHELL_ENV_TIMEOUT_MS}`;
}
function getPathProbeTtlMs(result: VerifiedOpenCodeBinaryProbe): number {
return result.ok ? VERSION_PROBE_SUCCESS_CACHE_TTL_MS : VERSION_PROBE_FAILURE_CACHE_TTL_MS;
}
function getRuntimeBinaryResolveTtlMs(binaryPath: string | null): number {
return binaryPath ? VERSION_PROBE_SUCCESS_CACHE_TTL_MS : VERSION_PROBE_FAILURE_CACHE_TTL_MS;
}
async function probeFirstWorkingPathOpenCodeBinary(
options: OpenCodeRuntimeBinaryResolveOptions = {}
): Promise<VerifiedOpenCodeBinaryProbe> {
@ -268,20 +342,86 @@ async function probeFirstWorkingPathOpenCodeBinary(
);
}
async function probeFirstWorkingPathOpenCodeBinaryCached(
options: OpenCodeRuntimeBinaryResolveOptions = {}
): Promise<VerifiedOpenCodeBinaryProbe> {
const cacheKey = getPathProbeCacheKey(options);
const cached = pathProbeCache.get(cacheKey);
if (cached && Date.now() - cached.cachedAt < cached.ttlMs) {
return cached.result;
}
const inFlight = pathProbeInFlight.get(cacheKey);
if (inFlight) {
return inFlight;
}
const request = probeFirstWorkingPathOpenCodeBinary(options)
.then((result) => {
pathProbeCache.set(cacheKey, {
result,
cachedAt: Date.now(),
ttlMs: getPathProbeTtlMs(result),
});
return result;
})
.finally(() => {
if (pathProbeInFlight.get(cacheKey) === request) {
pathProbeInFlight.delete(cacheKey);
}
});
pathProbeInFlight.set(cacheKey, request);
return request;
}
async function resolveVerifiedPathOpenCodeBinaryPath(
options: OpenCodeRuntimeBinaryResolveOptions = {}
): Promise<string | null> {
const result = await probeFirstWorkingPathOpenCodeBinary(options);
const result = await probeFirstWorkingPathOpenCodeBinaryCached(options);
return result.ok ? result.binaryPath : null;
}
export function clearOpenCodeRuntimeBinaryResolverCache(): void {
versionProbeCache.clear();
versionProbeInFlight.clear();
pathProbeCache.clear();
pathProbeInFlight.clear();
runtimeBinaryResolveCache.clear();
runtimeBinaryResolveInFlight.clear();
}
export async function resolveVerifiedOpenCodeRuntimeBinaryPath(
options: OpenCodeRuntimeBinaryResolveOptions = {}
): Promise<string | null> {
return (
const cacheKey = getPathProbeCacheKey(options);
const cached = runtimeBinaryResolveCache.get(cacheKey);
if (cached && Date.now() - cached.cachedAt < cached.ttlMs) {
return cached.binaryPath;
}
const inFlight = runtimeBinaryResolveInFlight.get(cacheKey);
if (inFlight) {
return inFlight;
}
const request = (async () =>
(await resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath()) ??
(await resolveVerifiedPathOpenCodeBinaryPath(options))
);
(await resolveVerifiedPathOpenCodeBinaryPath(options)))()
.then((binaryPath) => {
runtimeBinaryResolveCache.set(cacheKey, {
binaryPath,
cachedAt: Date.now(),
ttlMs: getRuntimeBinaryResolveTtlMs(binaryPath),
});
return binaryPath;
})
.finally(() => {
if (runtimeBinaryResolveInFlight.get(cacheKey) === request) {
runtimeBinaryResolveInFlight.delete(cacheKey);
}
});
runtimeBinaryResolveInFlight.set(cacheKey, request);
return request;
}
function isLinuxMuslRuntime(): boolean {
@ -511,6 +651,8 @@ export class OpenCodeRuntimeInstallerService {
private mainWindow: BrowserWindow | null = null;
private installPromise: Promise<OpenCodeRuntimeStatus> | null = null;
private latestStatus: OpenCodeRuntimeStatus | null = null;
private latestStatusAt = 0;
private statusPromise: Promise<OpenCodeRuntimeStatus> | null = null;
setMainWindow(win: BrowserWindow | null): void {
this.mainWindow = win;
@ -518,16 +660,40 @@ export class OpenCodeRuntimeInstallerService {
invalidateStatusCache(): void {
this.latestStatus = null;
this.latestStatusAt = 0;
this.statusPromise = null;
clearOpenCodeRuntimeBinaryResolverCache();
}
async getStatus(): Promise<OpenCodeRuntimeStatus> {
if (this.installPromise && this.latestStatus) {
return this.latestStatus;
}
if (this.installPromise) {
return this.installPromise;
}
if (this.latestStatus && Date.now() - this.latestStatusAt < this.getStatusCacheTtlMs()) {
return this.latestStatus;
}
if (this.statusPromise) {
return this.statusPromise;
}
const request = this.resolveStatus().finally(() => {
if (this.statusPromise === request) {
this.statusPromise = null;
}
});
this.statusPromise = request;
return request;
}
private async resolveStatus(): Promise<OpenCodeRuntimeStatus> {
const appManagedStatus = await this.getAppManagedStatus();
if (appManagedStatus.installed) {
this.latestStatus = appManagedStatus;
this.rememberStatus(appManagedStatus);
return appManagedStatus;
}
@ -538,7 +704,7 @@ export class OpenCodeRuntimeInstallerService {
appManagedStatus.state !== 'failed'
? pathStatus
: appManagedStatus;
this.latestStatus = status;
this.rememberStatus(status);
return status;
}
@ -553,10 +719,21 @@ export class OpenCodeRuntimeInstallerService {
}
private publish(status: OpenCodeRuntimeStatus): void {
this.latestStatus = status;
this.rememberStatus(status);
safeSendToRenderer(this.mainWindow, CHANNEL, status);
}
private rememberStatus(status: OpenCodeRuntimeStatus): void {
this.latestStatus = status;
this.latestStatusAt = Date.now();
}
private getStatusCacheTtlMs(): number {
return this.latestStatus?.installed === true
? RUNTIME_STATUS_SUCCESS_CACHE_TTL_MS
: RUNTIME_STATUS_FAILURE_CACHE_TTL_MS;
}
private publishProgress(progress: OpenCodeRuntimeInstallProgress): void {
this.publish({
installed: false,
@ -571,32 +748,28 @@ export class OpenCodeRuntimeInstallerService {
if (!isAbsoluteExistingFile(manifest?.binaryPath)) {
return { installed: false, source: 'missing', state: 'idle' };
}
try {
const { stdout } = await execCli(manifest.binaryPath, ['--version'], {
timeout: VERSION_TIMEOUT_MS,
windowsHide: true,
});
const version = await probeOpenCodeBinaryVersionCached(manifest.binaryPath);
if (version.ok) {
return {
installed: true,
binaryPath: manifest.binaryPath,
version: stdout.trim() || manifest.version,
version: version.version ?? manifest.version,
source: 'app-managed',
state: 'ready',
};
} catch (error) {
return {
installed: false,
binaryPath: manifest.binaryPath,
version: manifest.version,
source: 'app-managed',
state: 'failed',
error: getErrorMessage(error),
};
}
return {
installed: false,
binaryPath: manifest.binaryPath,
version: manifest.version,
source: 'app-managed',
state: 'failed',
error: version.error,
};
}
private async getPathStatus(): Promise<OpenCodeRuntimeStatus> {
const result = await probeFirstWorkingPathOpenCodeBinary();
const result = await probeFirstWorkingPathOpenCodeBinaryCached();
if (result.ok) {
return {
installed: true,
@ -687,6 +860,7 @@ export class OpenCodeRuntimeInstallerService {
`${JSON.stringify(manifest, null, 2)}\n`,
'utf8'
);
clearOpenCodeRuntimeBinaryResolverCache();
const status: OpenCodeRuntimeStatus = {
installed: true,

View file

@ -28,6 +28,7 @@ vi.mock('@main/utils/shellEnv', () => ({
}));
import {
clearOpenCodeRuntimeBinaryResolverCache,
extractOpenCodeRuntimeBinaryFromTarball,
getOpenCodeRuntimePlatformCandidates,
OpenCodeRuntimeInstallerService,
@ -41,6 +42,20 @@ import { setAppDataBasePath } from '@main/utils/pathDecoder';
let tempRoot: string | null = null;
let originalPath: string | undefined;
function deferred<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
} {
let resolve!: (value: T) => void;
let reject!: (error: unknown) => void;
const promise = new Promise<T>((promiseResolve, promiseReject) => {
resolve = promiseResolve;
reject = promiseReject;
});
return { promise, resolve, reject };
}
function writeOctal(header: Buffer, offset: number, length: number, value: number): void {
const encoded = value
.toString(8)
@ -84,6 +99,7 @@ describe('OpenCodeRuntimeInstallerService resolver', () => {
setAppDataBasePath(tempRoot);
originalPath = process.env.PATH;
process.env.PATH = '';
clearOpenCodeRuntimeBinaryResolverCache();
execCliMock.mockReset();
execCliMock.mockResolvedValue({ stdout: 'opencode 1.0.0\n', stderr: '' });
buildMergedCliPathMock.mockReset();
@ -97,6 +113,7 @@ describe('OpenCodeRuntimeInstallerService resolver', () => {
});
afterEach(async () => {
clearOpenCodeRuntimeBinaryResolverCache();
setAppDataBasePath(null);
if (originalPath === undefined) {
delete process.env.PATH;
@ -194,11 +211,53 @@ describe('OpenCodeRuntimeInstallerService resolver', () => {
windowsHide: true,
});
clearOpenCodeRuntimeBinaryResolverCache();
execCliMock.mockRejectedValueOnce(new Error('broken binary'));
await expect(resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath()).resolves.toBeNull();
});
it('coalesces concurrent app-managed OpenCode verification probes', async () => {
const binaryPath = path.join(
tempRoot!,
'data',
'runtimes',
'opencode',
'versions',
'1.0.0',
'opencode-test',
'opencode'
);
const manifestPath = path.join(tempRoot!, 'data', 'runtimes', 'opencode', 'current.json');
await mkdir(path.dirname(binaryPath), { recursive: true });
await mkdir(path.dirname(manifestPath), { recursive: true });
await writeFile(binaryPath, 'binary', { mode: 0o755 });
await writeFile(
manifestPath,
`${JSON.stringify({
schemaVersion: 1,
version: '1.0.0',
platformPackage: 'opencode-test',
binaryPath,
integrity: 'sha512-test',
installedAt: '2026-05-12T00:00:00.000Z',
})}\n`,
'utf8'
);
const versionProbe = deferred<{ stdout: string; stderr: string }>();
execCliMock.mockReturnValue(versionProbe.promise);
const first = resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath();
const second = resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath();
await vi.waitFor(() => expect(execCliMock).toHaveBeenCalledTimes(1));
versionProbe.resolve({ stdout: 'opencode 1.0.0\n', stderr: '' });
await expect(Promise.all([first, second])).resolves.toEqual([binaryPath, binaryPath]);
await expect(resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath()).resolves.toBe(binaryPath);
expect(execCliMock).toHaveBeenCalledTimes(1);
});
it('returns a verified OpenCode binary from best-effort shell PATH when app-managed runtime is absent', async () => {
const binaryPath = path.join(tempRoot!, 'homebrew', 'bin', 'opencode');
await mkdir(path.dirname(binaryPath), { recursive: true });
@ -222,6 +281,61 @@ describe('OpenCodeRuntimeInstallerService resolver', () => {
});
});
it('coalesces concurrent verified OpenCode PATH probes and reuses the warm result', async () => {
const binaryPath = path.join(tempRoot!, 'homebrew', 'bin', 'opencode');
await mkdir(path.dirname(binaryPath), { recursive: true });
await writeFile(binaryPath, 'binary', { mode: 0o755 });
resolveInteractiveShellEnvBestEffortMock.mockResolvedValue({
PATH: path.dirname(binaryPath),
});
getShellPreferredHomeMock.mockReturnValue(tempRoot!);
const versionProbe = deferred<{ stdout: string; stderr: string }>();
execCliMock.mockReturnValue(versionProbe.promise);
const first = resolveVerifiedOpenCodeRuntimeBinaryPath({ shellEnvTimeoutMs: 0 });
const second = resolveVerifiedOpenCodeRuntimeBinaryPath({ shellEnvTimeoutMs: 0 });
await vi.waitFor(() => expect(execCliMock).toHaveBeenCalledTimes(1));
versionProbe.resolve({ stdout: 'opencode 1.0.0\n', stderr: '' });
await expect(Promise.all([first, second])).resolves.toEqual([binaryPath, binaryPath]);
await expect(resolveVerifiedOpenCodeRuntimeBinaryPath({ shellEnvTimeoutMs: 0 })).resolves.toBe(
binaryPath
);
expect(execCliMock).toHaveBeenCalledTimes(1);
expect(resolveInteractiveShellEnvBestEffortMock).toHaveBeenCalledTimes(1);
});
it('coalesces concurrent OpenCode runtime status checks and serves a short warm cache', async () => {
const binaryPath = path.join(tempRoot!, 'homebrew', 'bin', 'opencode');
await mkdir(path.dirname(binaryPath), { recursive: true });
await writeFile(binaryPath, 'binary', { mode: 0o755 });
resolveInteractiveShellEnvBestEffortMock.mockResolvedValue({
PATH: path.dirname(binaryPath),
});
getShellPreferredHomeMock.mockReturnValue(tempRoot!);
const versionProbe = deferred<{ stdout: string; stderr: string }>();
execCliMock.mockReturnValue(versionProbe.promise);
const service = new OpenCodeRuntimeInstallerService();
const first = service.getStatus();
const second = service.getStatus();
await vi.waitFor(() => expect(execCliMock).toHaveBeenCalledTimes(1));
versionProbe.resolve({ stdout: 'opencode 1.0.0\n', stderr: '' });
await expect(Promise.all([first, second])).resolves.toMatchObject([
{ installed: true, source: 'path', binaryPath },
{ installed: true, source: 'path', binaryPath },
]);
await expect(service.getStatus()).resolves.toMatchObject({
installed: true,
source: 'path',
binaryPath,
});
expect(execCliMock).toHaveBeenCalledTimes(1);
});
it('returns a verified OpenCode binary from the merged CLI PATH after zero-wait shell fallback', async () => {
const binaryPath = path.join(tempRoot!, 'merged-cli-path', 'bin', 'opencode');
await mkdir(path.dirname(binaryPath), { recursive: true });