Skip to content

fix: 消除两处 CI flaky 根因(锁心跳空窗口 + 测试锁 Worker 泄漏) - #29

Merged
YisfL merged 2 commits into
mainfrom
fix/v1.0.1-lock-heartbeat-atomic
Aug 23, 2026
Merged

fix: 消除两处 CI flaky 根因(锁心跳空窗口 + 测试锁 Worker 泄漏)#29
YisfL merged 2 commits into
mainfrom
fix/v1.0.1-lock-heartbeat-atomic

Conversation

@YisfL

@YisfL YisfL commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

背景

合并 #27main CI 出现两处独立的 flaky 失败,均为真实竞态而非合并引入的回归(重跑即过,但根因未除会反复假阴性)。

根因与修复

① shared-core 锁心跳空文件窗口(Shared Core ubuntu 覆盖率步骤偶发失败)

  • 现象:lock.test.js > 持锁方阻塞期间 token 仍被 Worker 心跳刷新 偶发 readToken 返回 null。
  • 根因:旧心跳用 ftruncateSync(fd,0)writeSync,两步之间留出空文件窗口,并发读者读到空 token 判损坏。
  • 修复:新增 rewriteTokenwriteSync(offset 0) 整体覆盖、后 ftruncateSync 精确截断),读者任一时刻只读到旧/新 token,绝不为空;Worker 与主线程回退路径统一改用。新增 2 个回归测试 + index.mjs ESM 垫片补导出。

② launcher 测试锁 Worker 泄漏(Launcher Test windows-latest 偶发失败)

  • 现象:qa3-store-atomic.test.js > writeState 成功写入后可 readState 读回 偶发 r.ok=false
  • 根因(本次新定位的真实根因):qa3-store-atomic.test.js / fix-batch3.test.jsacquireLock 成功后直接 releaseLock 却未传 refresh 句柄,锁心跳 Worker 线程未被停止(每 10s 仍写一次)。releaseLock 关闭 fd 后,后续测试打开文件可能复用同一 fd 号,泄漏的 Worker 把锁 token 写进该 fd → 覆盖 state.json 前 20 字节 → 读到损坏 JSON(确定性损坏,读重试无法恢复,故此前 fix: state 读路径瞬态 IO 有界重试,消除 Windows 防病毒 flaky(CI 假阴性) #28 的读重试并非此根因的解法)。
  • 修复:5 处 releaseLock 补传 refreshacquireLock 返回句柄),释放后 Worker 被同步停止,杜绝 fd 复用写入。

③ 内嵌 tgz 重打包

  • ① 的 vendor-shared 同步改变了 vendor/dseam-skillmcp/vendor-shared/fs/lock*.js,故 release/embedded/dseam-skillmcp-0.8.1-pre.tgz 同步重打包(check-embedded-tgz 逐字节一致,45 文件)。

验证

  • shared-core 178 测试通过(+2)、覆盖率行 90.95%(门槛 90)
  • launcher 523 测试通过、lint/depcheck 通过
  • vendor-shared 一致性、embedded tgz 一致性通过
  • 版本号维持 1.0.1(缺陷修复,不 bump)

根因:shared-core lock.test.js > 持锁方阻塞期间 Worker 心跳刷新 偶发 readToken 返回 null。旧实现 fs.ftruncateSync(fd,0) 后再 writeSync,两步之间留出空文件窗口,并发读者读到空 token 即判锁损坏(flaky 根因;覆盖率插桩放大时序窗口)。

修复:
- shared-core/fs/lock.js 新增 rewriteToken(先 writeSync(offset 0) 整体覆盖、后 ftruncateSync 精确截断),读者任一时刻只读到旧/新 token,绝不为空;写后截断保证 token 变短无残留。
- Worker 心跳(lock-heartbeat.js)与主线程回退路径(startHeartbeat 的 setInterval)统一改用 rewriteToken,消除非原子 truncate→write。
- index.mjs ESM 垫片补 rewriteToken(铁律 B 防漂移)。
- 新增 2 个回归测试:调用顺序(先写后截断)+ token 变短无残留。
- 同步 3 份 vendor-shared 副本(check-vendored-shared 逐文件字节一致)。

本地验证:shared-core 178 测试通过(+2)、覆盖率 行 90.95%(门槛 90)通过、vendor-shared 一致性通过、launcher 523 测试仍全绿。
@YisfL
YisfL requested a review from ARFCON as a code owner August 23, 2026 14:16
根因(launcher writeState→readState 偶发失败的真实原因):qa3-store-atomic.test.js / fix-batch3.test.js 中 acquireLock 成功后直接 releaseLock 却未传 refresh 句柄,锁心跳 Worker 线程未被停止(每 10s 仍写一次)。releaseLock 关闭 fd 后,后续测试打开文件可能复用同一 fd 号,泄漏的 Worker 把锁 token(pid\\ntimestamp\\n)写进该 fd → 覆盖 state.json 前 20 字节,readState 读到损坏 JSON → r.ok=false(非防病毒瞬态,重试无法恢复)。

修复:
- 5 处 releaseLock 补传 refresh(acquireLock 返回句柄),release 后 Worker 被同步停止,杜绝 fd 复用写入。
- 重打包 release/embedded/dseam-skillmcp-0.8.1-pre.tgz(vendor-shared 锁心跳同步后的必然产物,check-embedded-tgz 一致)。

本地验证:launcher 523 测试通过、lint/depcheck 通过、check-embedded-tgz 通过。
@YisfL YisfL changed the title fix: 锁心跳 token 原子重写(先写后截断),消除空文件窗口 flaky(CI 假阴性) fix: 消除两处 CI flaky 根因(锁心跳空窗口 + 测试锁 Worker 泄漏) Aug 23, 2026
@YisfL
YisfL merged commit cd6b9db into main Aug 23, 2026
11 checks passed
@YisfL
YisfL deleted the fix/v1.0.1-lock-heartbeat-atomic branch August 23, 2026 14:41
YisfL added a commit that referenced this pull request Aug 23, 2026
按需求将代码状态回退到 cd6b9db(PR #29 合并点,版本 1.0.1),撤销其后 4 个提交:
- f5d3ef6 feat(ui): 主页重设计为工业中控台 + 顶栏四分组导航
- 72a7373 build: 重编 exe/Setup 内嵌新 UI + 版本升级 1.0.3
- 0b12a5c build: 同步重建时刷新的 WebView2 运行 DLL
- 7fc5e53 build: 用协作方升级的 WebView2 DLL 重编 exe/Setup

force-push 被 main 分支保护拦截,故以 revert 提交实现(历史保留,树与 cd6b9db 逐字节一致)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant