























@@ -12,6 +12,7 @@ type LockFilePayload = {
1212createdAt?: string;
1313/** Process start time in clock ticks (from /proc/pid/stat field 22). */
1414starttime?: number;
15+maxHoldMs?: number;
1516};
16171718function isValidLockNumber(value: unknown): value is number {
@@ -378,6 +379,9 @@ async function readLockPayload(lockPath: string): Promise<LockFilePayload | null
378379if (isValidLockNumber(parsed.starttime)) {
379380payload.starttime = parsed.starttime;
380381}
382+if (isValidLockNumber(parsed.maxHoldMs) && parsed.maxHoldMs > 0) {
383+payload.maxHoldMs = parsed.maxHoldMs;
384+}
381385return payload;
382386} catch {
383387return null;
@@ -449,6 +453,7 @@ function inspectLockPayload(
449453payload: LockFilePayload | null,
450454staleMs: number,
451455nowMs: number,
456+opts: { respectMaxHold?: boolean } = {},
452457): LockInspectionDetails {
453458const pid = isValidLockNumber(payload?.pid) && payload.pid > 0 ? payload.pid : null;
454459const pidAlive = pid !== null ? isPidAlive(pid) : false;
@@ -481,6 +486,16 @@ function inspectLockPayload(
481486} else if (ageMs > staleMs) {
482487staleReasons.push("too-old");
483488}
489+const holderMaxHoldMs =
490+isValidLockNumber(payload?.maxHoldMs) && payload.maxHoldMs > 0 ? payload.maxHoldMs : undefined;
491+if (
492+opts.respectMaxHold === true &&
493+typeof holderMaxHoldMs === "number" &&
494+ageMs !== null &&
495+ageMs > holderMaxHoldMs
496+) {
497+staleReasons.push("hold-exceeded");
498+}
484499485500return {
486501 pid,
@@ -552,39 +567,6 @@ function sessionLockHeldByThisProcess(normalizedSessionFile: string): boolean {
552567);
553568}
554569555-async function removeReportedStaleLockIfStillStale(params: {
556-lockPath: string;
557-normalizedSessionFile: string;
558-staleMs: number;
559-readOwnerProcessArgs?: SessionLockOwnerProcessArgsReader;
560-}): Promise<boolean> {
561-const nowMs = Date.now();
562-const payload = await readLockPayload(params.lockPath);
563-if (payload === null) {
564-try {
565-await fs.access(params.lockPath);
566-} catch (error) {
567-if ((error as NodeJS.ErrnoException).code === "ENOENT") {
568-return true;
569-}
570-throw error;
571-}
572-}
573-const inspected = inspectLockPayloadForSession({
574- payload,
575-staleMs: params.staleMs,
576- nowMs,
577-heldByThisProcess: sessionLockHeldByThisProcess(params.normalizedSessionFile),
578-reclaimLockWithoutStarttime: true,
579-readOwnerProcessArgs: params.readOwnerProcessArgs ?? readProcessArgsSync,
580-});
581-if (!(await shouldReclaimContendedLockFile(params.lockPath, inspected, params.staleMs, nowMs))) {
582-return false;
583-}
584-await fs.rm(params.lockPath, { force: true });
585-return true;
586-}
587-588570function shouldTreatAsOrphanSelfLock(params: {
589571payload: LockFilePayload | null;
590572heldByThisProcess: boolean;
@@ -616,8 +598,11 @@ function inspectLockPayloadForSession(params: {
616598heldByThisProcess: boolean;
617599reclaimLockWithoutStarttime: boolean;
618600readOwnerProcessArgs: SessionLockOwnerProcessArgsReader;
601+respectMaxHold?: boolean;
619602}): LockInspectionDetails {
620-const inspected = inspectLockPayload(params.payload, params.staleMs, params.nowMs);
603+const inspected = inspectLockPayload(params.payload, params.staleMs, params.nowMs, {
604+respectMaxHold: params.respectMaxHold,
605+});
621606if (
622607shouldTreatAsOrphanSelfLock({
623608payload: params.payload,
@@ -745,18 +730,20 @@ export async function acquireSessionWriteLock(params: {
745730const normalizedSessionFile = await resolveNormalizedSessionFile(sessionFile);
746731const lockPath = `${normalizedSessionFile}.lock`;
747732await fs.mkdir(sessionDir, { recursive: true });
733+748734while (true) {
749735try {
750736const lock = await SESSION_LOCKS.acquire(sessionFile, {
751737 staleMs,
752738 timeoutMs,
753739retry: { minTimeout: 50, maxTimeout: 1000, factor: 1 },
740+staleRecovery: "remove-if-unchanged",
754741 allowReentrant,
755742metadata: { maxHoldMs },
756743payload: () => {
757744const createdAt = new Date().toISOString();
758745const starttime = resolveProcessStartTimeForLock(process.pid);
759-const lockPayload: LockFilePayload = { pid: process.pid, createdAt };
746+const lockPayload: LockFilePayload = { pid: process.pid, createdAt, maxHoldMs };
760747if (starttime !== null) {
761748lockPayload.starttime = starttime;
762749}
@@ -770,24 +757,27 @@ export async function acquireSessionWriteLock(params: {
770757 heldByThisProcess,
771758reclaimLockWithoutStarttime: true,
772759readOwnerProcessArgs: readProcessArgsSync,
760+respectMaxHold: !heldByThisProcess,
761+});
762+return await shouldReclaimContendedLockFile(lockPath, inspected, staleMs, nowMs);
763+},
764+shouldRemoveStaleLock: async ({ lockPath, normalizedTargetPath, payload }) => {
765+const nowMs = Date.now();
766+const heldByThisProcess = sessionLockHeldByThisProcess(normalizedTargetPath);
767+const inspected = inspectLockPayloadForSession({
768+payload: payload as LockFilePayload | null,
769+ staleMs,
770+ nowMs,
771+ heldByThisProcess,
772+reclaimLockWithoutStarttime: true,
773+readOwnerProcessArgs: readProcessArgsSync,
774+respectMaxHold: !heldByThisProcess,
773775});
774776return await shouldReclaimContendedLockFile(lockPath, inspected, staleMs, nowMs);
775777},
776778});
777779return { release: lock.release };
778780} catch (err) {
779-if (isFileLockError(err, "file_lock_stale")) {
780-const staleLockPath = (err as { lockPath?: string }).lockPath ?? lockPath;
781-if (
782-await removeReportedStaleLockIfStillStale({
783-lockPath: staleLockPath,
784- normalizedSessionFile,
785- staleMs,
786-})
787-) {
788-continue;
789-}
790-}
791781if (!isFileLockError(err, "file_lock_timeout")) {
792782throw err;
793783}
@@ -802,6 +792,7 @@ export async function acquireSessionWriteLock(params: {
802792export const testing = {
803793cleanupSignals: [...CLEANUP_SIGNALS],
804794 handleTerminationSignal,
795+inspectLockPayloadForTest: inspectLockPayload,
805796 releaseAllLocksSync,
806797 runLockWatchdogCheck,
807798setProcessStartTimeResolverForTest(resolver: ((pid: number) => number | null) | null): void {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。