Skip to content

Teardown error: "attached to a different loop" with even_loop module scope fixture #162

Description

@alblasco

I'm working on python/pytest/asyncio since beginning of this year, and when updating pytest-asyncio (from 0.10.0 to 0.12.0) then some previous working tests started to fail.

I have created following minimal example (isolated from a more complex solution) that always fails at teardown.
Can you please let me know if you can reproduce it, and your kind feedback about this potential issue.
Don´t hesitate to ask for any additional information or test on my side.

BR

Angel Luis

Additional info

Versions used to reproduce are:

  • pytest-asyncio 0.12.0. (it fails also with 0.11.0)
  • pytest 5.4.2
  • python 3.8.2
  • setuptools 46.2.0 & pip 20.1
  • windows 10 Pro [Versión 10.0.18362.175]
    Note: Minimal Example works with pytest-asyncio 0.10.0.

Minimal example is the following

import asyncio
import contextlib
import functools
import pytest


@pytest.mark.asyncio
async def test_simple(port):
    assert True

@pytest.fixture(scope="module")
def event_loop():
    """Change event_loop fixture to module level."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()


@pytest.fixture(scope="module")
async def port(request, event_loop):
    def port_finalizer(finalizer):
        async def port_afinalizer():
            await finalizer(None, None, None)
        event_loop.run_until_complete(port_afinalizer())

    context_manager = port_map()
    port = await context_manager.__aenter__()
    request.addfinalizer(functools.partial(port_finalizer, context_manager.__aexit__))
    return port

@contextlib.asynccontextmanager
async def port_map():
    worker = asyncio.create_task(background_worker())
    yield
    try:
        worker.cancel()
        await worker
    except asyncio.CancelledError:
        pass

async def background_worker():
    while True:
        await asyncio.sleep(10.0)

When I run the test I get the following console output:

(venv) C:\git\ATB2\ejemplo_issue>pytest
=================== test session starts =============================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\git\ATB2\ejemplo_issue
plugins: asyncio-0.12.0
collected 1 item                                                                                                                                                                                                                                                           

test_example.py .E                                                                                                                                                                                                                                                   [100%]

====================== ERRORS =======================================
______________ ERROR at teardown of test_simple _____________________

finalizer = <bound method _AsyncGeneratorContextManager.__aexit__ of <contextlib._AsyncGeneratorContextManager object at 0x0423AC40>>

    def port_finalizer(finalizer):
        async def port_afinalizer():
            await finalizer(None, None, None)
>       event_loop.run_until_complete(port_afinalizer())

test_example.py:24:
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Users\ablasco\AppData\Local\Programs\Python\Python38-32\lib\asyncio\base_events.py:616: in run_until_complete
    return future.result()
test_example.py:23: in port_afinalizer
    await finalizer(None, None, None)
C:\Users\ablasco\AppData\Local\Programs\Python\Python38-32\lib\contextlib.py:178: in __aexit__
    await self.gen.__anext__()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    @contextlib.asynccontextmanager
    async def port_map():
        worker = asyncio.create_task(background_worker())
        yield
        try:
            worker.cancel()
>           await worker
E           RuntimeError: Task <Task pending name='Task-4' coro=<port.<locals>.port_finalizer.<locals>.port_afinalizer() running at C:\git\ATB2\ejemplo_issue\test_example.py:23> cb=[_run_until_complete_cb() at C:\Users\ablasco\AppData\Local\Programs\Python\Python38-32
\lib\asyncio\base_events.py:184]> got Future <Task pending name='Task-2' coro=<background_worker() running at C:\git\ATB2\ejemplo_issue\test_example.py:43> wait_for=<Future cancelled>> attached to a different loop

test_example.py:37: RuntimeError
============================= short test summary info ==============================
ERROR test_example.py::test_simple - RuntimeError: Task <Task pending name='Task-4' coro=<port.<locals>.port_finalizer.<locals>.port_afinalizer() running at C:\git\ATB2\ejemplo_issue\test_example.py:23> cb=[_run_until_complete_cb() at C:\Users\ablasco\AppData\Local...

=========================== 1 passed, 1 error in 0.13s =============================

Unexpectedly either of the following changes, made the test to pass:

a) Change fixture to a lower level scope (class or function):

@pytest.fixture(scope="class")
def event_loop():
@pytest.fixture(scope="class")
async def port(request, event_loop):

b) Embed the test inside a class:

class TestClass:
    @pytest.mark.asyncio
    async def test_simple(port):
        assert True

c) Or change finalizer to call get_event_loop (instead of using event_loop from fixture)

    def port_finalizer(finalizer):
        async def port_afinalizer():
            await finalizer(None, None, None)
        # event_loop.run_until_complete(port_afinalizer())
        loop = asyncio.get_event_loop()
        loop.run_until_complete(port_afinalizer())

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions