fix: avoid fd warnings in lock exit cleanup

This commit is contained in:
Peter Steinberger
2026-03-24 01:01:21 +00:00
parent 6c44b2ea50
commit e9905fd696
3 changed files with 13 additions and 68 deletions

View File

@@ -1,4 +1,3 @@
import fsSync from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -228,17 +227,14 @@ describe("acquireSessionWriteLock", () => {
}
});
it("closes file descriptors synchronously during process-exit cleanup", async () => {
it("removes lock files during process-exit cleanup", async () => {
await withTempSessionLockFile(async ({ sessionFile, lockPath }) => {
const closeSyncSpy = vi.spyOn(fsSync, "closeSync");
const lock = await acquireSessionWriteLock({ sessionFile, timeoutMs: 500 });
__testing.releaseAllLocksSync();
expect(closeSyncSpy).toHaveBeenCalledTimes(1);
await expect(fs.access(lockPath)).rejects.toThrow();
await lock.release();
closeSyncSpy.mockRestore();
});
});

View File

@@ -63,27 +63,6 @@ type LockInspectionDetails = Pick<
"pid" | "pidAlive" | "createdAt" | "ageMs" | "stale" | "staleReasons"
>;
function markFileHandleClosedSync(handle: fs.FileHandle): void {
const mutableHandle = handle as unknown as Record<PropertyKey, unknown>;
for (const key of Reflect.ownKeys(handle)) {
if (typeof key !== "symbol") {
continue;
}
const name = String(key);
if (name === "Symbol(kFd)") {
mutableHandle[key] = -1;
continue;
}
if (name === "Symbol(kRefs)") {
mutableHandle[key] = 0;
continue;
}
if (name === "Symbol(kClosePromise)") {
mutableHandle[key] = undefined;
}
}
}
const HELD_LOCKS = resolveProcessScopedMap<HeldLock>(HELD_LOCKS_KEY);
function resolveCleanupState(): CleanupState {
@@ -198,14 +177,6 @@ async function releaseHeldLock(
*/
function releaseAllLocksSync(): void {
for (const [sessionFile, held] of HELD_LOCKS) {
try {
if (typeof held.handle.fd === "number" && held.handle.fd >= 0) {
fsSync.closeSync(held.handle.fd);
markFileHandleClosedSync(held.handle);
}
} catch {
// Ignore errors during cleanup - best effort
}
try {
fsSync.rmSync(held.lockPath, { force: true });
} catch {

View File

@@ -30,43 +30,21 @@ const HELD_LOCKS_KEY = Symbol.for("openclaw.fileLockHeldLocks");
const HELD_LOCKS = resolveProcessScopedMap<HeldLock>(HELD_LOCKS_KEY);
const CLEANUP_REGISTERED_KEY = Symbol.for("openclaw.fileLockCleanupRegistered");
function markFileHandleClosedSync(handle: fs.FileHandle): void {
const mutableHandle = handle as unknown as Record<PropertyKey, unknown>;
for (const key of Reflect.ownKeys(handle)) {
if (typeof key !== "symbol") {
continue;
}
const name = String(key);
if (name === "Symbol(kFd)") {
mutableHandle[key] = -1;
continue;
}
if (name === "Symbol(kRefs)") {
mutableHandle[key] = 0;
continue;
}
if (name === "Symbol(kClosePromise)") {
mutableHandle[key] = undefined;
}
function releaseAllLocksSync(): void {
for (const [normalizedFile, held] of HELD_LOCKS) {
// Let the OS close live descriptors on process exit. On Linux/macOS this
// avoids Node's unmanaged-fd warnings while still unlinking the stale
// lock path before the process is fully gone.
rmLockPathSync(held.lockPath);
HELD_LOCKS.delete(normalizedFile);
}
}
function releaseAllLocksSync(): void {
for (const [normalizedFile, held] of HELD_LOCKS) {
try {
if (typeof held.handle.fd === "number" && held.handle.fd >= 0) {
fsSync.closeSync(held.handle.fd);
markFileHandleClosedSync(held.handle);
}
} catch {
// Best-effort exit cleanup only.
}
try {
fsSync.rmSync(held.lockPath, { force: true });
} catch {
// Best-effort exit cleanup only.
}
HELD_LOCKS.delete(normalizedFile);
function rmLockPathSync(lockPath: string): void {
try {
fsSync.rmSync(lockPath, { force: true });
} catch {
// Best-effort exit cleanup only.
}
}