Skip to content

fixture teardown loop closed #1200

@DragonRollDev

Description

@DragonRollDev

Hi, I'm using versions:

Python 3.12
"pytest>=8.4.1",
"pytest-asyncio>=1.1.0",

I'm trying to create a fixture for TortoiseORM to upgrade the database before running the tests and downgrade it after the test through aerich, but it seems to me that I'm facing a problem due to incorrectly loop closed.

the boilerplate code looks like this:

@pytest.fixture(scope="session")
async def sanic_app():
    from aerich import Command
    from social_backend.app.db_session import TORTOISE_CONFIG, close_db, init_db
    await init_db()
    async with Command(tortoise_config=TORTOISE_CONFIG, app="models") as command:
        await command.upgrade()
    from social_backend.service_core import app

    yield app
    async with Command(tortoise_config=TORTOISE_CONFIG, app="models") as command:
        await command.downgrade(-1, False)
    await close_db()

I've debugged, and the command.downgrade only issues an "drop table" command to the postgres connector asyncpg>=0.30.0.

There are two situations:

  1. Endpoint doesn't do anything DB related, then this setup works perfectly, the downgrade also is issued correctly
  2. Endpoint does anything DB related, like creating an user, the downgrade fails

Here's the full stacktrace of the error:

=================================== ERRORS ====================================
________________ ERROR at teardown of test_register_and_login _________________

self = <asyncpg.pool.Pool object at 0x00000265225CA800>

    async def close(self):
        """Attempt to gracefully close all connections in the pool.
    
        Wait until all pool connections are released, close them and
        shut down the pool.  If any error (including cancellation) occurs
        in ``close()`` the pool will terminate by calling
        :meth:`Pool.terminate() <pool.Pool.terminate>`.
    
        It is advisable to use :func:`python:asyncio.wait_for` to set
        a timeout.
    
        .. versionchanged:: 0.16.0
            ``close()`` now waits until all pool connections are released
            before closing them and the pool.  Errors raised in ``close()``
            will cause immediate pool termination.
        """
        if self._closed:
            return
        self._check_init()
    
        self._closing = True
    
        warning_callback = None
        try:
>           warning_callback = self._loop.call_later(
                60, self._warn_on_long_close)

.venv\Lib\site-packages\asyncpg\pool.py:931: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:765: in call_later
    timer = self.call_at(self.time() + delay, callback, *args,
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:778: in call_at
    self._check_closed()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <ProactorEventLoop running=False closed=True debug=False>

    def _check_closed(self):
        if self._closed:
>           raise RuntimeError('Event loop is closed')
E           RuntimeError: Event loop is closed

..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:545: RuntimeError

During handling of the above exception, another exception occurred:

    def finalizer() -> None:
        """Yield again, to finalize."""
    
        async def async_finalizer() -> None:
            try:
                await gen_obj.__anext__()  # type: ignore[union-attr]
            except StopAsyncIteration:
                pass
            else:
                msg = "Async generator fixture didn't stop."
                msg += "Yield only once."
                raise ValueError(msg)
    
>       runner.run(async_finalizer(), context=context)

.venv\Lib\site-packages\pytest_asyncio\plugin.py:289: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\runners.py:118: in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:691: in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
.venv\Lib\site-packages\pytest_asyncio\plugin.py:281: in async_finalizer
    await gen_obj.__anext__()  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
social-tests\src\conftest.py:47: in sanic_app
    await all_exec()
social-tests\src\conftest.py:43: in all_exec
    await downgrade()
social-tests\src\conftest.py:37: in downgrade
    async with Command(tortoise_config=TORTOISE_CONFIG, app="models") as command:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv\Lib\site-packages\aerich\__init__.py:163: in __aenter__
    await self.init()
.venv\Lib\site-packages\aerich\__init__.py:160: in init
    await Migrate.init(self.tortoise_config, self.app, self.location)
.venv\Lib\site-packages\aerich\migrate.py:108: in init
    await Tortoise.init(config=config)
.venv\Lib\site-packages\tortoise\__init__.py:472: in init
    await connections.close_all(discard=True)
.venv\Lib\site-packages\tortoise\connection.py:197: in close_all
    await asyncio.gather(*tasks)
.venv\Lib\site-packages\tortoise\backends\base_postgres\client.py:114: in close
    await self._close()
.venv\Lib\site-packages\tortoise\backends\asyncpg\client.py:81: in _close
    await asyncio.wait_for(self._pool.close(), 10)
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\tasks.py:520: in wait_for
    return await fut
           ^^^^^^^^^
.venv\Lib\site-packages\asyncpg\pool.py:943: in close
    self.terminate()
.venv\Lib\site-packages\asyncpg\pool.py:964: in terminate
    ch.terminate()
.venv\Lib\site-packages\asyncpg\pool.py:253: in terminate
    self._con.terminate()
.venv\Lib\site-packages\asyncpg\connection.py:1515: in terminate
    self._abort()
.venv\Lib\site-packages\asyncpg\connection.py:1567: in _abort
    self._protocol.abort()
asyncpg\\protocol\\protocol.pyx:607: in asyncpg.protocol.protocol.BaseProtocol.abort
    ???
asyncpg\\protocol\\coreproto.pyx:1205: in asyncpg.protocol.protocol.CoreProtocol._terminate
    ???
asyncpg\\protocol\\protocol.pyx:967: in asyncpg.protocol.protocol.BaseProtocol._write
    ???
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\proactor_events.py:366: in write
    self._loop_writing(data=bytes(data))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_ProactorSocketTransport fd=1484 read=<_OverlappedFuture cancelled>>
f = None, data = b'X\x00\x00\x00\x04'

    def _loop_writing(self, f=None, data=None):
        try:
            if f is not None and self._write_fut is None and self._closing:
                # XXX most likely self._force_close() has been called, and
                # it has set self._write_fut to None.
                return
            assert f is self._write_fut
            self._write_fut = None
            self._pending_write = 0
            if f:
                f.result()
            if data is None:
                data = self._buffer
                self._buffer = None
            if not data:
                if self._closing:
                    self._loop.call_soon(self._call_connection_lost, None)
                if self._eof_written:
                    self._sock.shutdown(socket.SHUT_WR)
                # Now that we've reduced the buffer size, tell the
                # protocol to resume writing if it was paused.  Note that
                # we do this last since the callback is called immediately
                # and it may add more data to the buffer (even causing the
                # protocol to be paused again).
                self._maybe_resume_protocol()
            else:
>               self._write_fut = self._loop._proactor.send(self._sock, data)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^
E               AttributeError: 'NoneType' object has no attribute 'send'

..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\proactor_events.py:402: AttributeError

It appears to me that the issue revolves around the loop being closed ahead of time, any idea on how to tackle this problem?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions