ISSUE-035: event_balance 守恒报红 (实际根因是 ISSUE-036 NAK 路径事件丢失,非 cancel_timer 设计)
发现日期:2026-06-05
状态:已修复 (实际根因不是 cancel_timer 设计缺陷;修复 ISSUE-036 NAK 路径 let _ bug 后 16/17 测试自动恢复)
类型:误诊 — 原以为 Task 5 守恒模型定义不符 G5 kernel 实际语义,实际是 ISSUE-036 的 NAK 路径 on_ack 返回值丢弃在所有 DCQCN 拥塞场景的副作用
影响范围:G5 multi_chip 集成测试套 16/17 测试报 event_balance != 0 已全部消除。
实际根因澄清 (2026-06-05 ISSUE-036 调查后)
ISSUE-035 的"cancel_timer 设计缺陷"诊断是错的。 实际根因是 ISSUE-036 在 handle_ack_arrived NAK 分支 let (completions, _) = paxi.on_ack(...) 丢弃 RcFrameDone 事件,导致:
RcFrameDone永不调度 →on_tx_done永不触发 →tx_busy=true永久 → LG 死锁- kernel 事件队列里少了一批应有的 dequeued,events_enqueued > events_dequeued → event_balance 偏差
为什么 16/17 都报 event_balance:这些测试都是 allreduce / alltoall / clos 高拥塞场景,都会触发 DCQCN/NAK 路径,都让一批 RcFrameDone 丢失,event_balance 必然偏移。数值线性放大与 workload 规模相关也由此解释 (NAK 频度 ∝ 拥塞强度 ∝ workload)。
修复后实测 (ISSUE-036 修完):cargo test --release 从 360 passed / 17 failed 升到 377 passed / 0 failed / 3 ignored,所有 16 个 event_balance 报红测试自动恢复。
Lesson Learned:严格无容差守恒断言哲学的胜利 — 当时若按 Plan 选项 B 临时移除 event_balance from CONSERVATION_KEYS 绕过,ISSUE-036 这个真 bug 会继续静默存在。"伪红进去查不加容差" 真把根因逼了出来。
cancel_timer 机制本身不影响守恒 — Task 5 subagent 当时怀疑的 schedule_timer+cancel_timer 不平衡其实在正常路径上是平衡的 (timer 被 cancel 时仍走 pop 路径 +1 dequeued),只是被 ISSUE-036 NAK 路径丢的事件掩盖了。
历史诊断记录 (保留为 lesson 用)
以下是 2026-06-05 调查 ISSUE-036 之前的初步分析,已被推翻。保留供后续 review 学习"症状归因偏差"的典型案例。
问题现象
Phase 1 Task 6 把守恒 helper 套到 17 个集成测试结尾后,16/17 测试 panic,全部报 event_balance != 0,数值随 workload 规模线性放大:
| 测试 | event_balance 数值 |
|---|---|
test_relay_chip_2hop_p2p_completes | 2 |
test_switch_port_mapping_from_neighbors | 18 |
test_switch_incast_vs_no_contention | 23 |
test_switch_ecn_bidirectional_traffic | 110 |
test_switch_ecn_dcqcn_feedback | 119 |
test_switch_incast_3_to_1 | 133 |
test_clos_vs_direct_allreduce | 192 |
test_2port_allreduce_faster_than_1port | 552 |
test_cfs_ring_ar_completes_no_deadlock | 552 |
test_cfs_2port_faster_than_1port | 552 |
test_cfs_ring_ar_latency_not_less_than_chs | 552 |
test_2port_latency_varies_with_n | 764 |
test_clos_allreduce_8_chips | 920 |
test_clos_allreduce_32_chips | 15880 |
test_alltoall_data_conservation_no_frame_drop | 132026 |
数值范围 2~132026 与 workload 规模(chip 数 × message 数 × 时长)强相关,提示是正常事件流里的某一类事件没纳入 dequeued 计数。
调查过程
- [查代码]
perfmodel/evaluation/g5/src/kernel/sim_kernel.rsTask 5 插桩 5 个点:schedule()/schedule_at()/schedule_timer()→events_enqueued += 1run()内queue.pop()/pop_next()成功出队 →events_dequeued += 1
- [查代码]
schedule_timer()返回可 cancel 的 handle,可通过cancel_timer()将事件标为 cancelled。run()/pop_next()中 cancelled 事件被 pop 后continue,不计events_dequeued。 - [假设] Task 5 subagent 当时决策:cancel 路径不豁免,触发即"非自然终止进去查"。但实际通信场景中
schedule_timer+cancel_timer是合法常规事件流(如 DCQCN rate-block 超时取消、ACK timer 提前到达取消、retry timer 在 ACK 后取消等)——这些是设计预期,不是泄漏。 - [实验] 数值线性放大与"每个 frame 一个 timer + cancel"的预测吻合(132026 ≈ alltoall 16 chip × 3MB/pair 的 timer 数量级)。
根因分析
sim_kernel.rs 中 cancelled timer 弹出时被跳过,导致 events_enqueued > events_dequeued——但 cancel 本身是设计预期的合法事件流终止方式,不是泄漏。当前 event_balance = enqueued - dequeued 的定义把"cancelled timer 数量"算成"未处理事件",语义错误。
Plan v2 阶段曾在 review NEW-1 (MEDIUM) 标记 events_pending 定义模糊,Plan 写"G5 不支持事件 cancel,不需要例外口子",但该判断错误——G5 确实有 cancel_timer 机制。
Spec / 文档依据
无 G5 spec 锚定事件守恒模型(本 Phase 1 是首次引入)。
设计依据来自 docs/knowledge/04-软件工程实践/02-测试与验证方法论.md 守恒不变式章节,但守恒等式的具体边界(cancel 算不算合法 dequeue)需结合 G5 kernel 实际语义定义。
解决方案
备选方案
| 方案 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| A | cancel_timer 时 events_dequeued += 1(把 cancel 当作合法 dequeue) | 守恒等式回正,无场景豁免 | 失去了"cancel 是否被滥用"的探测能力 |
| B | 加 events_cancelled 第三个计数器,守恒等式改为 enqueued = dequeued + cancelled | 完整描述 kernel 状态,可单独追 cancel 量 | helper 需更新,3 类 stat 而非 1 类 |
| C | 把 event_balance 移出 CONSERVATION_KEYS,kernel 守恒作为可选指标不强制 | 不引入新代码 | 失去事件守恒兜底层探测,plan 设计意图丢失 |
采用方案
待 Phase 1.5 决策。倾向 B(完整描述 + 可单独追 cancel),但需先确认 cancel 是否真的全部"合法"。
涉及文件:
| 文件 | 改动说明 |
|---|---|
perfmodel/evaluation/g5/src/kernel/sim_kernel.rs | 按选定方案修计数逻辑 |
perfmodel/evaluation/g5/src/top/multi_chip.rs | collect_results 输出新 stat key |
perfmodel/evaluation/g5/src/top/multi_chip/test_conservation.rs | CONSERVATION_KEYS 列表更新 |
验证
Phase 1.5 修复后:
- 16 个原本报
event_balance != 0的测试应全部恢复绿(假设它们都是合法 cancel 导致) - 如果剩 N 个仍红,N 即真 bug 数量,逐个调查
遗留问题
- 修复时需对账:cancel_timer 是否真的全部"合法",有没有滥用路径(如忘了 cancel 导致超时反复触发)。如果有滥用,方案 A 会掩盖。
- Phase 2 蜕变测试(MR)上线后,可加一条"event_balance 数量 ∝ workload 规模"的标度性 MR 辅助验证守恒模型正确性。
- 同时调查 PSN gap 计数语义 (Phase 1 code-review F003 发现):
rc_link_rx.rs:186-191在 NAK 路径每次累加 gap,即同一 lost frame 触发 N 次 NAK 重发会被计 N 次。守恒断言psn_gaps == 0在含合法 GBN 重传场景下会 false-positive。17 个 failed 测试中是否有 PSN gap 路径污染需要确认。建议拆psn_gap_observed(信息性)vspsn_unrecovered_gaps(守恒断言用)。