Repository: pytest-dev/pytest-asyncio

Issue #708: Loop is closed before fixture teardown completes
Created: 2023-12-06T05:31:17Z
I am looking to use pytest-asyncio to create a fixture that spawns a subprocess and then kills the subprocess when the fixture teardown is complete. I've noticed that the event loop can be closed before the fixture teardown is complete, which can lead to the subprocess kill not working.
Here is a simple example (tested using pytest-asyncio 0.23.2):
import asyncio
import pytest
import pytest_asyncio

import logging
logger = logging.getLogger(__name__)

@pytest_asyncio.fixture
async def echo():
    p = await asyncio.create_subprocess_shell(
        f"sleep 2"
    )
    yield p
    p.kill()

@pytest.mark.asyncio
async def test_echo(echo):
    logger.info(echo)
Which produces the following output:
================================ test session starts =================================
platform linux -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0
rootdir: /home/jason/pytest-asyncio
plugins: asyncio-0.23.2
asyncio: mode=Mode.STRICT
collected 1 item                                                                     

test.py::test_echo 
----------------------------------- live log call ------------------------------------
INFO     test:test.py:18 <Process 756596>
PASSED                                                                         [100%]
--------------------------------- live log teardown ----------------------------------
WARNING  asyncio:unix_events.py:1427 Loop <_UnixSelectorEventLoop running=False closed=True debug=False> that handles pid 756596 is closed


================================= 1 passed in 0.00s ==================================
I am invoking the test using python3 -m pytest --log-cli-level=INFO ./test.py. I noticed that the issue is intermittent without logging to CLI... perhaps timing-related.
In my actual use case the warning cannot be ignored as the subprocess will not terminate on its own.

Duplicate issue #1083: TaskGroup around yield in fixture doesn't terminate with exceptions
Created: 2025-03-25T15:50:22Z
I tried to make a fixture to throw an exception after a certain amount of time to catch deadlocks in my tests, but the exception isn't causing the TaskGroup to error out.
Here is a minimal example fixture that interacts with a literally trivial test
https://github.com/sawalls/pytest-asyncio-example/blob/main/tests/conftest.py
@fixture(scope="function")
async def async_buddy():
    mytasks = set()
    async def forever_loop():
        while True:
            await sleep(1)

    async def exception_task():
        await sleep(5)
        raise Exception("This is an exception")

    async with TaskGroup() as tg:
        mytasks.add(tg.create_task(forever_loop()))
        mytasks.add(tg.create_task(exception_task()))
        yield

---

async def test_example(async_buddy):
    pass

It's inspired by the recipe in the python docs to terminate a TaskGroup:
https://docs.python.org/3/library/asyncio-task.html#terminating-a-task-group
Expected behavior:

Make TaskGroup
Make Tasks
Yield to test, which returns immediately
Enter context manager waiting for tasks
Get Exception from exception task, cancel all tasks, and throw out of the fixture

Observed behavior:

Make TaskGroup
Make Tasks
Yield to test, which returns immediately (so at this point the test has reported success)
Enter context manager waiting for tasks
Hangs indefinitely

Please let me know if there's anything about my intentions I can make more clear or documents I should read to get a better understanding.
Also, if you want to direct me at some code to understand the library's understanding of this, I may be able to try and reason about it.

Closing comment (2025-03-25T15:51:52Z): Wanted to note this is similar to #708 but that had multiple processes involved, which confounded my understanding a bit.