From e590bea70ef5777aaec4a7ffb69b641666fc0652 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Tue, 11 Aug 2026 11:16:08 -0400 Subject: [PATCH] =?UTF-8?q?fix(soak):=20=E7=8E=AF=E5=A2=83=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E6=8A=8A=E4=BC=A0=E8=BE=93=E5=B1=82=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E4=B9=9F=E7=BF=BB=E6=88=90=20AppWorldError=EF=BC=8C?= =?UTF-8?q?=E3=80=8C=E8=BF=9E=E4=B8=8D=E4=B8=8A=E3=80=8D=E9=82=A3=E4=B8=80?= =?UTF-8?q?=E6=A1=A3=E5=8E=9F=E6=9C=AC=E6=B2=A1=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit execute 的 docstring 一直写着「只有环境自己坏了——连不上、HTTP 非 2xx、返回体不是约定 形状——才抛 AppWorldError」,但实现里只处理了后两档。连不上、读超时、连接中途断掉时, httpx 抛的是它自己的异常类型,而上层的动作执行接缝只认 AppWorldError,于是容器一挂整次 运行会以一个未捕获的第三方异常炸出去,正确的行为是记成环境故障、由库合成一段观察、以 env_error 收尾。 这个缺口是压测里真把容器 docker kill 掉之后才暴露的——在那之前它只写在 docstring 里。 顺带覆盖了另一个一直没验过的路径:客户端读超时同属这一类。 CancelledError 继承 BaseException,不在 httpx.HTTPError 的范围内,取消照旧原样穿过去。 修完重跑那一类,七条判据仍然全过。 Co-Authored-By: Claude Opus 5 (1M context) --- tools/soak/appworld.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tools/soak/appworld.py b/tools/soak/appworld.py index a381540..2bc1346 100644 --- a/tools/soak/appworld.py +++ b/tools/soak/appworld.py @@ -796,8 +796,21 @@ async def _post_json( path: str, body: Mapping[str, Any], ) -> Any: - """打一个 POST,检查状态码,剥掉 environment server 统一的 output 包装。""" - response = await client.post(f"{base_url}{path}", json=dict(body)) + """打一个 POST,检查状态码,剥掉 environment server 统一的 output 包装。 + + **传输层的失败也要翻成 `AppWorldError`。** 连不上、读超时、连接中途断掉,httpx 抛的是 + 它自己的异常类型,而上层的动作执行接缝只认 `AppWorldError`——不翻的话,容器挂掉会让整 + 次运行以一个未捕获的第三方异常炸出去,而正确的行为是记成环境故障、由库合成一段观察、 + 以 `env_error` 收尾。这个缺口是压测里真把容器 `docker kill` 掉之后才暴露的:在那之前 + 「连不上」这一档只写在上面那个 docstring 里,没有实现。 + + `asyncio.CancelledError` 不在 `httpx.HTTPError` 的范围内(它继承 `BaseException`), + 所以取消照旧原样穿过去。 + """ + try: + response = await client.post(f"{base_url}{path}", json=dict(body)) + except httpx.HTTPError as exc: + raise AppWorldError(f"{path} 的请求没能完成:{type(exc).__name__}: {exc}") from exc if response.is_error: raise AppWorldError(f"{path} 返回 HTTP {response.status_code}:{response.text[:2000]}") payload = response.json()