From b88ca42fe3d39989d1d297132a73285db0903d4b Mon Sep 17 00:00:00 2001 From: 777genius Date: Tue, 26 May 2026 09:12:05 +0300 Subject: [PATCH] fix(startup): serialize provider runtime checks --- src/main/ipc/cliInstaller.ts | 16 +++++++++--- test/main/ipc/cliInstaller.test.ts | 40 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/main/ipc/cliInstaller.ts b/src/main/ipc/cliInstaller.ts index 16d618f3..9775cc43 100644 --- a/src/main/ipc/cliInstaller.ts +++ b/src/main/ipc/cliInstaller.ts @@ -38,6 +38,7 @@ const logger = createLogger('IPC:cliInstaller'); let service: CliInstallerService; const statusInFlight = new Map>(); const providerStatusInFlight = new Map>(); +let providerRuntimeRequestTail: Promise = Promise.resolve(); const cachedStatus = new Map< CliInstallerProviderStatusMode, { value: CliInstallationStatus; at: number } @@ -110,11 +111,21 @@ function canUseStatusForCacheKey( ); } +function runProviderRuntimeRequest(operation: () => Promise): Promise { + const request = providerRuntimeRequestTail.then(operation, operation); + providerRuntimeRequestTail = request.then( + () => undefined, + () => undefined + ); + return request; +} + /** * Initializes CLI installer handlers with the service instance. */ export function initializeCliInstallerHandlers(installerService: CliInstallerService): void { service = installerService; + providerRuntimeRequestTail = Promise.resolve(); } /** @@ -255,8 +266,7 @@ async function handleGetProviderStatus( } const generation = statusCacheGeneration; - const request = service - .getProviderStatus(providerId) + const request = runProviderRuntimeRequest(() => service.getProviderStatus(providerId)) .then((status) => { if (generation === statusCacheGeneration) { patchCachedProviderStatus(status); @@ -296,7 +306,7 @@ async function handleVerifyProviderModels( ): Promise> { try { const generation = statusCacheGeneration; - const status = await service.verifyProviderModels(providerId); + const status = await runProviderRuntimeRequest(() => service.verifyProviderModels(providerId)); if (generation === statusCacheGeneration) { patchCachedProviderStatus(status); } diff --git a/test/main/ipc/cliInstaller.test.ts b/test/main/ipc/cliInstaller.test.ts index abf7878a..5e4ff5f9 100644 --- a/test/main/ipc/cliInstaller.test.ts +++ b/test/main/ipc/cliInstaller.test.ts @@ -221,6 +221,46 @@ describe('cliInstaller IPC handlers', () => { expect(service.invalidateStatusCache).toHaveBeenCalledTimes(1); }); + it('serializes explicit provider runtime status requests to avoid startup memory spikes', async () => { + const codexRequest = deferred(); + const opencodeRequest = deferred(); + const startedProviders: CliProviderId[] = []; + service.getProviderStatus.mockImplementation((providerId: CliProviderId) => { + startedProviders.push(providerId); + return providerId === 'codex' ? codexRequest.promise : opencodeRequest.promise; + }); + + const codexInvoke = ipcMain.invoke( + CLI_INSTALLER_GET_PROVIDER_STATUS, + 'codex' + ) as Promise>; + await vi.waitFor(() => expect(service.getProviderStatus).toHaveBeenCalledTimes(1)); + + const opencodeInvoke = ipcMain.invoke( + CLI_INSTALLER_GET_PROVIDER_STATUS, + 'opencode' + ) as Promise>; + await Promise.resolve(); + await Promise.resolve(); + + expect(startedProviders).toEqual(['codex']); + expect(service.getProviderStatus).toHaveBeenCalledTimes(1); + + codexRequest.resolve(provider({ providerId: 'codex', authenticated: true })); + await expect(codexInvoke).resolves.toMatchObject({ + success: true, + data: { providerId: 'codex' }, + }); + await vi.waitFor(() => expect(service.getProviderStatus).toHaveBeenCalledTimes(2)); + + expect(startedProviders).toEqual(['codex', 'opencode']); + opencodeRequest.resolve(provider({ providerId: 'opencode', authenticated: true })); + await expect(opencodeInvoke).resolves.toMatchObject({ + success: true, + data: { providerId: 'opencode' }, + }); + }); + it('does not reuse or recache a status request that was in flight before invalidation', async () => { const staleStatus = status([ provider({