网络诊断基石:Ping命令参数详解与实战排错指南
2026/8/4 2:48:54
在运行RetrievalService的集成测试(使用pytest-asyncio)时,当连续运行多个异步测试用例时,遇到了以下错误:
RuntimeError: Task <Task pending ...> got Future <Future pending ...> attached to a different loop ... RuntimeError: Event loop is closedtest_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 ...该问题源于pytest-asyncio管理 Event Loop 的机制与我们应用程序创建 SQLAlchemy Engine 的方式之间存在冲突。
pytest-asyncio会为每个测试函数创建一个新的asyncio Event Loop,以确保隔离性。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,)get_async_engine()被调用。它创建了Engine 1并将其绑定到Loop A。get_async_engine()再次被调用。@lru_cache),它返回了Engine 1(这个 Engine 仍然绑定在已关闭的Loop A上)。asyncpg连接池)试图使用已关闭的 Loop A。我们需要确保为每个测试上下文创建一个新的 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 的其余部分 ...通过调用get_async_engine.cache_clear(),我们使缓存的AsyncEngine实例失效。随后的get_async_engine()调用会重新执行函数体,创建一个正确绑定到当前运行 Event Loop 的新AsyncEngine实例。
event_loopfixture 的 scope 更改为session(所有测试共用一个 Loop)。这虽然降低了隔离性,但避免了多 Loop 问题。get_async_engine依赖。conftest.py的 autouse fixture 中实现缓存清除,从而全局应用于所有测试。