perf(test): remove sleeps from session store lock suite

This commit is contained in:
Peter Steinberger
2026-02-15 00:25:42 +00:00
parent 9eb749b0a6
commit 1eeffd7c09

View File

@@ -1,9 +1,8 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "./types.js"; import type { SessionEntry } from "./types.js";
import { sleep } from "../../utils.js";
import { import {
clearSessionStoreCacheForTest, clearSessionStoreCacheForTest,
getSessionStoreLockQueueSizeForTest, getSessionStoreLockQueueSizeForTest,
@@ -18,11 +17,34 @@ describe("session store lock (Promise chain mutex)", () => {
let caseId = 0; let caseId = 0;
let tmpDirs: string[] = []; let tmpDirs: string[] = [];
function createDeferred<T>() {
let resolve!: (value: T) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
async function waitForFile(filePath: string, maxTicks = 50): Promise<void> {
for (let tick = 0; tick < maxTicks; tick += 1) {
try {
await fs.access(filePath);
return;
} catch {
// Works under both real + fake timers (setImmediate is faked).
await new Promise<void>((resolve) => process.nextTick(resolve));
}
}
throw new Error(`timed out waiting for file: ${filePath}`);
}
async function makeTmpStore( async function makeTmpStore(
initial: Record<string, unknown> = {}, initial: Record<string, unknown> = {},
): Promise<{ dir: string; storePath: string }> { ): Promise<{ dir: string; storePath: string }> {
const dir = path.join(fixtureRoot, `case-${caseId++}`); const dir = path.join(fixtureRoot, `case-${caseId++}`);
await fs.mkdir(dir, { recursive: true }); await fs.mkdir(dir);
tmpDirs.push(dir); tmpDirs.push(dir);
const storePath = path.join(dir, "sessions.json"); const storePath = path.join(dir, "sessions.json");
if (Object.keys(initial).length > 0) { if (Object.keys(initial).length > 0) {
@@ -280,30 +302,44 @@ describe("session store lock (Promise chain mutex)", () => {
}); });
it("times out queued operations strictly and does not run them later", async () => { it("times out queued operations strictly and does not run them later", async () => {
const { storePath } = await makeTmpStore({ vi.useFakeTimers();
x: { sessionId: "x", updatedAt: 100 }, try {
}); const { storePath } = await makeTmpStore({
let timedOutRan = false; x: { sessionId: "x", updatedAt: 100 },
});
let timedOutRan = false;
const lockHolder = withSessionStoreLockForTest( const lockPath = `${storePath}.lock`;
storePath, const releaseLock = createDeferred<void>();
async () => { const lockHolder = withSessionStoreLockForTest(
await sleep(15); storePath,
}, async () => {
{ timeoutMs: 1_000 }, await releaseLock.promise;
); },
const timedOut = withSessionStoreLockForTest( { timeoutMs: 1_000 },
storePath, );
async () => { await waitForFile(lockPath);
timedOutRan = true; const timedOut = withSessionStoreLockForTest(
}, storePath,
{ timeoutMs: 5 }, async () => {
); timedOutRan = true;
},
{ timeoutMs: 5 },
);
await expect(timedOut).rejects.toThrow("timeout waiting for session store lock"); // Attach rejection handler before advancing fake timers to avoid unhandled rejections.
await lockHolder; const timedOutExpectation = expect(timedOut).rejects.toThrow(
await sleep(2); "timeout waiting for session store lock",
expect(timedOutRan).toBe(false); );
await vi.advanceTimersByTimeAsync(5);
await timedOutExpectation;
releaseLock.resolve();
await lockHolder;
await vi.runOnlyPendingTimersAsync();
expect(timedOutRan).toBe(false);
} finally {
vi.useRealTimers();
}
}); });
it("creates and removes lock file while operation runs", async () => { it("creates and removes lock file while operation runs", async () => {
@@ -312,23 +348,15 @@ describe("session store lock (Promise chain mutex)", () => {
[key]: { sessionId: "s1", updatedAt: 100 }, [key]: { sessionId: "s1", updatedAt: 100 },
}); });
const lockPath = `${storePath}.lock`;
const allowWrite = createDeferred<void>();
const write = updateSessionStore(storePath, async (store) => { const write = updateSessionStore(storePath, async (store) => {
await sleep(8); await allowWrite.promise;
store[key] = { ...store[key], modelOverride: "v" } as unknown as SessionEntry; store[key] = { ...store[key], modelOverride: "v" } as unknown as SessionEntry;
}); });
const lockPath = `${storePath}.lock`; await waitForFile(lockPath);
let lockSeen = false; allowWrite.resolve();
for (let i = 0; i < 20; i += 1) {
try {
await fs.access(lockPath);
lockSeen = true;
break;
} catch {
await sleep(1);
}
}
expect(lockSeen).toBe(true);
await write; await write;
const files = await fs.readdir(dir); const files = await fs.readdir(dir);