You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

asyncio、pytest与SQLAlchemy交互的测试异常问题咨询

问题背景与疑问

我有一个Python 3.8程序,运行无限循环的协程,通过create_task调度后台协程(不等待),这些协程使用SQLAlchemy的AsyncEngine写入数据库。触发异常时,会取消循环协程并通过gather等待剩余后台任务,程序运行正常。

使用pytest和pytest-asyncio编写测试用例,通过打桩强制触发异常以执行gather逻辑。单个测试可通过,批量运行时先出现SQLAlchemy错误:cannot perform operation: another operation is in progress;关闭连接池后该错误消失,但批量运行过多测试时会超时,卡在await asyncio.gather(*outstanding_tasks),剩余任务处于pending状态。

疑问:

  1. 为何测试会共享AsyncEngine或其连接池?是否与同进程运行有关?
  2. 为何大量测试时部分任务无法调度?即使调高超时时间也无效。

注:测试用例为参数化,行为非确定性,偶尔全部测试可通过。


问题解答

1. 测试共享AsyncEngine/连接池的原因

是的,这完全和pytest的同进程运行机制有关。pytest默认在同一个Python进程中运行所有测试用例,如果你的AsyncEngine是全局初始化的(比如模块级变量、单例模式),所有测试都会复用同一个引擎实例及其连接池。

SQLAlchemy的AsyncEngine连接池本身是协程安全的,但批量测试时,前一个测试未完全关闭的连接可能被后一个测试复用,当连接还处于未完成的操作状态时,就会抛出cannot perform operation: another operation is in progress错误。即使手动关闭连接池,如果测试结束时没有彻底清理引擎实例(比如未调用await engine.dispose()),残留的连接状态依然会影响后续测试。

另外,pytest-asyncio默认会为每个测试创建新的事件循环,但如果引擎跨测试共享,不同事件循环中的协程尝试复用同一个连接池,也会导致冲突。

2. 大量测试时任务无法调度的原因

这种pending状态通常是因为后台协程被挂起且无法被唤醒,常见场景包括:

  • 连接池资源耗尽:即使关闭了连接池,如果测试中创建的后台协程持有未释放的数据库连接,连接池关闭后这些协程会一直等待获取连接,永远无法完成,导致gather持续阻塞。
  • 事件循环清理不彻底:pytest-asyncio批量测试时,可能存在事件循环残留任务的情况。前一个测试的未取消任务占用了事件循环的调度资源,导致新测试的后台任务无法被调度执行。
  • 参数化测试的并发冲突:参数化测试会快速创建大量测试实例,如果每个测试都生成大量后台任务,事件循环的调度队列可能被占满,部分任务无法及时调度;加上任务本身持有数据库连接等资源,就会陷入永久等待。
  • 协程取消处理不当:强制触发异常并取消循环协程时,如果后台协程没有正确捕获asyncio.CancelledError,可能会处于半终止状态,既不完成也不抛出异常,导致gather无法结束。

针对性建议

  • 每个测试用例独立初始化AsyncEngine,测试结束后立即调用await engine.dispose()彻底销毁引擎和连接池,避免跨测试共享。
  • 使用pytest.fixture的scope="function"级别管理引擎实例,确保每个测试拿到全新的引擎。
  • 后台协程中添加try/except捕获CancelledError,确保协程能正确退出,避免残留pending状态。
  • 批量测试时,限制每个测试的后台任务数量,或者在测试结束前主动取消所有未完成的后台任务,再执行gather等待。

内容的提问来源于stack exchange,提问作者danben

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:05:27