diigo-cache: pace diigo calls only; cache hits don't wait
page() ran the 500 ms pacing gate before the cache check, so every hit waited too: a fill rerun skipping 166 cached pages took ~80 s and looked like a start from scratch. The gate now sits after the hit check (re-checking storage once through it, in case a request ahead had just etched the page). Regression test: the fixed-clock test cache records its sleeps; a miss sleeps once, three hits add none (failed before: 4 sleeps). Generated-by: Claude Opus 5.5
This commit is contained in:
1 parent
f5d57f73d3
commit
fd16c7db53
3 files changed
+43
-8
No files matched your search
@@ -285,8 +285,15 @@ abstract class PagedCache extends DurableObject<CacheEnv> {
|
||||
if (!init) throw new Error("not wired: this cache id was never set up");
|
||||
this.ctx.storage.sql.exec(SCHEMA_V1);
|
||||
|
||||
// one diigo call path at a time, min spacing MS_PER_CALL (webapp's
|
||||
// Gate pattern; per activation)
|
||||
const hit = await this.#pageFromStorage(pageNum, count);
|
||||
if (hit) {
|
||||
console.log({ event: "cache", kind: this.kind, page: pageNum, outcome: "hit", items: hit.items.length });
|
||||
return hit;
|
||||
}
|
||||
|
||||
// a miss calls diigo: one call path at a time, min spacing
|
||||
// MS_PER_CALL (webapp's Gate pattern; per activation). Hits above
|
||||
// never wait.
|
||||
let before;
|
||||
do {
|
||||
before = this.#step;
|
||||
@@ -294,11 +301,11 @@ abstract class PagedCache extends DurableObject<CacheEnv> {
|
||||
} while (before !== this.#step);
|
||||
this.#step += 1;
|
||||
this.#pause = this.#clock.sleep(MS_PER_CALL);
|
||||
|
||||
const hit = await this.#pageFromStorage(pageNum, count);
|
||||
if (hit) {
|
||||
console.log({ event: "cache", kind: this.kind, page: pageNum, outcome: "hit", items: hit.items.length });
|
||||
return hit;
|
||||
// a request ahead of us at the gate may have etched this page
|
||||
const late = await this.#pageFromStorage(pageNum, count);
|
||||
if (late) {
|
||||
console.log({ event: "cache", kind: this.kind, page: pageNum, outcome: "hit", items: late.items.length });
|
||||
return late;
|
||||
}
|
||||
|
||||
// RPC stubs (the app and each pipelined step) are disposed once the
|
||||
|
||||
@@ -12,6 +12,7 @@ const WEBAPP_ID = "fake-webapp-id";
|
||||
type TestEnv = {
|
||||
CACHE_DO: DurableObjectNamespace<import("../src/index.js").DiigoCache>;
|
||||
PUBLIC_DO: DurableObjectNamespace<import("../src/index.js").PublicCache>;
|
||||
CLOCKED_DO: DurableObjectNamespace<import("./test-worker.js").FixedClockCache>;
|
||||
WEBAPP: { calls(): Promise<number> };
|
||||
};
|
||||
const testEnv = env as unknown as TestEnv;
|
||||
@@ -109,6 +110,18 @@ describe("page size", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("pacing", () => {
|
||||
it("paces diigo calls only: a cache hit doesn't wait", async () => {
|
||||
const stub = testEnv.CLOCKED_DO.get(testEnv.CLOCKED_DO.newUniqueId());
|
||||
await stub.setup({ webappId: WEBAPP_ID });
|
||||
await stub.page(1, PAGE_SIZE);
|
||||
const afterMiss = (await stub.sleeps()).length;
|
||||
expect(afterMiss).toBeGreaterThan(0);
|
||||
for (let i = 0; i < 3; i += 1) expect((await stub.page(1, PAGE_SIZE)).cached).toBe(true);
|
||||
expect(await stub.sleeps()).toHaveLength(afterMiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe("eviction", () => {
|
||||
it("a cache miss leaves no undisposed RPC stubs to block eviction", async () => {
|
||||
const stub = testEnv.CACHE_DO.get(testEnv.CACHE_DO.newUniqueId());
|
||||
|
||||
@@ -12,8 +12,23 @@ export { default } from "../src/index.js";
|
||||
/** 2026-10-07T12:00:00Z */
|
||||
export const FIXED_NOW = Date.UTC(2026, 9, 7, 12, 0, 0);
|
||||
|
||||
/** a fixed clock that records each sleep it is asked for */
|
||||
export class FixedClockCache extends DiigoCacheLogic {
|
||||
#sleeps: number[] = [];
|
||||
|
||||
constructor(ctx: DurableObjectState, env: CacheEnv) {
|
||||
super(ctx, env, { now: () => FIXED_NOW, sleep: async () => {} });
|
||||
const sleeps: number[] = [];
|
||||
super(ctx, env, {
|
||||
now: () => FIXED_NOW,
|
||||
sleep: async (ms) => {
|
||||
sleeps.push(ms);
|
||||
},
|
||||
});
|
||||
this.#sleeps = sleeps;
|
||||
}
|
||||
|
||||
/** the sleeps so far (test hook) */
|
||||
async sleeps(): Promise<number[]> {
|
||||
return [...this.#sleeps];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user