mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
test(skills): cover repeated runtime cache eviction
This commit is contained in:
@@ -271,8 +271,7 @@ describe('installed agent skill discovery lifecycle', () => {
|
||||
})
|
||||
|
||||
it('evicts only the retired environments, leaving other entries warm', async () => {
|
||||
// Why: #11429 — removal must not degrade into a flush, or every removal
|
||||
// re-fires each mounted consumer's scan (the storm #7670 fixed).
|
||||
// Why: a flush would re-fire every mounted consumer's scan.
|
||||
const discover = discoverSkillsForRuntimeTarget
|
||||
discover.mockResolvedValueOnce(result(1))
|
||||
discover.mockResolvedValueOnce(result(2))
|
||||
@@ -283,7 +282,6 @@ describe('installed agent skill discovery lifecycle', () => {
|
||||
|
||||
evictSkillDiscoveryForRuntimeEnvironments(['env-a'])
|
||||
|
||||
// env-b and local are still served from cache; only env-a rescans.
|
||||
await expect(discoverInstalledAgentSkills(false, undefined, remote('env-b'))).resolves.toEqual(
|
||||
result(2)
|
||||
)
|
||||
@@ -297,9 +295,7 @@ describe('installed agent skill discovery lifecycle', () => {
|
||||
})
|
||||
|
||||
it('does not let a scan in flight at eviction time repopulate the retired entry', async () => {
|
||||
// Why: an environment removed mid-scan (ephemeral VM teardown) must not
|
||||
// resurrect its entry when the scan lands — a re-pair reusing the id would
|
||||
// then be served the retired peer's skill list.
|
||||
// Why: VM teardown can race a scan already in flight.
|
||||
const staleScan = deferred<SkillDiscoveryResult>()
|
||||
const discover = discoverSkillsForRuntimeTarget
|
||||
discover.mockReturnValueOnce(staleScan.promise)
|
||||
@@ -309,7 +305,6 @@ describe('installed agent skill discovery lifecycle', () => {
|
||||
staleScan.resolve(result(1))
|
||||
await expect(staleRequest).resolves.toEqual(result(1))
|
||||
|
||||
// The next read must rescan rather than serve the retired result.
|
||||
discover.mockResolvedValueOnce(result(2))
|
||||
await expect(discoverInstalledAgentSkills(false, undefined, remote('env-a'))).resolves.toEqual(
|
||||
result(2)
|
||||
|
||||
@@ -39,14 +39,7 @@ export function invalidateInstalledAgentSkillDiscovery(): void {
|
||||
pendingDiscoverySatisfiesForcedRefreshByTarget.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict the cached and in-flight scans owned by runtime environments that left
|
||||
* the saved list (removed, or re-paired under the same id). Keyed, not a flush:
|
||||
* the local/WSL entries and surviving remotes stay warm, so removal cannot
|
||||
* re-fire every mounted consumer's scan. Called by the runtime-status slice —
|
||||
* this module must stay store-free (#6887), so the store subscription lives on
|
||||
* the consuming side.
|
||||
*/
|
||||
// Keyed eviction keeps surviving targets warm and this discovery module store-free.
|
||||
export function evictSkillDiscoveryForRuntimeEnvironments(environmentIds: readonly string[]): void {
|
||||
for (const environmentId of environmentIds) {
|
||||
const key = getRuntimeScopedSkillDiscoveryKey({ kind: 'environment', environmentId }, undefined)
|
||||
@@ -124,8 +117,7 @@ function startInstalledAgentSkillDiscovery(
|
||||
const normalizedTarget = normalizeSkillDiscoveryTarget(target)
|
||||
const discovery = discoverSkillsForRuntimeTarget(runtimeTarget, normalizedTarget)
|
||||
.then((result) => {
|
||||
// Why: only the still-registered scan may populate — a scan whose runtime
|
||||
// environment was evicted mid-flight must not resurrect the retired entry.
|
||||
// Why: an evicted in-flight scan must not resurrect a retired entry.
|
||||
if (generation === discoveryGeneration && pendingDiscoveryByTarget.get(key) === discovery) {
|
||||
writeInstalledAgentSkillDiscoveryCache(key, result)
|
||||
}
|
||||
|
||||
@@ -159,9 +159,7 @@ export const createRuntimeStatusSlice: StateCreator<AppState, [], [], RuntimeSta
|
||||
const retiredEnvironmentIds = [...new Set([...removedIds, ...replacedEnvironmentIds])]
|
||||
if (retiredEnvironmentIds.length > 0) {
|
||||
get().purgeStaleRuntimeHostState?.(retiredEnvironmentIds)
|
||||
// Why: a retired environment's cached skill scan would otherwise be served
|
||||
// verbatim if its id is re-paired, and ephemeral VMs mint a fresh id per
|
||||
// start, leaking one retained entry per start until LRU pressure (#11429).
|
||||
// Why: ephemeral runtimes otherwise leak one cached scan per retired id.
|
||||
evictSkillDiscoveryForRuntimeEnvironments(retiredEnvironmentIds)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
/**
|
||||
* Staleness + memory-leak regression (#11429): runtime-scoped skill discovery
|
||||
* cache entries must be evicted when their runtime environment leaves the
|
||||
* saved list.
|
||||
*
|
||||
* Remote scans cache under `runtime:<environmentId>` (#6887) in the bounded
|
||||
* LRU (#7670), but nothing evicted them on environment removal. Ephemeral VMs
|
||||
* mint a fresh environment id per start, so every start left a permanently
|
||||
* retained entry until LRU pressure, and re-pairing an id served the removed
|
||||
* peer's skill list. `setRuntimeEnvironments` now evicts retired ids —
|
||||
* removed and same-id re-paired alike.
|
||||
*/
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { create } from 'zustand'
|
||||
import type { PublicKnownRuntimeEnvironment } from '../../../../shared/runtime-environments'
|
||||
@@ -19,6 +7,7 @@ import {
|
||||
resetSkillDiscoveryCacheForTests
|
||||
} from '@/hooks/installed-agent-skill-discovery'
|
||||
import {
|
||||
getInstalledAgentSkillDiscoveryCacheSizeForTests,
|
||||
hasInstalledAgentSkillDiscoveryCacheEntryForTests,
|
||||
writeInstalledAgentSkillDiscoveryCache
|
||||
} from '@/hooks/installed-agent-skill-discovery-cache'
|
||||
@@ -57,7 +46,6 @@ describe('skill discovery cache evicted on environment removal (#11429)', () =>
|
||||
store.getState().setRuntimeEnvironments([env('env-keep')])
|
||||
|
||||
expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests(runtimeKey('env-drop'))).toBe(false)
|
||||
// Surviving remote and local entries stay warm — eviction is keyed, not a flush.
|
||||
expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests(runtimeKey('env-keep'))).toBe(true)
|
||||
expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests('host')).toBe(true)
|
||||
})
|
||||
@@ -71,4 +59,26 @@ describe('skill discovery cache evicted on environment removal (#11429)', () =>
|
||||
|
||||
expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests(runtimeKey('env-a'))).toBe(false)
|
||||
})
|
||||
|
||||
it('returns to baseline across repeated ephemeral runtime cycles', () => {
|
||||
const store = createSliceStore()
|
||||
const keep = env('env-keep')
|
||||
store.getState().setRuntimeEnvironments([keep])
|
||||
writeInstalledAgentSkillDiscoveryCache(runtimeKey(keep.id), discovery(1))
|
||||
writeInstalledAgentSkillDiscoveryCache('host', discovery(2))
|
||||
const baseline = getInstalledAgentSkillDiscoveryCacheSizeForTests()
|
||||
|
||||
for (let cycle = 0; cycle < 100; cycle += 1) {
|
||||
const ephemeral = env(`orca-${cycle}`)
|
||||
store.getState().setRuntimeEnvironments([keep, ephemeral])
|
||||
writeInstalledAgentSkillDiscoveryCache(runtimeKey(ephemeral.id), discovery(cycle + 3))
|
||||
expect(getInstalledAgentSkillDiscoveryCacheSizeForTests()).toBe(baseline + 1)
|
||||
|
||||
store.getState().setRuntimeEnvironments([keep])
|
||||
expect(getInstalledAgentSkillDiscoveryCacheSizeForTests()).toBe(baseline)
|
||||
}
|
||||
|
||||
expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests(runtimeKey(keep.id))).toBe(true)
|
||||
expect(hasInstalledAgentSkillDiscoveryCacheEntryForTests('host')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user