question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RunTimeError: got Future <Future pending> attached to a different loop when using custom loop in sync fixtures when upgrading from 0.14.2 to 0.15.0

See original GitHub issue

Checklist

  • The bug is reproducible against the latest release and/or master.
  • There are no similar issues or pull requests to fix it yet.

Describe the bug

Upgrading starlette>=0.15.0 breaks current testing strategy. The setup is mocking a nats subscription by actually using the nats server. The code works with starlette 0.14.2, upgradign to 0.15.0 gives RunTumeError got Future <Future pending> attached to a different loop . When upgrading to starlette 0.16.0 it gives TimeOut errors. I would love to keep tests sync.

To reproduce

requirements.txt

starlette
requests
pytest
asyncio-nats-client

code

from starlette.routing import Route
from starlette.testclient import TestClient
import pytest
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
import asyncio


from nats.aio.client import Client as NATS

"""
Test work with starlette 0.14.2
Error with starlette 0.15.0: RunTimeError: got Future <Future pending> attached to a different loop
Error with starlette 0.16.0: Nats timeout

The test is that the client code makes a nats request to a mocked nats service over nats itself.

Requirement a running nats server `docker run -d -p 4222:4222 nats:latest`
"""

HOST_NATS = "localhost:4222"


# =======================================================================
# CODE
# =======================================================================


def create_app():
    async def index(request):
        r = await request.app.state.nc.request("subject1", timeout=1, payload=b"PING")
        return PlainTextResponse(content=r.data.decode())

    async def setup() -> None:
        await app.state.nc.connect(HOST_NATS)
        print("Connected to nats")

    app = Starlette(debug=True, routes=[Route('/', index)], on_startup=[setup])
    app.state.nc: NATS = NATS()

    return app


app = create_app()


# =======================================================================
# MOCKS & TESTS
# =======================================================================


class NatsServiceMock:
    def __init__(self) -> None:
        self.nc: NATS = NATS()

    async def lifespan(self) -> None:
        await self.nc.connect(HOST_NATS)
        await self.nc.subscribe("subject1", cb=self.handle)

    async def handle(self, msg):
        await self.nc.publish(msg.reply, b"PONG")

    def __enter__(self):
        loop = asyncio.get_event_loop()
        loop.run_until_complete(self.lifespan())
        return self

    def __exit__(self, *args) -> None:
        pass


@pytest.fixture(scope="session")
def nats_service() -> None:
    with NatsServiceMock() as nc:
        yield nc


@pytest.fixture(scope="session")
def test_app(nats_service) -> None:
    with TestClient(create_app()) as client:
        yield client


# =======================================================================
# TESTS
# =======================================================================


def test_index_should_give_a_succesful_response(test_app):
    r = test_app.get("/")
    assert r.status_code == 200
    assert r.text == "PONG"

Run:

pytest <file>

Expected behavior

The test to work.

Actual behavior

Test does not work.

Debugging material

output running with starlette 0.15.0:

        try:
            # wait until the future completes or the timeout
            try:
>               await waiter
E               RuntimeError: Task <Task pending name='anyio.from_thread.BlockingPortal._call_func' coro=<BlockingPortal._call_func() running at /home/sevaho/.local/share/virtualenvs/testje-KTUsWEz0/lib/python3.8/site-packages/anyio/from_thread.py:177> cb=[TaskGroup._spawn.<locals>.task_done() at /home/sevaho/.local/share/virtualenvs/testje-KTUsWEz0/lib/python3.8/site-packages/anyio/_backends/_asyncio.py:622]> got Future <Future pending> attached to a different loop

/usr/lib/python3.8/asyncio/tasks.py:481: RuntimeError

output when running with starlette 0.16.0:

        # Wait for the response or give up on timeout.
        try:
            msg = await asyncio.wait_for(future, timeout, loop=self._loop)
            return msg
        except asyncio.TimeoutError:
            del self._resp_map[token.decode()]
            future.cancel()
>           raise ErrTimeout
E           nats.aio.errors.ErrTimeout: nats: Timeout

/home/sevaho/.local/share/virtualenvs/testje-KTUsWEz0/lib/python3.8/site-packages/nats/aio/client.py:945: ErrTimeout

Environment

  • OS: Linux
  • Python version: 3.8
  • Starlette version: 0.14.2 / 0.15.0 / 0.16.0

Additional context

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:22
  • Comments:26 (6 by maintainers)

github_iconTop GitHub Comments

9reactions
Kludexcommented, Jun 26, 2022

I have faced the same issues in my unit tests. I fixed the issue by using a context manager for TestClient. I’ve no idea why it works though (especially because it is not used as context manager in the docs of FastAPI) 😞

Doesn’t work:

@pytest.fixture(scope="session")
def client() -> TestClient:
    return TestClient(app)

Works:

@pytest.fixture(scope="session")
def client() -> TestClient:
    with TestClient(app) as c:
        yield c

The latter calls the startup event, while the former doesn’t.

8reactions
Mityuhacommented, Nov 10, 2021

I’ve got similar issue in tests with FastAPI (0.68.2 --> 0.69.0 which means Starlette upgrading from 0.14.2 to 0.15.0) and databases library (based on asyncpg):

...
[stacktrace here]
...
asyncpg/protocol/protocol.pyx:323: in query
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   ???
E   asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress

asyncpg/protocol/protocol.pyx:707: InterfaceError

With FastAPI 0.68.2 (Starlette 0.14.2) works like a charm.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Task got Future <Future pending> attached to a different loop ...
run_until_disconnected() only needs to be called if you need to. async def are called by using await on them, not by creating a...
Read more >
Changelog — aiohttp 3.8.3 documentation
Always create a new event loop in aiohttp.web.run_app() . This adds better compatibility with asyncio.run() or if trying to run multiple apps in...
Read more >
Changelog - Cypress Documentation
Fixed an issue with Angular Component Testing where urls within SASS/SCSS files were not being correctly resolved which could result in incomplete styling....
Read more >
Changelog - Prefect Docs
Allow custom job name for Kubernetes flow runsto be set in job template - # ... Update LocalDaskExecutor to use new Python futures...
Read more >
The Ultimate Guide To Custom Watercooling Your PC
What is the minimum radiator size I need for my watercooling loop? A Beginners Guide to Watercooling Components. CPU / GPU / RAM...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found