给FastAPI项目做单元测试时(app的lifespan挂载了异步redis),控制台打印了如下信息:
Exception ignored in: <function AbstractConnection.__del__ at 0x7fca80112700>
Traceback (most recent call last):
File "/home/grap/.cache/pypoetry/virtualenvs/stream-formatter-7CmN-bn6-py3.11/lib/python3.11/site-packages/redis/asyncio/connection.py", line 216, in __del__
self._close()
File "/home/grap/.cache/pypoetry/virtualenvs/stream-formatter-7CmN-bn6-py3.11/lib/python3.11/site-packages/redis/asyncio/connection.py", line 223, in _close
self._writer.close()
File "/usr/local/lib/python3.11/asyncio/streams.py", line 355, in close
return self._transport.close()
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/selector_events.py", line 860, in close
self._loop.call_soon(self._call_connection_lost, None)
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
self._check_closed()
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception ignored in: <function AbstractConnection.__del__ at 0x7fca80112700>
Traceback (most recent call last):
File "/home/grap/.cache/pypoetry/virtualenvs/stream-formatter-7CmN-bn6-py3.11/lib/python3.11/site-packages/redis/asyncio/connection.py", line 216, in __del__
self._close()
File "/home/grap/.cache/pypoetry/virtualenvs/stream-formatter-7CmN-bn6-py3.11/lib/python3.11/site-packages/redis/asyncio/connection.py", line 223, in _close
self._writer.close()
File "/usr/local/lib/python3.11/asyncio/streams.py", line 355, in close
return self._transport.close()
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/selector_events.py", line 860, in close
self._loop.call_soon(self._call_connection_lost, None)
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
self._check_closed()
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Exception ignored in: <function AbstractConnection.__del__ at 0x7fca80112700>
Traceback (most recent call last):
File "/home/grap/.cache/pypoetry/virtualenvs/stream-formatter-7CmN-bn6-py3.11/lib/python3.11/site-packages/redis/asyncio/connection.py", line 216, in __del__
self._close()
File "/home/grap/.cache/pypoetry/virtualenvs/stream-formatter-7CmN-bn6-py3.11/lib/python3.11/site-packages/redis/asyncio/connection.py", line 223, in _close
self._writer.close()
File "/usr/local/lib/python3.11/asyncio/streams.py", line 355, in close
return self._transport.close()
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/selector_events.py", line 860, in close
self._loop.call_soon(self._call_connection_lost, None)
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
self._check_closed()
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
其中fastapi服务main.py代码大致如下:
from contextlib import asynccontextmanager
from fastapi import FastAPI
from redis.asyncio import Redis
@asynccontextmanager
async def lifespan(app):
app.state.redis = Redis()
yield
await app.state.redis.aclose()
app = FastAPI(lifespan=lifespan)
conftest.py主要代码如下:
from pathlib import Path
import pytest
from asgi_lifespan import LifespanManager
from httpx import ASGITransport, AsyncClient
from main import app
@pytest.fixture(scope="session")
def anyio_backend():
return "asyncio"
@pytest.fixture(scope="session")
async def client():
async with LifespanManager(app) as manager:
transport = ASGITransport(app=manager.app) # type:ignore[arg-type]
async with AsyncClient(transport=transport, base_url="http://test") as client:
yield client
原因:跟这个类似Not waiting for connection_lost() when transport closed · Issue #1925 · aio-libs/aiohttp · GitHub
在conftest.py文件里,开头增加import asyncio,结尾增加如下几行:
origin_shutdown = asyncio.BaseEventLoop.shutdown_default_executor
async def _shutdown(self):
await origin_shutdown(self)
await asyncio.sleep(5)
asyncio.BaseEventLoop.shutdown_default_executor = _shutdown # type:ignore