Files
inkwatch/rust-core
PMon Dev e9c02c981e fix(退避): 严格单调断言偶发失败——是断言过严,不是 jitter 源码 bug
## 结论:断言过严(抖动实现本身正确,未改退避算法)
`apply_jitter_downward` 只向下扣 [0, capped*ratio],结果恒落在
[capped*(1-ratio), capped] 内,**绝不突破 max_ms** —— 上限这条硬承诺没被破坏,
抖动叠加方式也没有写错,不存在"源码 bug"那一路。
严格单调不成立是设计内的必然:封顶后 capped 恒为 60s,扣减量
offset = (now_ms() ^ capped.rotate_left(13)) % (span+1),每跨 1 毫秒 offset 就 +1,
于是后一个样本比前一个正好小 1ms(实测 59009 → 59008、50901 → 50900)。
原断言要求"相邻样本单调不减",等于要求这件不可能的事。

## 复现(真复现,非推测)
- 并行单跑该用例:48 次内命中
  `退避应单调不减: [... 57955, 57954, 57954 ...]`
- 变异回严格单调后,16 进程 × --test-threads=61 并发整包跑:第 1600 次命中
  `[... 50901, 50901, 50901, 50900 ...]`
单跑命中率约 1/1000(12 次调用里只有封顶区那段约 1µs 窗口可能跨毫秒),
这正是它"平时全绿、负载高才红"的原因。

## 改动
- transport.rs 主用例改按**档位表**断言(capped(n)=min(base*2^n, cap)):
  每个样本必须落在本档位抖动带 [level*(1-ratio), level];相邻样本回落不得超过
  一个抖动幅度(这才是"防退化"该守的不变量);末次必须真的顶进封顶带。
  注释写清为什么不能要求严格单调,防后人改回去。
- 新增 capped_backoff_dips_within_jitter_band:在封顶区**跨毫秒**采样,
  断言"必然观察到回落,且回落幅度 ≤ 一个抖动幅度",把"回落是正常行为"
  钉成可执行断言(而不是靠注释约定)。
- 新增 Backoff::base_delay_ms / max_delay_ms / jitter_ratio 只读访问器:
  测试不再重抄 1000 / 60_000 / 0.3,避免第二个真相源。
- buffer.rs 头注释 "±jitter" 与实现(只向下)不符,一并改正。

## 变异自证
1) 把 dips 用例的容差断言改成 `after >= before`(严格单调)→ 确定性变红 3/3,
   报文 `45363 → 45362`。
2) 把主用例容差断言改回 `w[1] >= w[0]` → 上述并发压测第 1600 次变红,
   特征与原报告一致;恢复后 500 次整包并发复跑全绿。

## 测试
改前 60 passed / 0 failed;改后 61 passed / 0 failed(净增 1 条)。
仅动 rust-core/src/buffer.rs 与 rust-core/src/transport.rs,server/ tests/ client/ deploy/ 未触碰。
2026-10-27 08:09:52 +08:00
..