mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 16:02:37 +00:00
fix: bound runtime fetch caches (#4068)
Cap runtime remote-fetch cache maps while preserving freshness and in-flight coalescing behavior.
This commit is contained in:
@@ -118,6 +118,24 @@ describe('OrcaRuntimeService.fetchRemoteWithCache', () => {
|
||||
expect(fetchCallCount()).toBe(1)
|
||||
})
|
||||
|
||||
it('bounds process-lifetime fetch cache maps for churned repo paths', async () => {
|
||||
mockFetchResults(Array.from({ length: 520 }, () => ({ stdout: '', stderr: '' })))
|
||||
const runtime = new OrcaRuntimeService(null)
|
||||
const caches = runtime as unknown as {
|
||||
canonicalFetchKeyCache: Map<string, string>
|
||||
fetchLastCompletedAt: Map<string, number>
|
||||
}
|
||||
|
||||
for (let i = 0; i < 520; i += 1) {
|
||||
await runtime.fetchRemoteWithCache(`/repo/cache-${i}`, 'origin')
|
||||
}
|
||||
|
||||
expect(caches.canonicalFetchKeyCache.size).toBeLessThanOrEqual(512)
|
||||
expect(caches.fetchLastCompletedAt.size).toBeLessThanOrEqual(512)
|
||||
expect(caches.canonicalFetchKeyCache.has('/repo/cache-0::origin')).toBe(false)
|
||||
expect(caches.fetchLastCompletedAt.has('/repo/cache-0::origin')).toBe(false)
|
||||
})
|
||||
|
||||
it('resolves remote-tracking bases with longest configured remote matching', async () => {
|
||||
gitExecFileAsyncMock.mockResolvedValue({ stdout: 'foo\nfoo/bar\norigin\n', stderr: '' })
|
||||
const runtime = new OrcaRuntimeService(null)
|
||||
|
||||
@@ -8270,6 +8270,7 @@ export class OrcaRuntimeService {
|
||||
const cacheKey = `${repoPath}::${remote}`
|
||||
const cached = this.canonicalFetchKeyCache.get(cacheKey)
|
||||
if (cached !== undefined) {
|
||||
setBoundedMapEntry(this.canonicalFetchKeyCache, cacheKey, cached, REMOTE_FETCH_CACHE_MAX)
|
||||
return cached
|
||||
}
|
||||
let resolved = cacheKey
|
||||
@@ -8286,7 +8287,7 @@ export class OrcaRuntimeService {
|
||||
// Fall through to the caller-provided path. The fetch still runs from
|
||||
// repoPath; this key only controls cache sharing.
|
||||
}
|
||||
this.canonicalFetchKeyCache.set(cacheKey, resolved)
|
||||
setBoundedMapEntry(this.canonicalFetchKeyCache, cacheKey, resolved, REMOTE_FETCH_CACHE_MAX)
|
||||
return resolved
|
||||
}
|
||||
|
||||
@@ -8308,11 +8309,15 @@ export class OrcaRuntimeService {
|
||||
async getOrStartRemoteFetch(repoPath: string, remote: string): Promise<RemoteFetchResult> {
|
||||
const key = await this.getCanonicalFetchKey(repoPath, remote)
|
||||
const lastAt = this.fetchLastCompletedAt.get(key)
|
||||
if (lastAt !== undefined && Date.now() - lastAt < FETCH_FRESHNESS_MS) {
|
||||
// Why: freshness window hit — skip the fetch entirely. Do NOT reuse any
|
||||
// in-flight promise here; the timestamp is only written on success, so
|
||||
// hitting this branch means a previous fetch did succeed recently.
|
||||
return { ok: true }
|
||||
if (lastAt !== undefined) {
|
||||
if (Date.now() - lastAt < FETCH_FRESHNESS_MS) {
|
||||
// Why: freshness window hit — skip the fetch entirely. Do NOT reuse any
|
||||
// in-flight promise here; the timestamp is only written on success, so
|
||||
// hitting this branch means a previous fetch did succeed recently.
|
||||
setBoundedMapEntry(this.fetchLastCompletedAt, key, lastAt, REMOTE_FETCH_CACHE_MAX)
|
||||
return { ok: true }
|
||||
}
|
||||
this.fetchLastCompletedAt.delete(key)
|
||||
}
|
||||
|
||||
const existing = this.fetchInflight.get(key)
|
||||
@@ -8327,7 +8332,7 @@ export class OrcaRuntimeService {
|
||||
.then((): RemoteFetchResult => {
|
||||
// Why (§3.3 Lifecycle): timestamp on success ONLY. Writing on rejection
|
||||
// would make the freshness cache lie about the last known remote state.
|
||||
this.fetchLastCompletedAt.set(key, Date.now())
|
||||
setBoundedMapEntry(this.fetchLastCompletedAt, key, Date.now(), REMOTE_FETCH_CACHE_MAX)
|
||||
return { ok: true }
|
||||
})
|
||||
.catch((err): RemoteFetchResult => {
|
||||
@@ -12773,8 +12778,23 @@ const PTY_CONTROLLER_LIST_TIMEOUT_MS = 3000
|
||||
// clicks and successive coordinator dispatches feel snappy, while still being
|
||||
// short enough that a genuinely-changed remote is observed on the next action.
|
||||
const FETCH_FRESHNESS_MS = 30_000
|
||||
const REMOTE_FETCH_CACHE_MAX = 512
|
||||
const DRIFT_PROBE_SUBJECT_LIMIT = 5
|
||||
|
||||
function setBoundedMapEntry<K, V>(map: Map<K, V>, key: K, value: V, maxEntries: number): void {
|
||||
if (map.has(key)) {
|
||||
map.delete(key)
|
||||
}
|
||||
map.set(key, value)
|
||||
while (map.size > maxEntries) {
|
||||
const oldest = map.keys().next()
|
||||
if (oldest.done) {
|
||||
return
|
||||
}
|
||||
map.delete(oldest.value)
|
||||
}
|
||||
}
|
||||
|
||||
function getExplicitWorktreeIdSelector(selector: string | undefined): string | null {
|
||||
if (!selector?.startsWith('id:')) {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user