fix(gateway): align dedupe eviction with entry timestamp

This commit is contained in:
Gabriel Piss
2026-05-08 22:52:17 -04:00
committed by Peter Steinberger
parent 11e6c66004
commit da03269f6d
2 changed files with 37 additions and 3 deletions
+28
View File
@@ -1,6 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { HealthSummary } from "../commands/health.js";
import type { ChatAbortControllerEntry } from "./chat-abort.js";
import { DEDUPE_MAX } from "./server-constants.js";
const cleanOldMediaMock = vi.fn(async () => {});
@@ -222,4 +223,31 @@ describe("startGatewayMaintenanceTimers", () => {
stopMaintenanceTimers(timers);
});
it("evicts dedupe overflow by oldest timestamp even after reinsertion", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-22T00:00:00Z"));
const { startGatewayMaintenanceTimers } = await import("./server-maintenance.js");
const deps = createMaintenanceTimerDeps();
const now = Date.now();
for (let index = 0; index < DEDUPE_MAX; index += 1) {
deps.dedupe.set(`stable-${index}`, { ts: now - 1_000 + index, ok: true });
}
deps.dedupe.delete("stable-10");
deps.dedupe.set("stable-10", { ts: now - 2_000, ok: true });
deps.dedupe.set("overflow-newest", { ts: now - 100, ok: true });
const timers = startGatewayMaintenanceTimers(deps);
await vi.advanceTimersByTimeAsync(60_000);
expect(deps.dedupe.size).toBe(DEDUPE_MAX);
expect(deps.dedupe.has("stable-10")).toBe(false);
expect(deps.dedupe.has("stable-0")).toBe(true);
expect(deps.dedupe.has("overflow-newest")).toBe(true);
stopMaintenanceTimers(timers);
});
});
+9 -3
View File
@@ -90,9 +90,15 @@ export function startGatewayMaintenanceTimers(params: {
}
}
if (params.dedupe.size > DEDUPE_MAX) {
const entries = [...params.dedupe.entries()].toSorted((a, b) => a[1].ts - b[1].ts);
for (let i = 0; i < params.dedupe.size - DEDUPE_MAX; i++) {
params.dedupe.delete(entries[i][0]);
const excess = params.dedupe.size - DEDUPE_MAX;
// Keep overflow eviction aligned with the entry timestamp, not Map
// insertion order, so refresh/reinsert paths still prune the oldest data.
const oldestKeys = [...params.dedupe.entries()]
.toSorted(([, left], [, right]) => left.ts - right.ts)
.slice(0, excess)
.map(([key]) => key);
for (const key of oldestKeys) {
params.dedupe.delete(key);
}
}