两个 P0 挖到最后,结论是我们自己的测试写错了
一句话结论:两个 P0:并发一高就大面积报 500,派发路径少写一层目录导致产物落错地方。查根因时我们先后推翻了自己两次归因——一次是「客户端超时误报不安全」,一次是「Windows 句柄没释放」。最后真相是:一个是上游限流被当成内部错误,一个是测试脚本自己的场景设计有缺陷。
起因
测试跑出来两个 P0,都是那种看着像"系统有 bug"的问题:
第一个,并发一上来就开始大面积 500。并发 2、3 都好好的,到 5 就开始爆。更怪的是,同一个操作单独跑永远不出问题。
第二个,派发出去的活儿产物落到了错的地方。任务让 AI 处理某个目录下的文件,它把产物写到了隔壁目录,验收时找不到,判 0 分。
排查
第一个 P0:一路错到"安全机制误伤"
按常规套路查并发 500,先看是不是数据库锁、是不是数据竞争、是不是 panic。全都不是。
顺着错误链往上翻,找到了第一环:上游在限流,返回的是 429。而我们的适配层对 429 和 5xx 之外的错误一视同仁——原样往上抛,错误语义在这一层就丢了。语义丢了之后,下游那个同步调用点只能把所有错误一律映射成 500。
所以并发 5 触发限流 → 限流被当成内部错误 → 客户端看到 500。本来该告诉客户端"你太快了、稍后重试",我们说的是"我内部炸了"。
还有一个附带现象:某个请求超时后,后续请求开始连续失败。查下来是客户端断开把请求上下文取消了,取消信号一路传到安全分类器那儿,安全分类器把它读成了"语义分类超时",于是判定为不安全并拒绝后续请求。也就是说——用户自己点了取消,我们把它当成了安全事件。
第二个 P0:路径"看起来合法"就够了
这个更隐蔽。任务要求处理某个多层目录下的文件,AI 派发时把路径写成了少一层的短路径,产物就落到了隔壁目录。而验收找的是原目录,于是判 0 分。
我们原本的校验逻辑是这样的:检查路径的形态是否合法(不是绝对路径、不越出沙箱、不是注入)。那个少了一层的相对路径是完全合法的形态,形态检查一路通过。
"合法"和"落在对的地方"是两件事,而我们只查了前一件。
放大这个问题的是三个细节叠加:提示里只给了根目录名、测试题面传的是绝对路径所以 AI 自己做"相对化"时截掉了尾段、而路径前缀剥离规则里恰好包含一个会把层级吃掉的名字。
结论
第一个 P0 的修法是把错误语义接回去:限流映射成限流态、上游加退避重试、超时断开与"不安全"在语义上分开——客户端自己断开,不该被记成安全拒绝。修完之后,那批请求里的 500 归零。
第二个 P0 的修法是给路径加锚:派发时以任务里真实出现过的源目录为锚,声明的产物路径必须跟源文件同目录;多锚点不猜、不一致就改写并留痕。
两次推翻自己的归因
这轮排查里我们错了两次,都记一下:
第一次,超时后连续失败的现象,最初归因是"客户端超时导致请求上下文被取消"。这个方向没错,但它掩盖了更前面的一环——上游限流才是主因,超时只是表象。而且"取消"被内容安全检查读成"不安全",这层语义串味是独立问题,不该混在一起归因。
第二次,更彻底。测试里有一组"杀进程后重启"的场景连续失败,我们归因为"Windows 句柄释放滞后"——这个判断是错的。定向诊断证明:单独杀进程、停两秒,端口立刻就释放了,没有滞后。
真因在测试脚本自己身上:主实例从头到尾没死,测试脚本想测的是"杀掉再重启",但它启动的新实例压根绑不上端口(端口被主实例占着),起来就退出,于是脚本杀的是那个刚退出的小进程——端口的持有者从头到尾都是主实例。而"重启成功"的那几组,也是假绿:健康检查是被主实例应答的。
换句话说,这个 P0 有相当一部分,根本不是产品的问题,是我们写的那条测试用例本身有缺陷。
还没解决的
第一个 P0 里,安全分类器在并发下偶发超时,表现为限流态而不是内部错误——语义是对的,但它跟上游退避串起来之后,最坏耗时仍然会超过客户端的等待窗口。到底是放宽客户端窗口、把安全判定并行化,还是接受这个边界,没定。
第二个 P0 相关的另一个坑记下来了:产物目录提取那段逻辑写死了只认一个特定的锚点名,其他多层目录的产物契约取不到目录,会落到沙箱根。它和这次的 P0 是同一族,修的时候该一并泛化。
还有那条测试用例本身——场景设计得改成"先杀主实例",或者判据改成"确认被杀的进程确实不存在了",否则它会一直报假红。
本文由 Lyncore 生成,经人工审核后发布。来源:https://lyncore.top/journal/2026-11-03
上一条是稳定性测试报了 5 次崩溃,全是我们自己误杀。也看看一个 30 分钟任务连跑 13 轮。机制细节见异步双工。