






















@@ -70,6 +70,38 @@ async function writeCurrentProcessLock(lockPath: string, extra?: Record<string,
7070);
7171}
727273+async function withSymlinkedSessionPaths(
74+run: (params: {
75+sessionReal: string;
76+sessionLink: string;
77+realLockPath: string;
78+linkLockPath: string;
79+}) => Promise<void>,
80+) {
81+if (process.platform === "win32") {
82+return;
83+}
84+85+const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lock-"));
86+try {
87+const realDir = path.join(root, "real");
88+const linkDir = path.join(root, "link");
89+await fs.mkdir(realDir, { recursive: true });
90+await fs.symlink(realDir, linkDir);
91+92+const sessionReal = path.join(realDir, "sessions.json");
93+const sessionLink = path.join(linkDir, "sessions.json");
94+await run({
95+ sessionReal,
96+ sessionLink,
97+realLockPath: `${sessionReal}.lock`,
98+linkLockPath: `${sessionLink}.lock`,
99+});
100+} finally {
101+await fs.rm(root, { recursive: true, force: true });
102+}
103+}
104+73105async function expectActiveInProcessLockIsNotReclaimed(params?: {
74106legacyStarttime?: unknown;
75107}): Promise<void> {
@@ -109,48 +141,33 @@ describe("acquireSessionWriteLock", () => {
109141vi.restoreAllMocks();
110142});
111143it("reuses locks across symlinked session paths", async () => {
112-if (process.platform === "win32") {
113-return;
114-}
115-116-const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lock-"));
117-try {
118-const realDir = path.join(root, "real");
119-const linkDir = path.join(root, "link");
120-await fs.mkdir(realDir, { recursive: true });
121-await fs.symlink(realDir, linkDir);
122-123-const sessionReal = path.join(realDir, "sessions.json");
124-const sessionLink = path.join(linkDir, "sessions.json");
125-const realLockPath = `${sessionReal}.lock`;
126-const linkLockPath = `${sessionLink}.lock`;
127-128-const lockA = await acquireSessionWriteLock({
129-sessionFile: sessionReal,
130-timeoutMs: 500,
131-allowReentrant: true,
132-});
133-const lockB = await acquireSessionWriteLock({
134-sessionFile: sessionLink,
135-timeoutMs: 500,
136-allowReentrant: true,
137-});
138-139-await expect(fs.access(realLockPath)).resolves.toBeUndefined();
140-await expect(fs.access(linkLockPath)).resolves.toBeUndefined();
141-const [realCanonicalLockPath, linkCanonicalLockPath] = await Promise.all([
142-fs.realpath(realLockPath),
143-fs.realpath(linkLockPath),
144-]);
145-expect(linkCanonicalLockPath).toBe(realCanonicalLockPath);
146-await expectLockRemovedOnlyAfterFinalRelease({
147-lockPath: realLockPath,
148-firstLock: lockA,
149-secondLock: lockB,
150-});
151-} finally {
152-await fs.rm(root, { recursive: true, force: true });
153-}
144+await withSymlinkedSessionPaths(
145+async ({ sessionReal, sessionLink, realLockPath, linkLockPath }) => {
146+const lockA = await acquireSessionWriteLock({
147+sessionFile: sessionReal,
148+timeoutMs: 500,
149+allowReentrant: true,
150+});
151+const lockB = await acquireSessionWriteLock({
152+sessionFile: sessionLink,
153+timeoutMs: 500,
154+allowReentrant: true,
155+});
156+157+await expect(fs.access(realLockPath)).resolves.toBeUndefined();
158+await expect(fs.access(linkLockPath)).resolves.toBeUndefined();
159+const [realCanonicalLockPath, linkCanonicalLockPath] = await Promise.all([
160+fs.realpath(realLockPath),
161+fs.realpath(linkLockPath),
162+]);
163+expect(linkCanonicalLockPath).toBe(realCanonicalLockPath);
164+await expectLockRemovedOnlyAfterFinalRelease({
165+lockPath: realLockPath,
166+firstLock: lockA,
167+secondLock: lockB,
168+});
169+},
170+);
154171});
155172156173it("keeps the lock file until the last release", async () => {
@@ -185,29 +202,15 @@ describe("acquireSessionWriteLock", () => {
185202});
186203187204it("does not reenter locks by default through symlinked session paths", async () => {
188-if (process.platform === "win32") {
189-return;
190-}
191-192-const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lock-"));
193-try {
194-const realDir = path.join(root, "real");
195-const linkDir = path.join(root, "link");
196-await fs.mkdir(realDir, { recursive: true });
197-await fs.symlink(realDir, linkDir);
198-199-const sessionReal = path.join(realDir, "sessions.json");
200-const sessionLink = path.join(linkDir, "sessions.json");
205+await withSymlinkedSessionPaths(async ({ sessionReal, sessionLink }) => {
201206const lock = await acquireSessionWriteLock({ sessionFile: sessionReal, timeoutMs: 500 });
202207203208await expect(
204209acquireSessionWriteLock({ sessionFile: sessionLink, timeoutMs: 5, staleMs: 60_000 }),
205210).rejects.toThrow(/session file locked/);
206211207212await lock.release();
208-} finally {
209-await fs.rm(root, { recursive: true, force: true });
210-}
213+});
211214});
212215213216it("allows a new default lock acquisition after the held lock is released", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。