Rust 异步编程与 Tokio 运行时:灰度阶段到底验证什么
练习中的“灰度”是让新实现只处理固定测试文件,旧实现仍是对照。我要比对的是结果内容和错误类别,而不是只看进程有没有退出。
tool parse fixtures/a.md > new.out
diff -u expected.out new.out
遇到差异先保留 fixture,再定位是哪条规则变了。不要把真实请求镜像到调试服务,也不要在对照输出里留下个人数据。这个办法适合离线工具,不等同于真实流量灰度。
先约定新旧实现各自负责什么
Tokio 项目里的灰度,不必一开始就做成复杂的流量切分。对解析器、转换器或批处理工具,更实用的起点是挑一组固定输入,让新实现和旧实现分别运行。输入要覆盖正常文件、空文件、格式不完整的文件以及预期会失败的文件。新路径只读取这批 fixture,旧路径继续作为基准。这样出现差异时,问题能落到一个可重复的文件和一条命令上,而不是落到某次不可复现的线上请求。
“结果相同”也要说清楚含义。若输出是 JSON,字段顺序可能并不影响语义,直接逐行 diff 会制造噪声;若输出是诊断信息,错误类型、位置和是否可继续处理反而比错误文案更重要。先为每类工具列出需要稳定比较的部分,再决定是比较文本、结构化字段,还是退出码加错误类别。没有这一步,灰度很容易变成每次 diff 都有差异、却没人知道该不该修。
Tokio 中要观察任务是否真正结束
异步程序的一个常见误判是:主函数返回了,就以为所有工作都结束了。任务可能还在等待 channel,某个 JoinHandle 也可能从未被等待。灰度阶段应当把任务完成和资源回收列为独立检查项。对于一批固定文件,可以记录每个文件对应任务的开始、成功、失败和取消状态;运行结束时,确认没有遗留的发送端让接收循环永久等待,也没有因错误分支绕过清理。
超时设置同样需要被纳入对照。测试环境里一个永远不返回的任务会让命令卡住,给单个操作设定有限等待时间,能把“程序无响应”变成可分类的失败。超时不是把错误藏起来:超时后应该保留对应 fixture、任务名称和已经完成的步骤,以便判断是等待外部资源、锁竞争,还是逻辑漏掉了完成信号。若工具本来就需要长时间处理大文件,阈值应来自它的正常行为,而不是随手写一个很短的数。
差异出现后按输入、边界和取消路径排查
当新旧输出不一致时,我不会先改新实现去贴近旧输出。先确认 fixture 是否相同、读入的编码和换行符是否一致,再检查两边对边界输入的定义。有时旧实现把空字段当作缺失,新实现保留为空字符串;有时一个版本遇到一条错误就中断,另一个版本会收集后续错误。两种行为未必谁对谁错,但必须明确写进预期结果,不能让测试只依赖偶然的历史输出。
异步场景还要单独验证取消路径。任务收到停止信号时,已经写入一半的临时文件如何处理,已发出的消息由谁消费,等待中的子任务怎样退出,这些都应在小规模 fixture 上先跑通。对文件工具来说,宁可让一次任务明确失败并留下可诊断的错误,也不要生成看似成功、实际只写了一半的结果。
最后,灰度目录本身也需要收口。fixture 和预期输出可以留在版本库中,但其中不应混入真实请求、账号信息或调试时抓到的原始内容。把可公开的最小输入沉淀下来,之后重构 Tokio 调度、调整并发度或替换解析库时,才有一组真正能拿来回归的对照样本。

550

被折叠的 条评论
为什么被折叠?



