diff --git a/src/main/git/worktree-shared-directories.test.ts b/src/main/git/worktree-shared-directories.test.ts index 7bd33987fba..21c6926c7b3 100644 --- a/src/main/git/worktree-shared-directories.test.ts +++ b/src/main/git/worktree-shared-directories.test.ts @@ -6,6 +6,8 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } import { clearConfiguredWorktreeSharedDirectoriesCacheForTests, getConfiguredWorktreeSharedDirectories, + getConfiguredWorktreeSharedDirectoriesCacheSizeForTests, + MAX_CONFIGURED_SHARED_DIRECTORIES_CACHE_ENTRIES, getWorktreeSharedLinkPaths, resolveWorktreeSharedDirectories } from './worktree-shared-directories' @@ -175,6 +177,15 @@ describe('resolveWorktreeSharedDirectories', () => { }) describe('getConfiguredWorktreeSharedDirectories', () => { + it('bounds cache growth when repository paths churn', () => { + for (let index = 0; index < MAX_CONFIGURED_SHARED_DIRECTORIES_CACHE_ENTRIES + 4; index += 1) { + getConfiguredWorktreeSharedDirectories(`/repo-${index}`) + } + + expect(getConfiguredWorktreeSharedDirectoriesCacheSizeForTests()).toBe( + MAX_CONFIGURED_SHARED_DIRECTORIES_CACHE_ENTRIES + ) + }) let repo: string beforeEach(() => { diff --git a/src/main/git/worktree-shared-directories.ts b/src/main/git/worktree-shared-directories.ts index 1dfe990a0e6..5d633d89c88 100644 --- a/src/main/git/worktree-shared-directories.ts +++ b/src/main/git/worktree-shared-directories.ts @@ -10,6 +10,7 @@ import { mapWithConcurrency } from '../../shared/map-with-concurrency' // duplicates disk; `orca.yaml` names the ones every worktree should share instead. const CONFIGURED_SHARED_DIRECTORIES_CACHE_TTL_MS = 30_000 +export const MAX_CONFIGURED_SHARED_DIRECTORIES_CACHE_ENTRIES = 512 // Why: resolving a worktree may list many generated directories; overlap // independent local probes without flooding the filesystem threadpool. const SHARED_DIRECTORY_STAT_CONCURRENCY = 8 @@ -33,6 +34,8 @@ export function getConfiguredWorktreeSharedDirectories(repoPath: string): readon const cached = configuredSharedDirectoriesByRepoPath.get(repoPath) const now = Date.now() if (cached && cached.expiresAt > now) { + configuredSharedDirectoriesByRepoPath.delete(repoPath) + configuredSharedDirectoriesByRepoPath.set(repoPath, cached) return cached.directories } const configured = loadHooks(repoPath)?.worktree?.sharedDirectories ?? [] @@ -40,6 +43,15 @@ export function getConfiguredWorktreeSharedDirectories(repoPath: string): readon directories: configured, expiresAt: now + CONFIGURED_SHARED_DIRECTORIES_CACHE_TTL_MS }) + while ( + configuredSharedDirectoriesByRepoPath.size > MAX_CONFIGURED_SHARED_DIRECTORIES_CACHE_ENTRIES + ) { + const oldest = configuredSharedDirectoriesByRepoPath.keys().next() + if (oldest.done || oldest.value === repoPath) { + break + } + configuredSharedDirectoriesByRepoPath.delete(oldest.value) + } return configured } @@ -48,6 +60,10 @@ export function clearConfiguredWorktreeSharedDirectoriesCacheForTests(): void { configuredSharedDirectoriesByRepoPath.clear() } +export function getConfiguredWorktreeSharedDirectoriesCacheSizeForTests(): number { + return configuredSharedDirectoriesByRepoPath.size +} + /** Every path Orca may have symlinked into a worktree: the per-user Worktree * Shared Paths setting plus the repo's `orca.yaml` shared directories. *