test(core): wait for cron lock probe takeover - #5535
Conversation
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Hi @tt-a1i — thanks for digging into this flaky test! The fix itself looks like a reasonable approach, but the PR body doesn't follow our required template.
The current body uses ## Summary / ## Test plan / ## AI Assistance Disclosure, but the template requires these sections:
## What this PR does## Why it's needed## Reviewer Test Plan(with### How to verify,### Evidence (Before & After),### Tested on)## Risk & Scope## Linked Issues
Could you reformat the body to match? The content is all there — it just needs to be under the right headings so reviewers can find it. Happy to re-run triage once it's updated.
中文说明
@tt-a1i 你好,感谢修复这个不稳定的测试!修复方案本身看起来合理,但 PR 正文没有按照我们的必填模板填写。
当前使用的是 ## Summary / ## Test plan / ## AI Assistance Disclosure,但模板要求以下章节:
## What this PR does## Why it's needed## Reviewer Test Plan(包含### How to verify、### Evidence (Before & After)、### Tested on)## Risk & Scope## Linked Issues
请把正文调整到对应的标题下,内容都在,只是需要放到正确的位置方便审查。更新后可以重新触发 triage。
— Qwen Code · qwen3.7-max
wenshao
left a comment
There was a problem hiding this comment.
Verified — a clean, well-scoped flaky-test fix. The waitFor-wrapped tick correctly handles the race where the lock file write is visible before the probe's .then flips isOwner, and the exact .toEqual(['probe-job']) is safe because the scheduler's lastFiredAt dedup (skips slots ≤ lastFiredAt) prevents the repeated ticks from double-firing. Ran the target test 5× (all green, no flakiness) and the full cronScheduler.test.ts (89/89); CI is green. LGTM! ✅
— claude-opus-4-8 via Qwen Code /qreview
|
@qwen-code /triage |
|
Re-running triage per maintainer request. Template was already flagged in the prior review — the maintainer's approval overrides that, so I'm moving past it. Quick note for @tt-a1i: next time, please use the PR template headings ( On direction: this is a focused fix for a documented CI flake in the cron scheduler lock probe test. Flaky tests erode CI trust — fixing them is always welcome. On approach: the change is +6/-4 in a single test file, targeting exactly the race described in the issue. No scope creep, no drive-by refactors. The right size for the problem. Moving on to code review. 🔍 中文说明按维护者要求重新跑 triage。 模板问题在上一轮 review 中已经提过了,维护者已批准,所以跳过。@tt-a1i 提醒一下:下次请用 PR 模板 的标题( 方向:这是针对 已记录的 CI 不稳定问题 的修复,cron scheduler 锁探针测试的竞态条件。不稳定测试会削弱 CI 信任度,修复总是受欢迎的。 方案:改动仅 +6/-4,集中在单个测试文件中,精确针对 issue 描述的竞态条件。没有范围蔓延,没有附带重构。大小合适。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewThe race condition is real and the fix is correct. Here's what's happening: In The fix wraps The assertion change from No correctness issues, no security concerns, no AGENTS.md violations. Clean, minimal fix. Test ResultsRan the specific test 5 times with the PR applied (Linux): All 5 runs pass consistently. The flake is gone. Note: this is a test-only fix with no user-visible behavior change, so before/after tmux comparison is N/A — the verification is the test itself. 中文说明代码审查竞态条件是真实存在的,修复是正确的。问题如下: 在 修复方案将 断言从 无正确性问题,无安全隐患,无 AGENTS.md 违规。干净、最小化的修复。 测试结果在 Linux 上用 PR 代码跑了 5 次特定测试,全部通过,不稳定问题已消除。 注:这是纯测试修复,无用户可见行为变化,因此前后对比 N/A——验证就是测试本身。 — Qwen Code · qwen3.7-max |
|
Straightforward call. The fix targets a well-understood race condition (lock file visible before No reservations. Approving. 中文说明简单的判断。修复针对一个理解清楚的竞态条件(锁文件在 没有顾虑,批准。 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
✅ Local verification — real test runs under tmux (merge reference)Verdict: verified. The change correctly removes the documented race, is robust under a deterministic reproduction of it, and introduces no collateral. Safe to merge. I verified this in a clean worktree checked out at the PR head ( Root cause recap (issue #5534)In the lock-probe takeover path ( void tryAcquireLock(...) // writes the lock file → immediately visible to a reader
.then((acquired) => {
if (acquired && !this.isOwner) {
this.isOwner = true; // ownership flips only in this .then continuation
...
}
});
Results
Run B confirms the flake is timing/platform-specific (it was reported on macOS CI) and does not surface on this Linux box — which is exactly why a deterministic reproduction was needed for a conclusive A/B. Decisive A/B — deterministic reproduction of the raceBecause the race is too narrow to hit naturally on Linux, I widened only the documented window in the scheduler (the gap between "lock file written" and " // packages/core/src/services/cronScheduler.ts — TEMPORARY widening, reverted before merge
// realSetTimeout is captured at module-eval, before any vi.useFakeTimers(), so it is
// immune to the fake clock and the await lands in real-timer territory.
- .then((acquired) => {
+ .then(async (acquired) => {
if (generation !== this.durableGeneration) { ...; return; }
+ // lock file already written above (visible to the test's reader); defer the
+ // owner flip past the test's immediate tick using a REAL macrotask.
+ await new Promise((r) => realSetTimeout(r, 60));
if (acquired && !this.isOwner) {
this.isOwner = true;
This isolates the change: under an identical, deliberately-widened race, the old assertion pattern breaks and the new one holds. Other checks (PR head, pristine)
Notes
Environment
🇨🇳 中文版(点击展开)✅ 本地验证 —— tmux 下真实测试运行(合并参考)结论:已验证。 此改动正确消除了所述竞态,在对该竞态的确定性复现下表现稳健,且无任何副作用。可以安全合并。 我在 PR HEAD( 根因回顾(issue #5534)在锁探测接管路径( 旧测试先等待锁文件显示 结果
Run B 表明该 flaky 是与时序/平台相关的(原报告出现在 macOS CI 上),在本 Linux 机器上不会出现 —— 这正是需要做确定性复现来给出决定性 A/B 对比的原因。 决定性 A/B —— 对竞态的确定性复现由于该竞态在 Linux 上窗口太窄、无法自然命中,我只放大了所述的那个窗口(“锁文件已写入” 与 “ // packages/core/src/services/cronScheduler.ts —— 临时放大,合并前已还原
// realSetTimeout 在模块求值期(任何 vi.useFakeTimers() 之前)捕获,因此不受 fake clock 影响,
// 该 await 落在真实定时器区间。
- .then((acquired) => {
+ .then(async (acquired) => {
if (generation !== this.durableGeneration) { ...; return; }
+ // 上面 tryAcquireLock 已写入锁文件(对测试的读取方可见);用一个真实宏任务把
+ // 所有权翻转推迟到测试的同步 tick 之后。
+ await new Promise((r) => realSetTimeout(r, 60));
if (acquired && !this.isOwner) {
this.isOwner = true;
这就把改动的效果隔离出来了:在同一个被刻意放大的竞态下,旧断言写法必然失败,新写法依然成立。 其他检查(PR HEAD,原始状态)
备注
环境
|
✅ Local verification report — cron lock-probe takeover flaky-test fix (#5535)
Environment
1. The PR's stated test plan — reproduced, green
2. The merged (fixed) test is stable
3. The old race is real but extremely rare (couldn't reproduce naturally)The flake (#5534, seen on macOS CI) is: the probe writes the lock file (visible on disk) and only afterwards flips In the takeover path,
So on Linux the 4. Deterministic reproduction — the conclusive proofSince the natural race won't surface on demand, I reproduced the exact race deterministically by wrapping the real
This shows the fix is not a no-op: under the race it changes a hard failure into a correct pass. Scope assessment (why test-only is the right fix)The change is test-only and does not alter the production race — correctly. In production the brief VerdictConfirmed good. The merged test waits for the real takeover instead of treating the visible lock write as proof of settled ownership, asserts a single fire, and is stable across 2700+ local iterations; a deterministic injection of the exact race proves the old assertion would fail and the new one holds. The merge was sound. 🇨🇳 中文版验证报告(点击展开)✅ 本地验证报告 —— cron lock-probe takeover 不稳定测试修复(#5535)
环境
1. PR 自述测试计划 —— 复现,全绿
2. 合并后(修复后)的测试稳定
3. 旧的竞态真实存在但极其罕见(无法自然复现)该不稳定(#5534,macOS CI 上出现)的本质是:probe 先写入 lock 文件(磁盘上可见),之后才在它的 在 takeover 路径中,
所以在 Linux 上 4. 确定性复现 —— 决定性证据既然自然竞态无法按需出现,我通过包装真实的
这表明修复并非空操作:在竞态下它把一次硬失败变成了正确的通过。 范围评估(为何"仅测试"是正确的修复)此改动仅涉及测试,且不改动生产竞态 —— 这是对的。生产中那短暂的 结论确认无误。 合并后的测试等待真正的 takeover,而不是把可见的 lock 写入当作所有权已稳定的证据;断言单次触发;在 2700+ 次本地迭代中稳定;对该竞态的确定性注入证明了旧断言会失败、新断言成立。此次合并是稳妥的。 Verification method: merged-vs-base worktree; PR test plan reproduced (89/89 + prettier + git diff); merged test 300× in-process; pre-fix test 2400× under 8× CPU + 16× concurrent load (race too rare to surface); deterministic injection of the exact "lock visible before isOwner flips" race showing the old single-tick assertion fails and the new waitFor-tick fires exactly once. |
Fixes #5534
Summary
Test plan
AI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.