Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d09a7db01 |
+17
-7
@@ -451,9 +451,12 @@ export class ContentCache implements PhotoContent, ThumbnailsAPI {
|
|||||||
}
|
}
|
||||||
known.set(fileID, dest);
|
known.set(fileID, dest);
|
||||||
// A fresh original may have crossed the limit; make room by
|
// A fresh original may have crossed the limit; make room by
|
||||||
// evicting least-recently-used originals (never this one, just
|
// evicting least-recently-used originals. The file just written
|
||||||
// touched). Thumbnails are never bounded or evicted.
|
// is excluded from the candidates so an on-demand fetch that is
|
||||||
if (kind === "original") await this.enforceOriginalsLimit();
|
// itself over budget proceeds over-limit and returns a path that
|
||||||
|
// exists on disk. Thumbnails are never bounded or evicted.
|
||||||
|
if (kind === "original")
|
||||||
|
await this.enforceOriginalsLimit(fileID);
|
||||||
return { path: dest, bytes: size, cached: false };
|
return { path: dest, bytes: size, cached: false };
|
||||||
},
|
},
|
||||||
{ priority, key: fileID },
|
{ priority, key: fileID },
|
||||||
@@ -540,16 +543,23 @@ export class ContentCache implements PhotoContent, ThumbnailsAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evict least-recently-used originals until usage fits the limit. Pinned
|
// Evict least-recently-used originals until usage fits the limit. Pinned
|
||||||
// originals are skipped; when only pinned originals remain the cache stays
|
// originals are skipped, as is `justWrittenID` (the original this write just
|
||||||
// over-limit until the pinned set shrinks.
|
// stored, so an over-budget on-demand fetch never evicts the file it is
|
||||||
private enforceOriginalsLimit(): Promise<void> {
|
// about to return). When only such originals remain the cache stays
|
||||||
|
// over-limit until the protected set shrinks; the skipped file becomes
|
||||||
|
// eligible on a later write.
|
||||||
|
private enforceOriginalsLimit(justWrittenID: number): Promise<void> {
|
||||||
return this.serializeEnforce(async () => {
|
return this.serializeEnforce(async () => {
|
||||||
const { entries, used } = await this.measureOriginals();
|
const { entries, used } = await this.measureOriginals();
|
||||||
const limit = await this.originalsLimit(used);
|
const limit = await this.originalsLimit(used);
|
||||||
let remaining = used;
|
let remaining = used;
|
||||||
if (remaining > limit) {
|
if (remaining > limit) {
|
||||||
const evictable = entries
|
const evictable = entries
|
||||||
.filter((e) => !this.isPinned(e.fileID))
|
.filter(
|
||||||
|
(e) =>
|
||||||
|
e.fileID !== justWrittenID &&
|
||||||
|
!this.isPinned(e.fileID),
|
||||||
|
)
|
||||||
.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
||||||
for (const e of evictable) {
|
for (const e of evictable) {
|
||||||
if (remaining <= limit) break;
|
if (remaining <= limit) break;
|
||||||
|
|||||||
@@ -187,6 +187,33 @@ describe("originals eviction", () => {
|
|||||||
expect(status.usedBytes).toBeGreaterThan(status.limitBytes ?? 0);
|
expect(status.usedBytes).toBeGreaterThan(status.limitBytes ?? 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps a just-written original larger than the limit, evicting it only on a later write", async () => {
|
||||||
|
// A 5-byte cap cannot hold even one 10-byte original, but a fetch must
|
||||||
|
// never evict the file it just wrote and is about to return.
|
||||||
|
const { cache } = buildCache({
|
||||||
|
statfs: abundantFree,
|
||||||
|
cacheOriginalsMaxBytes: 5,
|
||||||
|
freeBelowBytes: 0,
|
||||||
|
});
|
||||||
|
await cache.open();
|
||||||
|
|
||||||
|
const result = await cache.original(1);
|
||||||
|
|
||||||
|
// The just-written original survives its own over-budget write and the
|
||||||
|
// returned path exists on disk.
|
||||||
|
expect(existsSync(result.path)).toBe(true);
|
||||||
|
expect(existsSync(originalPath(1))).toBe(true);
|
||||||
|
expect(cache.originalsStatus().usedBytes).toBe(10);
|
||||||
|
setMtime(1, 1000);
|
||||||
|
|
||||||
|
// A later write finds file 1 eligible and evicts it to make room, while
|
||||||
|
// the newly written file 2 is itself kept over-limit.
|
||||||
|
await cache.original(2);
|
||||||
|
expect(existsSync(originalPath(1))).toBe(false);
|
||||||
|
expect(existsSync(originalPath(2))).toBe(true);
|
||||||
|
expect(cache.originalsStatus().usedBytes).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
it("adapts the limit down as the disk fills and up as space returns", async () => {
|
it("adapts the limit down as the disk fills and up as space returns", async () => {
|
||||||
// The configured max is generous; free space drives the limit. Eviction
|
// The configured max is generous; free space drives the limit. Eviction
|
||||||
// only kicks in once free space falls below the protected reserve.
|
// only kicks in once free space falls below the protected reserve.
|
||||||
|
|||||||
Reference in New Issue
Block a user