pytest-asyncio 0.25.2 teardown 报 Event loop is closed:检查重复关闭
pytest-asyncio 0.25.2 清理已经被自定义 fixture 关闭的循环时,可能在 teardown 报错。核对 0.25.3 修复边界、循环所有权及新版 event_loop 迁移。
测试断言已经通过,pytest-asyncio 在 teardown 阶段却报 RuntimeError: Event loop is closed,堆栈落在插件清理异步生成器的位置。若刚从 0.25.1 升到 0.25.2,同时项目自己的 fixture 也关闭事件循环,应先检查这次版本变化和重复清理。
这里针对一个有明确版本边界的清理回归:0.25.2 开始在关闭循环前调用 shutdown_asyncgens,却没有先排除循环已由用户代码关闭的情况。0.25.3 增加了检查。测试正文、Playwright 或其他库运行过程中出现同样错误,不能仅凭文字相同归入这一分支。
堆栈在哪个阶段,比测试显示 passed 更关键
保留完整错误,核对失败属于 setup、测试运行还是 teardown,以及执行原命令的 Python、pytest 和 pytest-asyncio 版本。项目同时存在多个虚拟环境时,不以另一个终端里的版本输出替代 CI 或 IDE 使用的环境。
可以由实际运行测试的环境查看安装信息,以下是只读结构示例:
python -m pip show pytest pytest-asyncio
再检查 conftest.py 和共享 fixtures:是否获取了插件提供的旧 event_loop fixture,是否在 yield 之后执行 shutdown_asyncgens、shutdown_default_executor 或 loop.close,以及其他插件是否也负责关闭。
Python 的 close 操作不可逆,关闭后不能继续在该循环上执行清理协程。不是给已经关闭的循环再调用一次 run_until_complete,就能把资源清理补齐;也不能随手新建循环,期待原循环所属任务自动迁移过去。
处于 0.25.2 的明确分支,先评估包含修复的版本
历史 0.25 系列中的修复点是 0.25.3:插件在清理前检查循环是否已经关闭,并调整清理异常的处理。这解释了为什么某些自定义 teardown 在 0.25.1 正常、0.25.2 出错;不表示所有 Event loop is closed 都由这次回归产生。
项目仍锁定旧系列时,先保存依赖声明、锁文件、Python 与 pytest 要求,在隔离环境按项目依赖流程评估包含修复的版本。旧修复版本只能说明这项历史问题的边界,不能当成当前所有项目都应长期停留的版本建议。
准备升级到当前受支持系列时,应同时检查迁移要求。pytest-asyncio 1.0 已移除旧 event_loop fixture,照搬获取或重定义该 fixture 的代码,会遇到另一个不兼容问题。不要无条件升级全部测试依赖,随后继续保留旧循环管理方式。
保留资源清理,移除对插件循环的重复所有权
更长期的调整是明确谁创建和关闭循环。插件管理的循环通常由插件结束;应用 fixtures 在这个循环仍可用时关闭自己创建的客户端、连接、异步生成器和后台任务,而不是在每个 teardown 里再次关闭整条循环。
这不意味着删掉所有清理代码。需要取消并等待任务、关闭异步客户端的项目,应保留明确的资源释放顺序,并确保它发生在对应循环关闭之前。使用 asyncio.runners._cancel_all_tasks 这类下划线私有接口的清理方式,也不应作为面向任意 Python 版本的稳定方案照搬。
若 fixture 缓存了跨测试对象,要核对对象所属循环、fixture scope 与插件的 loop_scope。对象在一个循环创建,又在已关闭或不同循环使用,属于另一类生命周期问题,不保证升级这个补丁就能解决。迁移新版时应按其公开配置选择范围,不用覆盖插件内部函数来跳过检查。
不要把 teardown 错误过滤成成功
只过滤 RuntimeWarning 或捕获所有 RuntimeError,可能隐藏未关闭的客户端和仍在运行的任务。先确定是否为上述版本回归,再处理具体资源顺序;cleanup 中的取消异常与测试运行中的网络、数据库错误也要保留区别。
如果相同测试在 0.25.1 已经失败,或堆栈不在这个插件清理路径,应另查实际关闭方和对象复用,而不是反复降级到更旧版本。测试正文显示 passed,也不代表清理失败可以不影响整个测试任务的状态。
在原来的测试入口验证正常结束、测试抛异常及需要资源清理的场景,确认退出状态符合 CI 规则,不再因同一路径对已关闭循环执行清理。也要确认异步客户端与任务确实结束,不能只检查错误文字消失。
需要回退时,一起恢复依赖声明、锁文件和 fixture 变更,并重建对应受控环境。若旧版本只是暂时避免这次回归,仍应保留循环所有权整改计划;回退不会自动让错误的跨测试资源生命周期变得正确。


