| author | |
| committer | |
| log | bfe40e6d2574447b0651bdb41337db743ddbe12c |
| tree | 4d8460b216d9076f68bbd17a53be23b8106980b7 |
| parent | 327a29ef56e215c69a5db5d0b6f8df89f04f5d5b |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>2 files changed, 8 insertions(+), 3 deletions(-)
apps/server/src/pullRequest/PullRequestReadCache.test.ts+5-2| ... | @@ -44,18 +44,21 @@ it.layer(NodeServices.layer)("PR filesystem cache", (it) => { | ... | @@ -44,18 +44,21 @@ it.layer(NodeServices.layer)("PR filesystem cache", (it) => { |
| 44 | ); | 44 | ); |
| 45 | const unrelated = "unrelated.json"; | 45 | const unrelated = "unrelated.json"; |
| 46 | yield* fs.writeFileString(`${directory}/${unrelated}`, "{}"); | 46 | yield* fs.writeFileString(`${directory}/${unrelated}`, "{}"); |
| 47 | // Starts with the store id but is not an entry file. | ||
| 48 | const prefixed = "pr-v2-backup.json"; | ||
| 49 | yield* fs.writeFileString(`${directory}/${prefixed}`, "{}"); | ||
| 47 | const justExpired = Duration.sum( | 50 | const justExpired = Duration.sum( |
| 48 | PullRequestReadCache.ENTRY_FILE_MAX_AGE, | 51 | PullRequestReadCache.ENTRY_FILE_MAX_AGE, |
| 49 | Duration.seconds(1), | 52 | Duration.seconds(1), |
| 50 | ); | 53 | ); |
| 51 | yield* ageFiles(directory, [stale!, revisions!, unrelated], justExpired); | 54 | yield* ageFiles(directory, [stale!, revisions!, unrelated, prefixed], justExpired); |
| 52 | yield* ageFiles(directory, [fresh!], PullRequestReadCache.ENTRY_FILE_MAX_AGE); | 55 | yield* ageFiles(directory, [fresh!], PullRequestReadCache.ENTRY_FILE_MAX_AGE); |
| 53 | 56 | ||
| 54 | yield* PullRequestReadCache.pruneExpiredEntryFiles(directory); | 57 | yield* PullRequestReadCache.pruneExpiredEntryFiles(directory); |
| 55 | 58 | ||
| 56 | assert.deepStrictEqual( | 59 | assert.deepStrictEqual( |
| 57 | (yield* fs.readDirectory(directory)).toSorted(), | 60 | (yield* fs.readDirectory(directory)).toSorted(), |
| 58 | [fresh!, revisions!, unrelated].toSorted(), | 61 | [fresh!, revisions!, unrelated, prefixed].toSorted(), |
| 59 | ); | 62 | ); |
| 60 | }), | 63 | }), |
| 61 | ); | 64 | ); |
apps/server/src/pullRequest/PullRequestReadCache.ts+3-1| ... | @@ -27,6 +27,8 @@ const CONCURRENT_READS = 512; | ... | @@ -27,6 +27,8 @@ const CONCURRENT_READS = 512; |
| 27 | // Persistence prefixes every entry key with the store id, so each entry file | 27 | // Persistence prefixes every entry key with the store id, so each entry file |
| 28 | // name starts with it. The scope `revisions` file does not. | 28 | // name starts with it. The scope `revisions` file does not. |
| 29 | const STORE_ID = "pr-v2"; | 29 | const STORE_ID = "pr-v2"; |
| 30 | // An entry file is the store id followed by the SHA-256 hex digest of its key. | ||
| 31 | const ENTRY_FILE_NAME = new RegExp(`^${STORE_ID}[0-9a-f]{64}$`); | ||
| 30 | /** | 32 | /** |
| 31 | * Entries expire a minute after they are written, and a write sets the file's | 33 | * Entries expire a minute after they are written, and a write sets the file's |
| 32 | * mtime. A file untouched for a day is long expired; the day only leaves slack | 34 | * 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 | ... | @@ -186,7 +188,7 @@ export const pruneExpiredEntryFiles = Effect.fn("PullRequestReadCache.pruneExpir |
| 186 | const path = yield* Path.Path; | 188 | const path = yield* Path.Path; |
| 187 | const cutoff = (yield* Clock.currentTimeMillis) - Duration.toMillis(ENTRY_FILE_MAX_AGE); | 189 | const cutoff = (yield* Clock.currentTimeMillis) - Duration.toMillis(ENTRY_FILE_MAX_AGE); |
| 188 | const entries = (yield* fileSystem.readDirectory(directory)).filter((name) => | 190 | const entries = (yield* fileSystem.readDirectory(directory)).filter((name) => |
| 189 | name.startsWith(STORE_ID), | 191 | ENTRY_FILE_NAME.test(name), |
| 190 | ); | 192 | ); |
| 191 | // One file at a time, and `partition` visits every file, so one locked file | 193 | // One file at a time, and `partition` visits every file, so one locked file |
| 192 | // does not stop the sweep. | 194 | // does not stop the sweep. |