现象
asyncio.wait_for 或 asyncio.timeout 抛出 TimeoutError;单个请求很快,并发时大量超时;事件循环像卡死一样,其他协程也停止推进;取消后出现 Task exception was never retrieved 警告。
诊断
先测量单次调用耗时:
python -X dev -c "import asyncio, time; from app.client import fetch; t = time.perf_counter(); asyncio.run(fetch()); print('elapsed', time.perf_counter() - t)"
PYTHONASYNCIODEBUG=1 python -m app.main 2>&1 | grep -Ei 'slow callback|took [0-9.]+ seconds|never awaited' | head -20
py-spy top --pid $(pgrep -f "python -m app.main")
若单次调用就超过阈值,问题在超时值或下游延迟;若日志显示回调阻塞事件循环,或在 py-spy 中看到大量时间停在 time.sleep、同步 socket 读取,则说明协程里混入了阻塞调用,所有任务的截止时间会一起到期。并发场景还要检查连接池大小与信号量,工作可能一直排队,请求根本没发出去。
修复
为每个网络调用设置连接、读取与总超时,用 asyncio.timeout 包住整个操作,阻塞库改走 asyncio.to_thread 或换成异步客户端;用信号量限制并发并与连接池匹配;只对幂等操作做带退避的重试,且不要用裸 except 吞掉 CancelledError:
async with sem:
async with asyncio.timeout(2.5):
response = await client.get(url)
response.raise_for_status()
return response.json()
验证
重新压测,确认正常流量下 p95 低于超时阈值且不再出现 TimeoutError;人为注入慢依赖,验证超时能按时触发、任务被干净取消、关闭时没有未取回异常。最后确认非幂等接口在超时后没有被重复写入。