- 
                Notifications
    You must be signed in to change notification settings 
- Fork 166
Open
Labels
needsinfoRequires additional information from the issue authorRequires additional information from the issue author
Description
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:
- Endpoint doesn't do anything DB related, then this setup works perfectly, the downgrade also is issued correctly
- 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
Labels
needsinfoRequires additional information from the issue authorRequires additional information from the issue author