fix(startup): serialize provider runtime checks
This commit is contained in:
parent
ec5bb5d5ea
commit
b88ca42fe3
2 changed files with 53 additions and 3 deletions
|
|
@ -38,6 +38,7 @@ const logger = createLogger('IPC:cliInstaller');
|
|||
let service: CliInstallerService;
|
||||
const statusInFlight = new Map<CliInstallerProviderStatusMode, Promise<CliInstallationStatus>>();
|
||||
const providerStatusInFlight = new Map<CliProviderId, Promise<CliProviderStatus | null>>();
|
||||
let providerRuntimeRequestTail: Promise<void> = Promise.resolve();
|
||||
const cachedStatus = new Map<
|
||||
CliInstallerProviderStatusMode,
|
||||
{ value: CliInstallationStatus; at: number }
|
||||
|
|
@ -110,11 +111,21 @@ function canUseStatusForCacheKey(
|
|||
);
|
||||
}
|
||||
|
||||
function runProviderRuntimeRequest<T>(operation: () => Promise<T>): Promise<T> {
|
||||
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<IpcResult<CliProviderStatus | null>> {
|
||||
try {
|
||||
const generation = statusCacheGeneration;
|
||||
const status = await service.verifyProviderModels(providerId);
|
||||
const status = await runProviderRuntimeRequest(() => service.verifyProviderModels(providerId));
|
||||
if (generation === statusCacheGeneration) {
|
||||
patchCachedProviderStatus(status);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CliProviderStatus>();
|
||||
const opencodeRequest = deferred<CliProviderStatus>();
|
||||
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<IpcResult<CliProviderStatus | null>>;
|
||||
await vi.waitFor(() => expect(service.getProviderStatus).toHaveBeenCalledTimes(1));
|
||||
|
||||
const opencodeInvoke = ipcMain.invoke(
|
||||
CLI_INSTALLER_GET_PROVIDER_STATUS,
|
||||
'opencode'
|
||||
) as Promise<IpcResult<CliProviderStatus | null>>;
|
||||
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({
|
||||
|
|
|
|||
Loading…
Reference in a new issue