

















@@ -742,6 +742,78 @@ describe("acquireSessionWriteLock", () => {
742742}
743743});
744744745+it("memoizes readOwnerProcessArgs across locks with the same pid in one sweep (#86509)", async () => {
746+const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lock-"));
747+const sessionsDir = path.join(root, "sessions");
748+await fs.mkdir(sessionsDir, { recursive: true });
749+const nowMs = Date.now();
750+const lockCount = 5;
751+try {
752+for (let i = 0; i < lockCount; i++) {
753+await fs.writeFile(
754+path.join(sessionsDir, `same-pid-${i}.jsonl.lock`),
755+JSON.stringify({ pid: process.pid, createdAt: new Date(nowMs).toISOString() }),
756+"utf8",
757+);
758+}
759+const readArgsCalls: number[] = [];
760+const readOwnerProcessArgs = (pid: number) => {
761+readArgsCalls.push(pid);
762+return ["node", "/srv/app/dist/index.js"];
763+};
764+const result = await cleanStaleLockFiles({
765+ sessionsDir,
766+staleMs: 30_000,
767+ nowMs,
768+removeStale: true,
769+ readOwnerProcessArgs,
770+});
771+expect(result.cleaned).toHaveLength(lockCount);
772+// Without memo this would be `lockCount`; the per-pid cache collapses it to a single call.
773+expect(readArgsCalls).toEqual([process.pid]);
774+} finally {
775+await fs.rm(root, { recursive: true, force: true });
776+}
777+});
778+779+it("does not poison the per-pid memo when readOwnerProcessArgs throws (#86509)", async () => {
780+// A helper one layer up (`readOwnerProcessArgs`) already catches thrown resolvers and
781+// returns null, so `cleanStaleLockFiles` never propagates the throw — but a naive memo
782+// could still cache that null-equivalent failure and short-circuit later locks for the
783+// same pid. The fix writes the cache only after the resolver returns, so each lock
784+// retries the resolver fresh after a throw.
785+const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lock-"));
786+const sessionsDir = path.join(root, "sessions");
787+await fs.mkdir(sessionsDir, { recursive: true });
788+const nowMs = Date.now();
789+const lockCount = 3;
790+try {
791+for (let i = 0; i < lockCount; i++) {
792+await fs.writeFile(
793+path.join(sessionsDir, `throwing-${i}.jsonl.lock`),
794+JSON.stringify({ pid: process.pid, createdAt: new Date(nowMs).toISOString() }),
795+"utf8",
796+);
797+}
798+let throwCalls = 0;
799+const result = await cleanStaleLockFiles({
800+ sessionsDir,
801+staleMs: 30_000,
802+ nowMs,
803+removeStale: true,
804+readOwnerProcessArgs: () => {
805+throwCalls++;
806+throw new Error("transient resolver failure");
807+},
808+});
809+// Resolver is invoked once per lock — the throw is not cached as a no-args entry.
810+expect(throwCalls).toBe(lockCount);
811+expect(result.cleaned).toHaveLength(0);
812+} finally {
813+await fs.rm(root, { recursive: true, force: true });
814+}
815+});
816+745817it("keeps fresh live .jsonl lock files with OpenClaw or unknown owners", async () => {
746818const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lock-"));
747819const sessionsDir = path.join(root, "sessions");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。