fix(test): stabilize windows lock and cache paths

This commit is contained in:
Ayaan Zaidi
2026-03-27 13:27:14 +05:30
parent a30dae3c71
commit d6662e2aa7
2 changed files with 77 additions and 1 deletions

View File

@@ -0,0 +1,58 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const acquireSessionWriteLockMock = vi.hoisted(() =>
vi.fn(async () => ({ release: vi.fn(async () => {}) })),
);
let withSessionStoreLockForTest: typeof import("./store.js").withSessionStoreLockForTest;
let clearSessionStoreCacheForTest: typeof import("./store.js").clearSessionStoreCacheForTest;
async function loadFreshStoreModule() {
vi.resetModules();
vi.doMock("../../agents/session-write-lock.js", async (importOriginal) => {
const original = await importOriginal<typeof import("../../agents/session-write-lock.js")>();
return {
...original,
acquireSessionWriteLock: acquireSessionWriteLockMock,
};
});
({ withSessionStoreLockForTest, clearSessionStoreCacheForTest } = await import("./store.js"));
}
describe("withSessionStoreLock", () => {
beforeEach(async () => {
acquireSessionWriteLockMock.mockClear();
await loadFreshStoreModule();
});
afterEach(() => {
clearSessionStoreCacheForTest();
vi.restoreAllMocks();
});
it("derives session lock hold time from the store lock timeout", async () => {
await withSessionStoreLockForTest("/tmp/openclaw-store.json", async () => {}, {
timeoutMs: 10_000,
});
expect(acquireSessionWriteLockMock).toHaveBeenCalledWith({
sessionFile: "/tmp/openclaw-store.json",
timeoutMs: 10_000,
staleMs: 30_000,
maxHoldMs: 15_000,
});
});
it("leaves the session lock hold time unset when store locking has no timeout", async () => {
await withSessionStoreLockForTest("/tmp/openclaw-store.json", async () => {}, {
timeoutMs: 0,
});
expect(acquireSessionWriteLockMock).toHaveBeenCalledWith({
sessionFile: "/tmp/openclaw-store.json",
timeoutMs: Number.POSITIVE_INFINITY,
staleMs: 30_000,
maxHoldMs: undefined,
});
});
});

View File

@@ -1,6 +1,9 @@
import fs from "node:fs";
import path from "node:path";
import { acquireSessionWriteLock } from "../../agents/session-write-lock.js";
import {
acquireSessionWriteLock,
resolveSessionLockMaxHoldFromTimeout,
} from "../../agents/session-write-lock.js";
import type { MsgContext } from "../../auto-reply/templating.js";
import { writeTextAtomic } from "../../infra/json-files.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
@@ -616,6 +619,9 @@ type SessionStoreLockOptions = {
staleMs?: number;
};
const SESSION_STORE_LOCK_MIN_HOLD_MS = 5_000;
const SESSION_STORE_LOCK_TIMEOUT_GRACE_MS = 5_000;
type SessionStoreLockTask = {
fn: () => Promise<unknown>;
resolve: (value: unknown) => void;
@@ -708,6 +714,17 @@ function lockTimeoutError(storePath: string): Error {
return new Error(`timeout waiting for session store lock: ${storePath}`);
}
function resolveSessionStoreLockMaxHoldMs(timeoutMs: number | undefined): number | undefined {
if (timeoutMs == null || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
return undefined;
}
return resolveSessionLockMaxHoldFromTimeout({
timeoutMs,
graceMs: SESSION_STORE_LOCK_TIMEOUT_GRACE_MS,
minMs: SESSION_STORE_LOCK_MIN_HOLD_MS,
});
}
function getOrCreateLockQueue(storePath: string): SessionStoreLockQueue {
const existing = LOCK_QUEUES.get(storePath);
if (existing) {
@@ -751,6 +768,7 @@ async function drainSessionStoreLockQueue(storePath: string): Promise<void> {
sessionFile: storePath,
timeoutMs: remainingTimeoutMs,
staleMs: task.staleMs,
maxHoldMs: resolveSessionStoreLockMaxHoldMs(task.timeoutMs),
});
result = await task.fn();
} catch (err) {