news 2026/7/2 0:29:58

故障排查:Pytest Asyncio Event Loop Closed 错误

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
故障排查:Pytest Asyncio Event Loop Closed 错误

1. 问题描述

在运行RetrievalService的集成测试(使用pytest-asyncio)时,当连续运行多个异步测试用例时,遇到了以下错误:

RuntimeError: Task <Task pending ...> got Future <Future pending ...> attached to a different loop ... RuntimeError: Event loop is closed

症状

  • 第一个测试用例 (test_search_knowledge_base_flow) 成功通过。
  • 第二个测试用例 (test_search_knowledge_base_no_results) 在 setup 或执行阶段立即失败,抛出RuntimeError

出错的代码(原始版本)

这是在修复之前,导致错误的测试代码结构和db_sessionfixture:

# test/services/test_retrieval_service.py@pytest.fixtureasyncdefdb_session():""" Creates a new database session for testing. """# 错误发生点:直接调用 get_async_engine(),它返回的是一个被缓存的 Engine 实例# 这个 Engine 绑定到了创建它时的 Event Loop(即第一个测试的 Loop)engine=get_async_engine()async_session=async_sessionmaker(engine,expire_on_commit=False)asyncwithasync_session()assession:asyncwithengine.begin()asconn:awaitconn.run_sync(Base.metadata.create_all)yieldsessionawaitsession.rollback()# 测试函数 1:使用新创建的 Loop A,成功获取 Engine(绑定到 Loop A)@pytest.mark.asyncioasyncdeftest_search_knowledge_base_flow(db_session):# ... PASS ...# 测试函数 2:使用新创建的 Loop B# 这里的 db_session fixture 再次运行,但 get_async_engine() 返回的是# 绑定到已关闭的 Loop A 的旧 Engine。导致报错。@pytest.mark.asyncioasyncdeftest_search_knowledge_base_no_results(db_session):# ... FAIL with RuntimeError: Event loop is closed ...

2. 根本原因分析

2.1 冲突来源

该问题源于pytest-asyncio管理 Event Loop 的机制与我们应用程序创建 SQLAlchemy Engine 的方式之间存在冲突。

  1. Pytest-Asyncio 的行为:默认情况下(严格模式),pytest-asyncio会为每个测试函数创建一个新的asyncio Event Loop,以确保隔离性。
  2. 应用程序的行为:我们的src/configs/db.py使用了functools.lru_cache来缓存AsyncEngine实例:
    # src/configs/db.pyfromfunctoolsimportlru_cache@lru_cache()# <--- Engine 实例被缓存了defget_async_engine():""" Returns a cached async engine instance. The engine is created on the first call and reused on subsequent calls within the same event loop. """logger.info("Creating new async engine instance.")returncreate_async_engine(DATABASE_URL,pool_pre_ping=True,echo=False,)

2.2 事件序列

  1. 测试 1 开始
    • Pytest 创建Loop A
    • get_async_engine()被调用。它创建了Engine 1并将其绑定到Loop A
    • 测试 1 结束。Pytest 关闭Loop A
  2. 测试 2 开始
    • Pytest 创建Loop B
    • get_async_engine()再次被调用。
    • 由于有缓存(@lru_cache),它返回了Engine 1(这个 Engine 仍然绑定在已关闭的Loop A上)。
    • 当 SQLAlchemy 尝试使用Engine 1Loop B中连接数据库或执行查询时,失败了,因为 Engine 的内部组件(如asyncpg连接池)试图使用已关闭的 Loop A。

3. 解决方案

3.1 修复方法

我们需要确保为每个测试上下文创建一个新的 AsyncEngine,并绑定到当前由pytest-asyncio提供的 Event Loop。

我们在测试文件 (test/services/test_retrieval_service.py) 的db_sessionfixture 中修改了代码,在请求 Engine 之前显式清除缓存。

