feat(redis-lock): PR-A — M2/M4/N1~N5 fixes for #662#703
feat(redis-lock): PR-A — M2/M4/N1~N5 fixes for #662#703jlin53882 wants to merge 2 commits intoCortexReach:masterfrom
Conversation
Update: Codex Review Fix AppliedCodex adversarial review identified and fixed two issues: Commit Fix 1: M1 RedisUnavailableError constructor.name bug (Critical)
Fix 2: Two asymmetric fallback paths (Architecture)
Files Changed
Generated by OpenClaw — 2026-04-27 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09e0e38efc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * 實現分散式 lock,用於解決高並發寫入時的 lock contention 問題。 | ||
| */ | ||
|
|
||
| import Redis from "ioredis"; |
There was a problem hiding this comment.
Declare ioredis as a runtime dependency
src/redis-lock.ts imports ioredis, but this commit does not add it to the package dependencies. On a clean install, importing this module throws ERR_MODULE_NOT_FOUND, so getRedisLockManager() can never initialize Redis locking and silently falls back to file locks for all writes.
Useful? React with 👍 / 👎.
| if (!lockAcquired) { | ||
| console.warn("[memory-lancedb-pro] Redis lock acquire failed, falling back to file lock:", err); | ||
| return this.runWithFileLockCore(fn); |
There was a problem hiding this comment.
Use one fallback lock path for acquire failures
This branch falls back to runWithFileLockCore() whenever acquire() fails before lockAcquired is set, but acquire() itself already uses a different file-lock path in Redis-down mode (/tmp/.memory-lock-... in src/redis-lock.ts). During Redis outages with concurrent writers, one process can proceed under the tmp lock while another proceeds under .memory-write.lock, which breaks mutual exclusion for the same DB path.
Useful? React with 👍 / 👎.
| */ | ||
| private createFileLock(key: string, ttl?: number): () => Promise<void> { | ||
| const lockPath = path.join(nodeTmpdir(), `.memory-lock-${key}.lock`); | ||
| const lockTTL = (ttl || this.defaultTTL) / 1000; // proper-lockfile 參數是秒 |
There was a problem hiding this comment.
Pass stale timeout to proper-lockfile in milliseconds
The fallback file lock converts ttl with / 1000 before passing stale, but proper-lockfile expects stale in milliseconds. With the current conversion, the lock can be treated as stale far too quickly (or rejected for being below the minimum), which undermines lock safety exactly when Redis is unavailable.
Useful? React with 👍 / 👎.
…nectionError + N1/N2/N5 PR-A for CortexReach#662: Redis distributed lock fixes. Squashed into single commit from origin/master (02b97bb). === DIFF 解釋:為什麼刪除 1286 行 === store.ts 的 runWithFileLock 方法(212-289 行,78 行) 被重構成兩個部分: 1. 抽取為 runWithFileLockCore()(原 body) — 新增 file-lock fallback 核心實作(保持原有 151s retry 行為) 2. 新增 runWithFileLock() thin wrapper — Redis-first:先嘗試 Redis lock,失敗時進 runWithFileLockCore() fallback 另外新增 getRedisLockManager() 工廠函式(M2: initPromise guard) 和 src/redis-lock.ts(全新檔案,包含 Redis lock manager 實作) 所以「刪除 1286 行」的實際意義是: store.ts 的總行數從 1278 → 1360(+82 行), 但因為重構置換了 runWithFileLock,Git 把舊的實作標記為「刪除」。 === 實作內容 === M2: getRedisLockManager() initPromise guard - 防止並發建立多個 Redis client - 模組層級變數:redisLockManager + redisInitPromise M4: isRedisConnectionError() + RedisUnavailableError - 正確區分 Redis 連線錯誤(ECONNREFUSED/ETIMEDOUT 等)和指令錯誤(WRONGTYPE 等) - depth=3 遞迴檢查 wrapped error(ioredis errors[] / cause) - RedisUnavailableError 使用 Symbol.for() marker(ESM-safe) M1: Redis-first hybrid lock - store.ts: runWithFileLock() 優先用 Redis lock - RedisUnavailableError 時進 runWithFileLockCore() file-lock fallback - Symbol.for("RedisUnavailableError") in err 檢查(ESM-safe) N1: loadProperLockfile() 延遲 import() - proper-lockfile 改為動態 import(),解決 ESM interop 問題 N2: nodeTmpdir() 函式呼叫修正 - nodeTmpdir() 是 function call,不是 property access N5: ioredis error event listener - 註冊 error event listener,捕捉非同步連線錯誤 === 新增檔案 === - src/redis-lock.ts: Redis lock manager 實作(241 行) - test/redis-lock-error-types.test.ts: isRedisConnectionError 分類測試 - test/redis-lock-concurrent-init.test.ts: initPromise guard 測試 === 測試註冊 === package.json 新增 ioredis 依賴 npm test 已包含 redis-lock 測試(N3 hermetic guard,無 Redis 時 skip)
6a1dbf8 to
fc45722
Compare
Update: Branch Rebased — Single Squashed CommitBranch Commit: Diff 解釋(常見疑惑)PR 顯示 +1878 / -1351,這是單一 commit 的總合 diff,不是兩個 commit 累加。 store.ts 的變更是重構置換:
完整實作內容M2: M4:
M1: Redis-first hybrid lock
N1: 測試
Next Step稍後會執行 Codex adversarial review,確認是否還有需要修正的問題。 Generated by OpenClaw — 2026-04-27 |
Codex 對抗式 Review — 追加修復 (commit
|
3136188 to
fc45722
Compare
…ch#662) - C1: getRedisLockManager() add compare-and-swap + write redisLockManager after resolve - H1: add instanceof RedisUnavailableError guard before Symbol.for check - H2: document isRedisConnectionError depth=3 assumption - H4: remove pre-flight ping, let first SET() fail naturally (avoid TOCTOU)
|
已經被PR704取代,關閉此PR |
PR-A Implementation — M2/M4/N1~N5 for #662
This PR implements Phase A fixes from the rwmjhb review #4176883415.
Source branch:
jlin/fix/pr662-phase-a(fromjlin53882/memory-lancedb-pro)Target:
masterImplemented
getRedisLockManager()initPromise guard prevents concurrent client creationisRedisConnectionError()with recursive wrapped error check; connection errors throwRedisUnavailableErrorimmediatelyRedisLockManagerregisterserrorevent listener, sets_connectionErrorflagrequire()issueloadProperLockfile()uses delayedimport()instead of top-levelrequire()nodeTmpdirreference bugcreateFileLock()callsnodeTmpdir()(function) notnodeTmpdir(reference)runWithFileLock()tries Redis first,RedisUnavailableErrorfalls back torunWithFileLockCore()New Files
src/redis-lock.ts: new file with RedisLockManager, isRedisConnectionError(), RedisUnavailableErrortest/redis-lock-error-types.test.mjs: unit tests for isRedisConnectionError()test/helpers.test.mjs: skipIfNoRedis() helperKey Design Decisions
initPromiseguard: concurrent first-write callers share one init promise (not multiple clients)isRedisConnectionError(): depth=3 recursive check onerrors[]/causechainscreateFileLock()(lock-level, no retries) andrunWithFileLockCore()(store-level, 151s retries)err.constructor.namecheck forRedisUnavailableErrorin store.ts may need Symbol marker (see Codex review)Testing
node --check src/store.ts src/redis-lock.ts— both passMODULE_NOT_FOUNDis pre-existing on master, unrelated to this PRRemaining (PR-B)
MemoryStore.count()API compatibilityGenerated by OpenClaw — 2026-04-27