diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index b769e522b5..6a19766ca0 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -290,7 +290,7 @@ import { localRouteFallbackProfiles, undialedSshRouteSeeds } from './plugin-profile-routes' -import { selectPoolEvictions } from './pool-eviction' +import { evictPoolEntries } from './pool-eviction' import { clampPoolLimits, parsePoolLimits, POOL_LIMITS_DEFAULTS } from './pool-limits' import { isBackgroundSlotWaitTimeout, @@ -1623,7 +1623,7 @@ function setPoolLimits(raw) { poolLimits = clampPoolLimits(raw) persistPoolLimits(poolLimits) localBackendSpawnCoordinator.setLimit(poolLimits.maxBackends) - evictLruPoolBackends(poolMaxBackends()) + void evictLruPoolBackends(poolMaxBackends()) startPoolIdleReaper() return { ...poolLimits } @@ -11430,7 +11430,10 @@ async function ensureBackend(profile, opts: { passive?: boolean; spawnPriority?: } assertNotPassiveSpawn(passive, key) - evictLruPoolBackends(poolMaxBackends() - 1) + // The hard slot is released only after the evicted child exits. Wait for + // that teardown before entering the spawn queue; otherwise a successful + // LRU choice still leaves this wake racing the old child for 30 seconds. + await evictLruPoolBackends(poolMaxBackends() - 1) const entry = { process: null, @@ -11609,7 +11612,7 @@ async function ensureRegistryBackend( } assertNotPassiveSpawn(passive, localRoute.poolKey) - evictLruPoolBackends(poolMaxBackends() - 1) + await evictLruPoolBackends(poolMaxBackends() - 1) const localEntry = { process: null, @@ -11683,7 +11686,7 @@ async function ensureRegistryBackend( } assertNotPassiveSpawn(passive, key) - evictLruPoolBackends(poolMaxBackends() - 1) + await evictLruPoolBackends(poolMaxBackends() - 1) const entry = { process: null, @@ -12334,13 +12337,17 @@ function touchPoolBackend(profile) { // across N registered remote connections LRU-evict a REAL local backend that // was merely idle past the keepalive window. Descriptors are still reclaimed // by the idle reaper. -function evictLruPoolBackends(keep) { - const evictions = selectPoolEvictions(backendPool.entries(), Math.max(0, keep), Date.now(), POOL_KEEPALIVE_FRESH_MS) - - for (const profile of evictions) { - rememberLog(`Evicting idle profile backend "${profile}" (LRU cap ${poolMaxBackends()})`) - stopPoolBackend(profile) - } +async function evictLruPoolBackends(keep) { + return evictPoolEntries( + backendPool.entries(), + Math.max(0, keep), + Date.now(), + POOL_KEEPALIVE_FRESH_MS, + async profile => { + rememberLog(`Evicting idle profile backend "${profile}" (LRU cap ${poolMaxBackends()})`) + await stopPoolBackend(profile) + } + ) } function startPoolIdleReaper() {