






















@@ -62,6 +62,10 @@ type LockInspectionDetails = Pick<
62626363const SESSION_LOCKS = createFileLockManager("openclaw.session-write-lock");
646465+function isFileLockError(error: unknown, code: string): boolean {
66+return (error as { code?: unknown } | null)?.code === code;
67+}
68+6569export type SessionWriteLockAcquireTimeoutConfig = {
6670session?: {
6771writeLock?: {
@@ -368,6 +372,33 @@ async function shouldReclaimContendedLockFile(
368372}
369373}
370374375+function sessionLockHeldByThisProcess(normalizedSessionFile: string): boolean {
376+return SESSION_LOCKS.heldEntries().some(
377+(entry) => entry.normalizedTargetPath === normalizedSessionFile,
378+);
379+}
380+381+async function removeReportedStaleLockIfStillStale(params: {
382+lockPath: string;
383+normalizedSessionFile: string;
384+staleMs: number;
385+}): Promise<boolean> {
386+const nowMs = Date.now();
387+const payload = await readLockPayload(params.lockPath);
388+const inspected = inspectLockPayloadForSession({
389+ payload,
390+staleMs: params.staleMs,
391+ nowMs,
392+heldByThisProcess: sessionLockHeldByThisProcess(params.normalizedSessionFile),
393+reclaimLockWithoutStarttime: true,
394+});
395+if (!(await shouldReclaimContendedLockFile(params.lockPath, inspected, params.staleMs, nowMs))) {
396+return false;
397+}
398+await fs.rm(params.lockPath, { force: true });
399+return true;
400+}
401+371402function shouldTreatAsOrphanSelfLock(params: {
372403payload: LockFilePayload | null;
373404heldByThisProcess: boolean;
@@ -502,42 +533,56 @@ export async function acquireSessionWriteLock(params: {
502533const normalizedSessionFile = await resolveNormalizedSessionFile(sessionFile);
503534const lockPath = `${normalizedSessionFile}.lock`;
504535await fs.mkdir(sessionDir, { recursive: true });
505-try {
506-const lock = await SESSION_LOCKS.acquire(sessionFile, {
507- staleMs,
508- timeoutMs,
509-retry: { minTimeout: 50, maxTimeout: 1000, factor: 1 },
510- allowReentrant,
511-metadata: { maxHoldMs },
512-payload: () => {
513-const createdAt = new Date().toISOString();
514-const starttime = getProcessStartTime(process.pid);
515-const lockPayload: LockFilePayload = { pid: process.pid, createdAt };
516-if (starttime !== null) {
517-lockPayload.starttime = starttime;
536+while (true) {
537+try {
538+const lock = await SESSION_LOCKS.acquire(sessionFile, {
539+ staleMs,
540+ timeoutMs,
541+retry: { minTimeout: 50, maxTimeout: 1000, factor: 1 },
542+ allowReentrant,
543+metadata: { maxHoldMs },
544+payload: () => {
545+const createdAt = new Date().toISOString();
546+const starttime = getProcessStartTime(process.pid);
547+const lockPayload: LockFilePayload = { pid: process.pid, createdAt };
548+if (starttime !== null) {
549+lockPayload.starttime = starttime;
550+}
551+return lockPayload as Record<string, unknown>;
552+},
553+shouldReclaim: async ({ payload, nowMs, heldByThisProcess }) => {
554+const inspected = inspectLockPayloadForSession({
555+payload: payload as LockFilePayload | null,
556+ staleMs,
557+ nowMs,
558+ heldByThisProcess,
559+reclaimLockWithoutStarttime: true,
560+});
561+return await shouldReclaimContendedLockFile(lockPath, inspected, staleMs, nowMs);
562+},
563+});
564+return { release: lock.release };
565+} catch (err) {
566+if (isFileLockError(err, "file_lock_stale")) {
567+const staleLockPath = (err as { lockPath?: string }).lockPath ?? lockPath;
568+if (
569+await removeReportedStaleLockIfStillStale({
570+lockPath: staleLockPath,
571+ normalizedSessionFile,
572+ staleMs,
573+})
574+) {
575+continue;
518576}
519-return lockPayload as Record<string, unknown>;
520-},
521-shouldReclaim: async ({ payload, nowMs, heldByThisProcess }) => {
522-const inspected = inspectLockPayloadForSession({
523-payload: payload as LockFilePayload | null,
524- staleMs,
525- nowMs,
526- heldByThisProcess,
527-reclaimLockWithoutStarttime: true,
528-});
529-return await shouldReclaimContendedLockFile(lockPath, inspected, staleMs, nowMs);
530-},
531-});
532-return { release: lock.release };
533-} catch (err) {
534-if ((err as { code?: unknown }).code !== "file_lock_timeout") {
535-throw err;
577+}
578+if (!isFileLockError(err, "file_lock_timeout")) {
579+throw err;
580+}
581+const timeoutLockPath = (err as { lockPath?: string }).lockPath ?? lockPath;
582+const payload = await readLockPayload(timeoutLockPath);
583+const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown";
584+throw new SessionWriteLockTimeoutError({ timeoutMs, owner, lockPath: timeoutLockPath });
536585}
537-const timeoutLockPath = (err as { lockPath?: string }).lockPath ?? lockPath;
538-const payload = await readLockPayload(timeoutLockPath);
539-const owner = typeof payload?.pid === "number" ? `pid=${payload.pid}` : "unknown";
540-throw new SessionWriteLockTimeoutError({ timeoutMs, owner, lockPath: timeoutLockPath });
541586}
542587}
543588此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。