FastAPI使用nest_asyncio.apply()时GitHub Action pytest无法自动终止
问题原因
nest_asyncio.apply()会修改Python asyncio原生事件循环的实现逻辑,解除事件循环不可嵌套运行的限制,同时会改变事件循环的默认退出判定规则。
- 本地运行pytest时,交互终端环境会在测试进程退出前隐式回收残留的事件循环资源,因此进程可以正常退出。
- GitHub Actions是无交互CI环境,被nest_asyncio修补过的事件循环会残留对标准IO等文件描述符的事件监听,pytest执行完所有用例后会持续等待事件循环任务完全清空,进程不会主动返回退出码,表现为步骤持续运行无响应。
- 移除
nest_asyncio.apply()后事件循环恢复原生逻辑,pytest可正常完成事件循环回收,因此流程能正常结束。
修复方案
可根据业务场景选择以下方案,优先推荐前两种:
方案1:添加pytest会话级清理逻辑
在项目根目录新建conftest.py,添加如下代码,所有测试用例执行完成后手动取消残留任务、关闭事件循环,彻底解决阻塞问题:import asyncio import pytest @pytest.fixture(scope="session", autouse=True) def cleanup_event_loop(): yield loop = asyncio.get_event_loop() # 取消所有未执行完成的残留任务 pending_tasks = [t for t in asyncio.all_tasks(loop) if not t.done()] for task in pending_tasks: task.cancel() loop.run_until_complete(asyncio.gather(*pending_tasks, return_exceptions=True)) # 停止并关闭事件循环 loop.stop() loop.close()注意原
test_main.py缺少TestClient的导入,需要补充,否则运行时会抛出NameError:# test_main.py 修正后 from fastapi.testclient import TestClient from .main import app client = TestClient(app) def test_hello(): res = client.get('/hello') assert res.status_code == 200方案2:调整nest_asyncio补丁的生效范围
不要在main.py模块顶层直接调用nest_asyncio.apply(),将这行代码移动到实际需要嵌套事件循环能力的业务函数内部,仅在业务逻辑执行时打补丁,避免补丁影响测试框架的事件循环逻辑:# main.py 调整示例 from fastapi import FastAPI import nest_asyncio app = FastAPI() def business_func_need_nest_loop(): # 仅在实际需要时才应用补丁 nest_asyncio.apply() # 原有调用asyncio.get_event_loop()的业务逻辑 ... @app.get('/hello') def hello(): return {'msg': 'hello'}方案3:为pytest命令增加超时限制(临时方案)
若暂时不想修改业务代码,可直接修改GitHub Actions工作流中的pytest执行命令,增加超时阈值强制结束进程,超时时间设置为大于所有测试用例的预估执行时长即可:# python-app.yml 中测试步骤修改示例 - name: Test with pytest run: | timeout 60 pytest该方案无法区分正常残留和测试用例死锁,仅适合临时快速修复场景。
内容的提问来源于stack exchange,提问作者Angie
相关产品推荐
相关产品推荐

