fix: normalize session lock path
This commit is contained in:
34
src/agents/session-write-lock.test.ts
Normal file
34
src/agents/session-write-lock.test.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { acquireSessionWriteLock } from "./session-write-lock.js";
|
||||||
|
|
||||||
|
describe("acquireSessionWriteLock", () => {
|
||||||
|
it("reuses locks across symlinked session paths", async () => {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-lock-"));
|
||||||
|
try {
|
||||||
|
const realDir = path.join(root, "real");
|
||||||
|
const linkDir = path.join(root, "link");
|
||||||
|
await fs.mkdir(realDir, { recursive: true });
|
||||||
|
await fs.symlink(realDir, linkDir);
|
||||||
|
|
||||||
|
const sessionReal = path.join(realDir, "sessions.json");
|
||||||
|
const sessionLink = path.join(linkDir, "sessions.json");
|
||||||
|
|
||||||
|
const lockA = await acquireSessionWriteLock({ sessionFile: sessionReal, timeoutMs: 500 });
|
||||||
|
const lockB = await acquireSessionWriteLock({ sessionFile: sessionLink, timeoutMs: 500 });
|
||||||
|
|
||||||
|
await lockB.release();
|
||||||
|
await lockA.release();
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -45,20 +45,28 @@ export async function acquireSessionWriteLock(params: {
|
|||||||
}> {
|
}> {
|
||||||
const timeoutMs = params.timeoutMs ?? 10_000;
|
const timeoutMs = params.timeoutMs ?? 10_000;
|
||||||
const staleMs = params.staleMs ?? 30 * 60 * 1000;
|
const staleMs = params.staleMs ?? 30 * 60 * 1000;
|
||||||
const sessionFile = params.sessionFile;
|
const sessionFile = path.resolve(params.sessionFile);
|
||||||
const lockPath = `${sessionFile}.lock`;
|
const sessionDir = path.dirname(sessionFile);
|
||||||
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
await fs.mkdir(sessionDir, { recursive: true });
|
||||||
|
let normalizedDir = sessionDir;
|
||||||
|
try {
|
||||||
|
normalizedDir = await fs.realpath(sessionDir);
|
||||||
|
} catch {
|
||||||
|
// Fall back to the resolved path if realpath fails (permissions, transient FS).
|
||||||
|
}
|
||||||
|
const normalizedSessionFile = path.join(normalizedDir, path.basename(sessionFile));
|
||||||
|
const lockPath = `${normalizedSessionFile}.lock`;
|
||||||
|
|
||||||
const held = HELD_LOCKS.get(sessionFile);
|
const held = HELD_LOCKS.get(normalizedSessionFile);
|
||||||
if (held) {
|
if (held) {
|
||||||
held.count += 1;
|
held.count += 1;
|
||||||
return {
|
return {
|
||||||
release: async () => {
|
release: async () => {
|
||||||
const current = HELD_LOCKS.get(sessionFile);
|
const current = HELD_LOCKS.get(normalizedSessionFile);
|
||||||
if (!current) return;
|
if (!current) return;
|
||||||
current.count -= 1;
|
current.count -= 1;
|
||||||
if (current.count > 0) return;
|
if (current.count > 0) return;
|
||||||
HELD_LOCKS.delete(sessionFile);
|
HELD_LOCKS.delete(normalizedSessionFile);
|
||||||
await current.handle.close();
|
await current.handle.close();
|
||||||
await fs.rm(current.lockPath, { force: true });
|
await fs.rm(current.lockPath, { force: true });
|
||||||
},
|
},
|
||||||
@@ -75,14 +83,14 @@ export async function acquireSessionWriteLock(params: {
|
|||||||
JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }, null, 2),
|
JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }, null, 2),
|
||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
HELD_LOCKS.set(sessionFile, { count: 1, handle, lockPath });
|
HELD_LOCKS.set(normalizedSessionFile, { count: 1, handle, lockPath });
|
||||||
return {
|
return {
|
||||||
release: async () => {
|
release: async () => {
|
||||||
const current = HELD_LOCKS.get(sessionFile);
|
const current = HELD_LOCKS.get(normalizedSessionFile);
|
||||||
if (!current) return;
|
if (!current) return;
|
||||||
current.count -= 1;
|
current.count -= 1;
|
||||||
if (current.count > 0) return;
|
if (current.count > 0) return;
|
||||||
HELD_LOCKS.delete(sessionFile);
|
HELD_LOCKS.delete(normalizedSessionFile);
|
||||||
await current.handle.close();
|
await current.handle.close();
|
||||||
await fs.rm(current.lockPath, { force: true });
|
await fs.rm(current.lockPath, { force: true });
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user