diff --git a/apps/server/src/pullRequest/PullRequestReadCache.test.ts b/apps/server/src/pullRequest/PullRequestReadCache.test.ts index a6a14dbae9cabd7d6b8d1ddff5e98058d9edd3bc..f1b543d1eddf334b713afd0cca4f417594a84e31 100644 --- a/apps/server/src/pullRequest/PullRequestReadCache.test.ts +++ b/apps/server/src/pullRequest/PullRequestReadCache.test.ts @@ -44,18 +44,21 @@ it.layer(NodeServices.layer)("PR filesystem cache", (it) => { ); const unrelated = "unrelated.json"; yield* fs.writeFileString(`${directory}/${unrelated}`, "{}"); + // Starts with the store id but is not an entry file. + const prefixed = "pr-v2-backup.json"; + yield* fs.writeFileString(`${directory}/${prefixed}`, "{}"); const justExpired = Duration.sum( PullRequestReadCache.ENTRY_FILE_MAX_AGE, Duration.seconds(1), ); - yield* ageFiles(directory, [stale!, revisions!, unrelated], justExpired); + yield* ageFiles(directory, [stale!, revisions!, unrelated, prefixed], justExpired); yield* ageFiles(directory, [fresh!], PullRequestReadCache.ENTRY_FILE_MAX_AGE); yield* PullRequestReadCache.pruneExpiredEntryFiles(directory); assert.deepStrictEqual( (yield* fs.readDirectory(directory)).toSorted(), - [fresh!, revisions!, unrelated].toSorted(), + [fresh!, revisions!, unrelated, prefixed].toSorted(), ); }), ); diff --git a/apps/server/src/pullRequest/PullRequestReadCache.ts b/apps/server/src/pullRequest/PullRequestReadCache.ts index a691898769be43101636062e00e5212f66da03de..a749a14031fd36c8b675809af9264be158d38bb3 100644 --- a/apps/server/src/pullRequest/PullRequestReadCache.ts +++ b/apps/server/src/pullRequest/PullRequestReadCache.ts @@ -27,6 +27,8 @@ const CONCURRENT_READS = 512; // Persistence prefixes every entry key with the store id, so each entry file // name starts with it. The scope `revisions` file does not. const STORE_ID = "pr-v2"; +// An entry file is the store id followed by the SHA-256 hex digest of its key. +const ENTRY_FILE_NAME = new RegExp(`^${STORE_ID}[0-9a-f]{64}$`); /** * Entries expire a minute after they are written, and a write sets the file's * mtime. A file untouched for a day is long expired; the day only leaves slack @@ -186,7 +188,7 @@ export const pruneExpiredEntryFiles = Effect.fn("PullRequestReadCache.pruneExpir const path = yield* Path.Path; const cutoff = (yield* Clock.currentTimeMillis) - Duration.toMillis(ENTRY_FILE_MAX_AGE); const entries = (yield* fileSystem.readDirectory(directory)).filter((name) => - name.startsWith(STORE_ID), + ENTRY_FILE_NAME.test(name), ); // One file at a time, and `partition` visits every file, so one locked file // does not stop the sweep.