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;
|
let service: CliInstallerService;
|
||||||
const statusInFlight = new Map<CliInstallerProviderStatusMode, Promise<CliInstallationStatus>>();
|
const statusInFlight = new Map<CliInstallerProviderStatusMode, Promise<CliInstallationStatus>>();
|
||||||
const providerStatusInFlight = new Map<CliProviderId, Promise<CliProviderStatus | null>>();
|
const providerStatusInFlight = new Map<CliProviderId, Promise<CliProviderStatus | null>>();
|
||||||
|
let providerRuntimeRequestTail: Promise<void> = Promise.resolve();
|
||||||
const cachedStatus = new Map<
|
const cachedStatus = new Map<
|
||||||
CliInstallerProviderStatusMode,
|
CliInstallerProviderStatusMode,
|
||||||
{ value: CliInstallationStatus; at: number }
|
{ 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.
|
* Initializes CLI installer handlers with the service instance.
|
||||||
*/
|
*/
|
||||||
export function initializeCliInstallerHandlers(installerService: CliInstallerService): void {
|
export function initializeCliInstallerHandlers(installerService: CliInstallerService): void {
|
||||||
service = installerService;
|
service = installerService;
|
||||||
|
providerRuntimeRequestTail = Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -255,8 +266,7 @@ async function handleGetProviderStatus(
|
||||||
}
|
}
|
||||||
|
|
||||||
const generation = statusCacheGeneration;
|
const generation = statusCacheGeneration;
|
||||||
const request = service
|
const request = runProviderRuntimeRequest(() => service.getProviderStatus(providerId))
|
||||||
.getProviderStatus(providerId)
|
|
||||||
.then((status) => {
|
.then((status) => {
|
||||||
if (generation === statusCacheGeneration) {
|
if (generation === statusCacheGeneration) {
|
||||||
patchCachedProviderStatus(status);
|
patchCachedProviderStatus(status);
|
||||||
|
|
@ -296,7 +306,7 @@ async function handleVerifyProviderModels(
|
||||||
): Promise<IpcResult<CliProviderStatus | null>> {
|
): Promise<IpcResult<CliProviderStatus | null>> {
|
||||||
try {
|
try {
|
||||||
const generation = statusCacheGeneration;
|
const generation = statusCacheGeneration;
|
||||||
const status = await service.verifyProviderModels(providerId);
|
const status = await runProviderRuntimeRequest(() => service.verifyProviderModels(providerId));
|
||||||
if (generation === statusCacheGeneration) {
|
if (generation === statusCacheGeneration) {
|
||||||
patchCachedProviderStatus(status);
|
patchCachedProviderStatus(status);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,46 @@ describe('cliInstaller IPC handlers', () => {
|
||||||
expect(service.invalidateStatusCache).toHaveBeenCalledTimes(1);
|
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 () => {
|
it('does not reuse or recache a status request that was in flight before invalidation', async () => {
|
||||||
const staleStatus = status([
|
const staleStatus = status([
|
||||||
provider({
|
provider({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue