目标
竞态之所以躲藏,是因为坏交错很稀有,而手工复现等于赌运气。能抓竞态的测试把运气工程化掉:压力循环抬高尝试次数、屏障把任务对齐到易损时刻、竞态检测器观察每个内存访问的调度。失败一旦确定化,当初暴露它的测试就成了修复的永久守卫。
步骤要点
- 写压力循环:200 个并发 increment 同时打一个计数器,断言终值恰为 200。
- 用 Barrier(2) 把两个写者对齐到可疑窗口,把侥幸时序变成确定性调度。
- 有检测器就跑 go test -race 或等价物:报告互相冲突的访问与两侧堆栈。
- 修复靠原子性而非时序:mutex 包住自增,压力测试从此每次必绿。
- 竞态守卫留在快速套件里:毫秒级的它每次提交都跑,而不是流放夜间。
排错提示
CI 能复现本地不能就加演员数或迭代数把失败率提到可靠检出水平,并固定随机种子;修复后压力测试仍抖,说明它断言的是时延而非不变量,改断言终态与一致性;运行时没有检测器时,屏障加大迭代压力循环依然是有效手段。
适用边界
压力测试证明存在竞态而非覆盖所有交错,检测器才能补上调度从未触达的部分;加同步有成本,串行化后要复测热路径。
代码示例
// Stress: many actors, one invariant
await Promise.all(Array.from({ length: 200 }, () => counter.increment()));
expect(counter.value).toBe(200);
// Barrier: align the adversaries at the window
const barrier = new Barrier(2);