fix(runtime): verify mutation lock publication - #5089
huangruiteng merged 2 commits into
Conversation
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Duang777
left a comment
There was a problem hiding this comment.
Exact-head self-review for 1f439b0: APPROVE for maintainer review. Lock acquisition now verifies the owner record after publication before entering the critical section, and the regression test covers replacement during the create-to-publish window. All branch-specific CI checks passed. The remaining installed-artifact failure is the main-branch packaging defect tracked by #5096.
…on-lock-publish-race Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Duang777
left a comment
There was a problem hiding this comment.
Exact-head self-review for f1c63291f20ec3d62ebe0408fc82e7bfde716931: ready for maintainer review. This head merges origin/main@eaa0c0fd089e5844b763cc24132dcf8a3881c39b without conflicts and retains only the mutation-lock publication fix plus its regression test. Local validation passed: 5 focused lock tests, TypeScript type-checking, docs governance, and the standard premerge gate with 10 checks.
|
CI triage for exact head |
huangruiteng
left a comment
There was a problem hiding this comment.
APPROVE — 未发现本 head 的阻塞问题;锁被替换时拒绝旧创建者进入临界区,竞争锁释放后仍能恢复真实写入,均已独立验证。
Reviewed head: f1c63291f20ec3d62ebe0408fc82e7bfde716931
Compared base: eaa0c0fd089e5844b763cc24132dcf8a3881c39b
动机
File mutation lock 的创建与 owner 发布之间有空文件窗口:旧创建者暂停时,另一进程可以按既有 stale-lock 规则回收并重新持锁。旧创建者之后向已被 unlink 的 inode 写入 owner,若仍把“写完并 close”当作获取锁成功,就会在另一进程持锁时执行修改。要修的是这一已证明的写入安全缺口,同时不能把安全拒绝变成无法恢复的持续停工。
改动思路
仍在既有 TypeScript IO owner 内校验,不另建 lock 状态或 provider。发布后,当前路径必须仍指向创建时捕获的文件身份,并且路径中的 PID/token 必须等于当前 owner;读 owner 前后都核对文件身份。校验失败时只清理自己创建的 inode,再沿原 deadline 重试或返回既有 typed timeout。这样既保留竞争者的锁,也不扩大成 Goal 全局门禁。
具体改动
2 个文件,65 additions / 1 deletion:生产 IO 29 行增加,聚焦测试 36/1。没有改公开函数签名、锁文件 owner 格式、默认 timeout、持久 receipt 或 FileAuthorityStore CAS 协议。
关键代码讲解
publishedMutationLockMatches(59–68):identity → PID/token → identity;阻止 owner 读取前后路径切换被误认成自己的 publication。acquireFileMutationLock(298,新增 337–352):publication 不匹配时调用原removeCreatedFile,其身份检查不能删除竞争进程的 replacement;deadline 到期使用原EffectRuntimeLockTimeoutError,否则重新获取。withFileMutationLock(418–429)及FileAuthorityStore.commitAuthority(341):只有 acquire 成功才执行实际提交;原有 finally 清理仍不能覆盖业务错误或 durable outcome。
我另写了隔离真实 File 的双 Node 进程反例,暂停在真实 wx/fstat 之后、owner 写入之前;另一进程回收空锁并持有 replacement,再恢复旧创建者。相同夹具下,base 错误 applied 并生成 authority head;head 返回 provider_lock_timeout,head 仍 missing、竞争锁保持不变。释放竞争锁后,原 operation 重新提交成功,另一实例继续提交到 revision 2,receipt 能读回;另一个文件路径也能独立推进。这是实际 FileAuthorityStore 写入和重新读回,不是只替换 mock 返回值。
对主干的风险
共享 IO 的调用者包括 File authority、effect handlers、协调与 journal 写路径,因此我检查了 unchanged callers,而非只读新增 helper。321 个 Node 测试覆盖 IO、File provider conformance 和 post-writeback transaction,77 个 Python effect integration/restart/request-scope 测试通过;control-plane typecheck、docs governance、maintainability、diff check 和 standard premerge 的 3 个 direct / 10 个 selected checks 都通过。
语义与 CI 对齐
当前 capability 要求本地验证,不获取、轮询或等待远端 CI。额外 uv run --extra test python examples/repository-hygiene-smoke.py 在上述不可变 base/head 均失败于同一 validate_release_timeline 的缺失版本 v1.2.0;本 PR 不改该 release/docs 路径,且 changed invariant 已有独立通过证据。因此归因为 pre_existing_unrelated,不以此 request changes,也不要求这个 PR 修 unrelated release 数据。它应由仓库 release/hygiene owner 处理,合并准备状态另行判断;这里没有声称整个 CI 全绿。
剩余验证边界:本次在 macOS 实际文件系统验证了竞态和恢复,没有跑 Windows 文件身份分支或全部平台矩阵。该补丁也不是抵御任意非协作进程持续篡改路径的通用 filesystem-CAS 保证。PostgreSQL provider 不走这个 File lock helper,本次不是影响 PostgreSQL 的 authority-store refactor,未把未运行的 PG 集成冒充已验证。
我的整体评价
范围与原缺陷相称:用一个私有验证 seam 补足现有 lock acquisition,而非引入新的状态机、超时策略或权限模型。未来维护 pass 已落实在“身份与 owner 发布核对”这一窄边界,当前不需要更大抽象。正常路径、竞争者保护、失败后真实恢复、后续提交与 durable readback 都验证了,故 approve;批准评审不等同于绕过仓库合并策略。
English verdict: APPROVE.
Summary
Root cause
A process could pause after
open(..., "wx")created an empty lock file but before owner JSON was written. After the malformed-owner stale interval, another process could reclaim that inode and publish its own lock. The original process would then finish writing its unlinked inode and incorrectly return success, allowing two FileAuthorityStore mutations to enter the critical section.Validation
node --no-warnings --experimental-strip-types --test tests/control_plane_ts/effect_runtime_io.test.ts(5 passed)npm run typecheck:control-planenpm run test:control-plane(3079 passed, 30 skipped, 0 failed)python -m loopx.cli canary premerge --from-git-diff --tier standard --format json --no-progress(passed, 10 selected checks)python examples/docs-governance-smoke.pyKnown baseline failure
python examples/repository-hygiene-smoke.pyreports the existing missing release timeline entry forv1.2.0; the same failure reproduces on unchangedorigin/main.Merge policy
Runtime locking behavior change. Maintainer review and merge required; do not self-merge.