@pytest.fixtureasyncdefdb_session():""" Creates a new database session for testing. """# 修复:强制为当前 Event Loop 创建一个新的 Engineget_async_engine.cache_clear()engine=get_async_engine()# 现在返回的是绑定到当前 Loop 的新 Engine# ... fixture 的其余部分 ...

3.2 为什么有效

通过调用get_async_engine.cache_clear(),我们使缓存的AsyncEngine实例失效。随后的get_async_engine()调用会重新执行函数体,创建一个正确绑定到当前运行 Event Loop 的新AsyncEngine实例。

4. 替代方案(供参考)

  1. Scope 匹配:将event_loopfixture 的 scope 更改为session(所有测试共用一个 Loop)。这虽然降低了隔离性,但避免了多 Loop 问题。
  2. 依赖覆盖:如果使用依赖注入框架,可以覆盖get_async_engine依赖。
  3. 全局 Conftest:在conftest.py的 autouse fixture 中实现缓存清除,从而全局应用于所有测试。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 19:52:57

Miniconda-Python3.10结合FastAPI构建高性能Token API

Miniconda-Python3.10 结合 FastAPI 构建高性能 Token API 在 AI 模型服务化浪潮中&#xff0c;一个常见但棘手的问题是&#xff1a;如何让训练好的模型稳定、安全、高效地对外提供接口&#xff1f;尤其当多个团队协作、环境频繁切换时&#xff0c;“在我机器上能跑”的尴尬局…

作者头像 李华
网站建设 2026/7/1 20:03:11

I2S与DMA协同配置:简化数据传输入门

I2S与DMA协同配置&#xff1a;让音频数据“自己跑起来”你有没有遇到过这样的场景&#xff1f;在做一个语音采集项目时&#xff0c;MCU的CPU使用率一路飙升到80%以上&#xff0c;哪怕只是在录一段48kHz的立体声音频。系统变得卡顿&#xff0c;响应延迟&#xff0c;甚至开始丢帧…

作者头像 李华
网站建设 2026/7/1 9:46:25

基于WinUSB的JLink烧录驱动开发实战案例

从零构建JLink烧录驱动&#xff1a;用WinUSB穿透调试器的“黑盒”你有没有遇到过这样的场景&#xff1f;在产线批量烧录固件时&#xff0c;J-Link突然掉线、SDK报错却无从查起&#xff1b;或者想做个自动化测试平台&#xff0c;结果发现官方库不支持多设备并发控制&#xff1b;…

作者头像 李华
网站建设 2026/6/30 4:58:53

STM32 CANFD中断处理优化:高性能实时响应操作指南

STM32 CANFD中断处理优化&#xff1a;如何打造微秒级实时响应系统在工业自动化、智能驾驶和高可靠性嵌入式系统的开发中&#xff0c;通信的实时性与确定性往往直接决定整个控制系统的成败。传统CAN总线虽稳定可靠&#xff0c;但其8字节数据长度和最高1 Mbps的速率早已无法满足现…

作者头像 李华
网站建设 2026/6/30 0:55:09

Miniconda-Python3.10镜像在代码生成大模型中的实践

Miniconda-Python3.10镜像在代码生成大模型中的实践 在当前AI研发节奏日益加快的背景下&#xff0c;一个看似不起眼却影响深远的问题正困扰着无数开发者&#xff1a;为什么同样的训练脚本&#xff0c;在同事的机器上能顺利运行&#xff0c;到了自己环境里却频频报错&#xff1f…

作者头像 李华
网站建设 2026/6/30 3:35:44

Miniconda-Python3.10镜像助力高校AI实验室快速搭建平台

Miniconda-Python3.10镜像助力高校AI实验室快速搭建平台 在高校人工智能教学与科研一线&#xff0c;你是否经历过这样的场景&#xff1a;学生刚装好Python环境&#xff0c;却因版本不兼容跑不通示例代码&#xff1b;多个项目依赖冲突&#xff0c;“在我电脑上明明能运行”成了口…

作者头像 李华