mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
src/domain/quotaCache.ts kept its quota state (cache Map, refreshingSet,
refreshTimer, tickRunning) in bare module-scope variables. In a Next.js 16
`output: "standalone"` build, code reachable only from instrumentation-node.ts
(providerLimitsSyncScheduler's write path) and code reachable from an
API-route/SSE-handler chunk (auth.ts::evaluateQuotaLimitPolicy()'s read path)
can be compiled into separate server chunks, each independently instantiating
this module's top-level state. A quota renewal written by the sync scheduler
was invisible to the routing read path, leaving accounts stuck exhausted until
a full process restart.
Anchors all quota-cache state on a single globalThis-held object, following
the same pattern already used in src/lib/credentialHealth/cache.ts and
src/lib/db/core.ts, and the identical fix already shipped for this exact
failure mode in src/lib/pricingSync.ts (#6325 / commit de9d748dac).
Regression test: tests/unit/repro-8065-quota-cache-cross-instance.test.ts
imports the module twice under distinct query-string specifiers to force two
separate module instances, proving a write from one instance is now visible
to a read from the other.
41 lines
1.7 KiB
TypeScript
41 lines
1.7 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-quota-cache-xinst-8065-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
|
|
test.after(() => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("#8065 a renewed quota written by one module instance is invisible to another module instance's routing read", async () => {
|
|
const connectionId = "conn-codex-8065";
|
|
|
|
// Instance R: simulates auth.ts's routing/credential-selection chunk.
|
|
const quotaCacheR = await import("../../src/domain/quotaCache.ts?instance=R");
|
|
quotaCacheR.setQuotaCache(connectionId, "codex", {
|
|
session: { remainingPercentage: 0, resetAt: new Date(Date.now() + 5 * 86400000).toISOString() },
|
|
});
|
|
assert.equal(quotaCacheR.isQuotaExhaustedForRequest(connectionId, "codex"), true);
|
|
|
|
// Instance W: simulates providerLimitsSyncScheduler's instrumentation-node.ts chunk.
|
|
const quotaCacheW = await import("../../src/domain/quotaCache.ts?instance=W");
|
|
quotaCacheW.setQuotaCache(connectionId, "codex", {
|
|
session: { remainingPercentage: 100, resetAt: new Date(Date.now() + 7 * 86400000).toISOString() },
|
|
});
|
|
assert.equal(quotaCacheW.isQuotaExhaustedForRequest(connectionId, "codex"), false);
|
|
|
|
// Back on instance R — must see the renewed quota written by instance W.
|
|
assert.equal(
|
|
quotaCacheR.isQuotaExhaustedForRequest(connectionId, "codex"),
|
|
false,
|
|
"instance R must see the renewed quota written by instance W"
|
|
);
|
|
});
|