From 22d9686adfdba5a6f772dab7d364e43208537324 Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Mon, 20 Apr 2026 22:32:58 +0800 Subject: [PATCH 1/3] fix(store): Issue #670 realpath:false + maintainer suggestions - Add realpath:false to avoid ENOENT after stale lock cleanup - Use lockfilePath option to separate target from artifact path - Update stale cleanup to only remove directory artifacts - Keep James conservative retry settings from Issue #415 --- src/store.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/store.ts b/src/store.ts index aa0dff9a..ec2d04a2 100644 --- a/src/store.ts +++ b/src/store.ts @@ -217,22 +217,38 @@ export class MemoryStore { try { const { writeFileSync } = await import("node:fs"); writeFileSync(lockPath, "", { flag: "wx" }); } catch {} } - // Proactive cleanup of stale lock artifacts (fixes stale-lock ECOMPROMISED) + // Proactive cleanup of stale lock artifacts (from PR #626, updated for proper-lockfile v4) + // Only remove stale DIRECTORY artifacts (proper-lockfile v4 uses mkdir, not files) if (existsSync(lockPath)) { try { const stat = statSync(lockPath); const ageMs = Date.now() - stat.mtimeMs; const staleThresholdMs = 5 * 60 * 1000; - if (ageMs > staleThresholdMs) { - try { unlinkSync(lockPath); } catch {} + if (ageMs > staleThresholdMs && stat.isDirectory()) { + try { rmSync(lockPath, { recursive: true, force: true }); } catch {} console.warn(`[memory-lancedb-pro] cleared stale lock: ${lockPath} ageMs=${ageMs}`); } } catch {} } - const release = await lockfile.lock(lockPath, { - retries: { retries: 10, factor: 2, minTimeout: 200, maxTimeout: 5000 }, - stale: 10000, + const release = await lockfile.lock(this.config.dbPath, { + lockfilePath: lockPath, // explicit artifact path, avoids ambiguity with proper-lockfile v4 mkdir behavior + realpath: false, // Fix #670: skip realpath() to avoid ENOENT after stale lock cleanup + retries: { + retries: 10, + factor: 2, + minTimeout: 1000, // James 保守設定:避免高負載下過度密集重試 + maxTimeout: 30000, // James 保守設定:支撐更久的 event loop 阻塞 + }, + stale: 10000, // 10 秒後視為 stale,觸發 ECOMPROMISED callback + // 注意:ECOMPROMISED 是 ambiguous degradation 訊號,mtime 無法區分 + // "holder 崩潰" vs "holder event loop 阻塞",所以不嘗試區分 + onCompromised: (err: unknown) => { + // 【修復 #415 關鍵】必須是同步 callback + // setLockAsCompromised() 不等待 Promise,async throw 無法傳回 caller + isCompromised = true; + compromisedErr = err; + }, }); try { return await fn(); } finally { await release(); } } From f0be3b96145c31826b42849778612c5e88d638f7 Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Mon, 20 Apr 2026 22:42:37 +0800 Subject: [PATCH 2/3] fix(store): use correct primitive for stale cleanup rmSync with recursive:true still calls rmdir on FILE paths (ENOTDIR). Use unlinkSync for files, rmSync for directories. --- src/store.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/store.ts b/src/store.ts index ec2d04a2..c90bb500 100644 --- a/src/store.ts +++ b/src/store.ts @@ -224,8 +224,14 @@ export class MemoryStore { const stat = statSync(lockPath); const ageMs = Date.now() - stat.mtimeMs; const staleThresholdMs = 5 * 60 * 1000; - if (ageMs > staleThresholdMs && stat.isDirectory()) { - try { rmSync(lockPath, { recursive: true, force: true }); } catch {} + if (ageMs > staleThresholdMs) { + try { + if (stat.isDirectory()) { + rmSync(lockPath, { recursive: true, force: true }); + } else { + unlinkSync(lockPath); // proper-lockfile v4 artifact is dir; FILE may be pre-created lock + } + } catch {} console.warn(`[memory-lancedb-pro] cleared stale lock: ${lockPath} ageMs=${ageMs}`); } } catch {} From dae3f74cd9a727d09a94b5140b1470733e831c63 Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Mon, 20 Apr 2026 23:00:19 +0800 Subject: [PATCH 3/3] fix(store): remove pre-creation that conflicts with proper-lockfile v4 mkdir Pre-creation created a FILE at lockPath. proper-lockfile v4 creates a DIRECTORY at the same path (since lockPath ends with .lock). This causes ENOTDIR when proper-lockfile tries to rmdir a file path. With realpath:false, pre-creation is no longer needed. --- src/store.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/store.ts b/src/store.ts index c90bb500..f858d635 100644 --- a/src/store.ts +++ b/src/store.ts @@ -211,12 +211,6 @@ export class MemoryStore { const lockfile = await loadLockfile(); const lockPath = join(this.config.dbPath, ".memory-write.lock"); - // Ensure lock file exists before locking (proper-lockfile requires it) - if (!existsSync(lockPath)) { - try { mkdirSync(dirname(lockPath), { recursive: true }); } catch {} - try { const { writeFileSync } = await import("node:fs"); writeFileSync(lockPath, "", { flag: "wx" }); } catch {} - } - // Proactive cleanup of stale lock artifacts (from PR #626, updated for proper-lockfile v4) // Only remove stale DIRECTORY artifacts (proper-lockfile v4 uses mkdir, not files) if (existsSync(lockPath)) { @@ -237,8 +231,7 @@ export class MemoryStore { } catch {} } - const release = await lockfile.lock(this.config.dbPath, { - lockfilePath: lockPath, // explicit artifact path, avoids ambiguity with proper-lockfile v4 mkdir behavior + const release = await lockfile.lock(lockPath, { realpath: false, // Fix #670: skip realpath() to avoid ENOENT after stale lock cleanup retries: { retries: 10,