From 35a9b0563774de29a1897fd92daa6b659b0bf70f Mon Sep 17 00:00:00 2001 From: 777genius Date: Fri, 29 May 2026 13:06:26 +0300 Subject: [PATCH] perf: cache persisted spawn status reads --- .../services/team/TeamProvisioningService.ts | 30 +++++++++++++++++-- .../team/TeamProvisioningService.test.ts | 24 +++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index c26a6b74..fc050119 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -3306,6 +3306,7 @@ export class TeamProvisioningService { private static readonly RUNTIME_PIDUSAGE_SINGLE_TIMEOUT_MS = 750; private static readonly RUNTIME_PIDUSAGE_FALLBACK_CONCURRENCY = 16; private static readonly MEMBER_SPAWN_STATUS_SNAPSHOT_CACHE_TTL_MS = 500; + private static readonly PERSISTED_MEMBER_SPAWN_STATUS_SNAPSHOT_CACHE_TTL_MS = 2_000; private static readonly LAUNCH_STATE_NOOP_REFRESH_MS = 15_000; private static readonly RETAINED_PROVISIONING_PROGRESS_TTL_MS = 5 * 60_000; private static readonly OPENCODE_RUNTIME_DELIVERY_ADVISORY_EVENT_TTL_MS = 24 * 60 * 60_000; @@ -3445,7 +3446,7 @@ export class TeamProvisioningService { { expiresAtMs: number; generation: number; - runId: string; + runId: string | null; snapshot: MemberSpawnStatusesSnapshot; } >(); @@ -13821,6 +13822,17 @@ export class TeamProvisioningService { source?: 'live' | 'persisted' | 'merged'; }> { const readPersistedStatuses = async (resolvedRunId: string | null) => { + const generationAtStart = this.getMemberSpawnStatusesCacheGeneration(teamName); + const cached = this.memberSpawnStatusesSnapshotCache.get(teamName); + if ( + cached && + cached.expiresAtMs > Date.now() && + cached.runId === resolvedRunId && + cached.generation === generationAtStart + ) { + return this.cloneMemberSpawnStatusesSnapshot(cached.snapshot); + } + const repairSnapshot = await this.readTaskActivityRepairLaunchSnapshot(teamName); this.repairStaleTaskActivityIntervalsOnce(teamName, repairSnapshot); const { snapshot, statuses } = await this.reconcilePersistedLaunchState(teamName); @@ -13842,7 +13854,7 @@ export class TeamProvisioningService { const summary = expectedMembers ? summarizeMemberSpawnStatusRecord(expectedMembers, nextStatuses) : undefined; - return { + const persistedSnapshot = { statuses: nextStatuses, runId: resolvedRunId, teamLaunchState: summary @@ -13854,6 +13866,20 @@ export class TeamProvisioningService { summary: summary ?? snapshot?.summary, source: 'persisted' as const, }; + if ( + this.getMemberSpawnStatusesCacheGeneration(teamName) === generationAtStart && + this.getTrackedRunId(teamName) === resolvedRunId + ) { + this.memberSpawnStatusesSnapshotCache.set(teamName, { + expiresAtMs: + Date.now() + + TeamProvisioningService.PERSISTED_MEMBER_SPAWN_STATUS_SNAPSHOT_CACHE_TTL_MS, + generation: generationAtStart, + runId: resolvedRunId, + snapshot: this.cloneMemberSpawnStatusesSnapshot(persistedSnapshot), + }); + } + return persistedSnapshot; }; const runId = this.getTrackedRunId(teamName); diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index 8d03a06c..af2565a0 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -620,6 +620,12 @@ type TeamProvisioningServicePrivateHarness = { applyProcessBootstrapTransportOverlay: ( input: Record ) => Record; + reconcilePersistedLaunchState: ( + teamName: string + ) => Promise<{ + snapshot: null; + statuses: Record; + }>; readProcessUsageStatsByPid: ( pids: readonly number[] ) => Promise>; @@ -21083,6 +21089,24 @@ describe('TeamProvisioningService', () => { }); }); + it('caches persisted member spawn statuses between close polling reads', async () => { + const teamName = 'zz-unit-persisted-status-cache'; + const svc = new TeamProvisioningService(); + const harness = privateHarness(svc); + harness.reconcilePersistedLaunchState = vi.fn(async () => ({ + snapshot: null, + statuses: {}, + })); + harness.attachLiveRuntimeMetadataToStatuses = vi.fn(async (_teamName, statuses) => statuses); + + const first = await svc.getMemberSpawnStatuses(teamName); + const second = await svc.getMemberSpawnStatuses(teamName); + + expect(first).toEqual(second); + expect(harness.reconcilePersistedLaunchState).toHaveBeenCalledTimes(1); + expect(harness.attachLiveRuntimeMetadataToStatuses).toHaveBeenCalledTimes(1); + }); + it('does not heal cleanup-finalized launch failures from stale bootstrap-state confirmation', async () => { allowConsoleLogs(); const teamName = 'zz-unit-cleanup-finalized-stale-bootstrap-ignored';