From 73073a91bb155dd1cd5f536ea0be86ba4cf9d73e Mon Sep 17 00:00:00 2001 From: Shakker Date: Wed, 1 Apr 2026 14:50:55 +0100 Subject: [PATCH] refactor: isolate session store test cleanup state --- src/config/sessions/store-lock-state.ts | 51 +++++++++++++++++++ src/config/sessions/store.ts | 66 ++++++------------------- src/test-utils/session-state-cleanup.ts | 8 ++- 3 files changed, 68 insertions(+), 57 deletions(-) create mode 100644 src/config/sessions/store-lock-state.ts diff --git a/src/config/sessions/store-lock-state.ts b/src/config/sessions/store-lock-state.ts new file mode 100644 index 00000000000..7ac92f93e31 --- /dev/null +++ b/src/config/sessions/store-lock-state.ts @@ -0,0 +1,51 @@ +import { clearSessionStoreCaches } from "./store-cache.js"; + +export type SessionStoreLockTask = { + fn: () => Promise; + resolve: (value: unknown) => void; + reject: (reason: unknown) => void; + timeoutMs?: number; + staleMs: number; +}; + +export type SessionStoreLockQueue = { + running: boolean; + pending: SessionStoreLockTask[]; + drainPromise: Promise | null; +}; + +export const LOCK_QUEUES = new Map(); + +export function clearSessionStoreCacheForTest(): void { + clearSessionStoreCaches(); + for (const queue of LOCK_QUEUES.values()) { + for (const task of queue.pending) { + task.reject(new Error("session store queue cleared for test")); + } + } + LOCK_QUEUES.clear(); +} + +export async function drainSessionStoreLockQueuesForTest(): Promise { + while (LOCK_QUEUES.size > 0) { + const queues = [...LOCK_QUEUES.values()]; + for (const queue of queues) { + for (const task of queue.pending) { + task.reject(new Error("session store queue cleared for test")); + } + queue.pending.length = 0; + } + const activeDrains = queues.flatMap((queue) => + queue.drainPromise ? [queue.drainPromise] : [], + ); + if (activeDrains.length === 0) { + LOCK_QUEUES.clear(); + return; + } + await Promise.allSettled(activeDrains); + } +} + +export function getSessionStoreLockQueueSizeForTest(): number { + return LOCK_QUEUES.size; +} diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index 48b54d7590d..ff6c62b3494 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -18,7 +18,6 @@ import { getFileStatSnapshot } from "../cache-utils.js"; import { enforceSessionDiskBudget, type SessionDiskBudgetSweepResult } from "./disk-budget.js"; import { deriveSessionMetaPatch } from "./metadata.js"; import { - clearSessionStoreCaches, dropSessionStoreObjectCache, getSerializedSessionStore, isSessionStoreCacheEnabled, @@ -26,6 +25,14 @@ import { setSerializedSessionStore, writeSessionStoreCache, } from "./store-cache.js"; +import { + clearSessionStoreCacheForTest, + drainSessionStoreLockQueuesForTest, + getSessionStoreLockQueueSizeForTest, + LOCK_QUEUES, + type SessionStoreLockQueue, + type SessionStoreLockTask, +} from "./store-lock-state.js"; import { capEntryCount, getActiveSessionMaintenanceWarning, @@ -43,6 +50,12 @@ import { type SessionEntry, } from "./types.js"; +export { + clearSessionStoreCacheForTest, + drainSessionStoreLockQueuesForTest, + getSessionStoreLockQueueSizeForTest, +} from "./store-lock-state.js"; + const log = createSubsystemLogger("sessions/store"); let sessionArchiveRuntimePromise: Promise< typeof import("../../gateway/session-archive.runtime.js") @@ -157,16 +170,6 @@ function normalizeSessionStore(store: Record): void { } } -export function clearSessionStoreCacheForTest(): void { - clearSessionStoreCaches(); - for (const queue of LOCK_QUEUES.values()) { - for (const task of queue.pending) { - task.reject(new Error("session store queue cleared for test")); - } - } - LOCK_QUEUES.clear(); -} - export function setSessionWriteLockAcquirerForTests( acquirer: typeof acquireSessionWriteLock | null, ): void { @@ -177,31 +180,6 @@ export function resetSessionStoreLockRuntimeForTests(): void { sessionWriteLockAcquirerForTests = null; } -export async function drainSessionStoreLockQueuesForTest(): Promise { - while (LOCK_QUEUES.size > 0) { - const queues = [...LOCK_QUEUES.values()]; - for (const queue of queues) { - for (const task of queue.pending) { - task.reject(new Error("session store queue cleared for test")); - } - queue.pending.length = 0; - } - const activeDrains = queues.flatMap((queue) => - queue.drainPromise ? [queue.drainPromise] : [], - ); - if (activeDrains.length === 0) { - LOCK_QUEUES.clear(); - return; - } - await Promise.allSettled(activeDrains); - } -} - -/** Expose lock queue size for tests. */ -export function getSessionStoreLockQueueSizeForTest(): number { - return LOCK_QUEUES.size; -} - export async function withSessionStoreLockForTest( storePath: string, fn: () => Promise, @@ -633,22 +611,6 @@ type SessionStoreLockOptions = { const SESSION_STORE_LOCK_MIN_HOLD_MS = 5_000; const SESSION_STORE_LOCK_TIMEOUT_GRACE_MS = 5_000; -type SessionStoreLockTask = { - fn: () => Promise; - resolve: (value: unknown) => void; - reject: (reason: unknown) => void; - timeoutMs?: number; - staleMs: number; -}; - -type SessionStoreLockQueue = { - running: boolean; - pending: SessionStoreLockTask[]; - drainPromise: Promise | null; -}; - -const LOCK_QUEUES = new Map(); - function getErrorCode(error: unknown): string | null { if (!error || typeof error !== "object" || !("code" in error)) { return null; diff --git a/src/test-utils/session-state-cleanup.ts b/src/test-utils/session-state-cleanup.ts index 8cf16c671c7..dcf188f62ba 100644 --- a/src/test-utils/session-state-cleanup.ts +++ b/src/test-utils/session-state-cleanup.ts @@ -1,8 +1,6 @@ import { drainSessionWriteLockStateForTest } from "../agents/session-write-lock.js"; -import { - clearSessionStoreCacheForTest, - drainSessionStoreLockQueuesForTest, -} from "../config/sessions/store.js"; +import { clearSessionStoreCaches } from "../config/sessions/store-cache.js"; +import { drainSessionStoreLockQueuesForTest } from "../config/sessions/store-lock-state.js"; import { drainFileLockStateForTest } from "../infra/file-lock.js"; let fileLockDrainerForTests: typeof drainFileLockStateForTest | null = null; @@ -33,7 +31,7 @@ export function resetSessionStateCleanupRuntimeForTests(): void { export async function cleanupSessionStateForTest(): Promise { await (sessionStoreLockQueueDrainerForTests ?? drainSessionStoreLockQueuesForTest)(); - clearSessionStoreCacheForTest(); + clearSessionStoreCaches(); await (fileLockDrainerForTests ?? drainFileLockStateForTest)(); await (sessionWriteLockDrainerForTests ?? drainSessionWriteLockStateForTest)(); }