Skip to content

0.69 introduces a RuntimeError: Task <...>got Future <Future pending> attached to a different loop #4100

@spacemanspiff2007

Description

@spacemanspiff2007

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google "How to X in FastAPI" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

Create a venv with the following versions:

fastapi==0.70.0
tortoise-orm==0.17.8
pytest==6.2.5

save this code as main.py (name is important!)

from typing import Dict

import pytest
from fastapi import FastAPI
from fastapi import status
from fastapi.testclient import TestClient
from tortoise import Model
from tortoise import Tortoise
from tortoise.fields import CharField, ManyToManyRelation, ManyToManyField


class DbObj(Model):
    name: str = CharField(max_length=255, pk=True)
    my_rels1: ManyToManyRelation['DbObjRelation1']
    my_rels2: ManyToManyRelation['DbObjRelation2']


class DbObjRelation1(Model):
    name: str = CharField(max_length=255, pk=True)
    my_objs: ManyToManyRelation['DbObj'] = ManyToManyField('models.DbObj', related_name='my_rels1')


class DbObjRelation2(Model):
    name: str = CharField(max_length=255, pk=True)
    my_objs: ManyToManyRelation['DbObj'] = ManyToManyField('models.DbObj', related_name='my_rels2')


app = FastAPI(title='Server')


@app.get(
    path="/obj",
    response_model=Dict[str, str],
    status_code=status.HTTP_200_OK,
)
async def obj_get():
    ct = await DbObj.all().prefetch_related('my_rels1', 'my_rels2')
    ret = {}
    for obj in ct:
        ret[obj.name] = ', '.join(list(r.name for r in obj.my_rels1))
    return ret


@app.put(
    path="/obj/{name}",
    response_model=Dict[str, str],
    status_code=status.HTTP_200_OK,
)
async def obj_create(name: str):
    obj = await DbObj.create(name=name)
    rel = await DbObjRelation1.create(name=f'{name}_rel')
    await obj.my_rels1.add(rel)
    return None


test_client = TestClient(app)


@pytest.fixture(scope="function", autouse=True)
@pytest.mark.asyncio
async def db(monkeypatch):
    await Tortoise.init(db_url='sqlite://:memory:', modules={'models': ['main']})
    await Tortoise.generate_schemas()

    yield

    await Tortoise.close_connections()


def test_read_main():
    # create one object
    response = test_client.put("/obj/name1")
    assert response.status_code == 200

    # read it back
    response = test_client.get("/obj")
    assert response.status_code == 200
    assert response.json() == {'name1': 'name1_rel'}

Description

FastAPI 0.68.2 runs without issues.
From FastAPI 0.69.0 on tests fail:

run
pytest main.py

results in

Testing started at 14:43 ...
Launching pytest with arguments C:/MyTestVenv/src/main.py --no-header --no-summary -q in C:/MyTestVenv/src

============================= test session starts =============================
collecting ... collected 1 item

main.py::test_read_main FAILED                                           [100%]
main.py:69 (test_read_main)
def test_read_main():
        # create one object
        response = test_client.put("/obj/name1")
        assert response.status_code == 200
    
        # read it back
>       response = test_client.get("/obj")

main.py:76: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\venv\lib\site-packages\requests\sessions.py:555: in get
    return self.request('GET', url, **kwargs)
..\venv\lib\site-packages\starlette\testclient.py:468: in request
    return super().request(
..\venv\lib\site-packages\requests\sessions.py:542: in request
    resp = self.send(prep, **send_kwargs)
..\venv\lib\site-packages\requests\sessions.py:655: in send
    r = adapter.send(request, **kwargs)
..\venv\lib\site-packages\starlette\testclient.py:266: in send
    raise exc
..\venv\lib\site-packages\starlette\testclient.py:263: in send
    portal.call(self.app, scope, receive, send)
..\venv\lib\site-packages\anyio\from_thread.py:230: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
C:\Program Files\Python38\lib\concurrent\futures\_base.py:439: in result
    return self.__get_result()
C:\Program Files\Python38\lib\concurrent\futures\_base.py:388: in __get_result
    raise self._exception
..\venv\lib\site-packages\anyio\from_thread.py:177: in _call_func
    retval = await retval
..\venv\lib\site-packages\fastapi\applications.py:208: in __call__
    await super().__call__(scope, receive, send)
..\venv\lib\site-packages\starlette\applications.py:112: in __call__
    await self.middleware_stack(scope, receive, send)
..\venv\lib\site-packages\starlette\middleware\errors.py:181: in __call__
    raise exc
..\venv\lib\site-packages\starlette\middleware\errors.py:159: in __call__
    await self.app(scope, receive, _send)
..\venv\lib\site-packages\starlette\exceptions.py:82: in __call__
    raise exc
..\venv\lib\site-packages\starlette\exceptions.py:71: in __call__
    await self.app(scope, receive, sender)
..\venv\lib\site-packages\starlette\routing.py:656: in __call__
    await route.handle(scope, receive, send)
..\venv\lib\site-packages\starlette\routing.py:259: in handle
    await self.app(scope, receive, send)
..\venv\lib\site-packages\starlette\routing.py:61: in app
    response = await func(request)
..\venv\lib\site-packages\fastapi\routing.py:226: in app
    raw_response = await run_endpoint_function(
..\venv\lib\site-packages\fastapi\routing.py:159: in run_endpoint_function
    return await dependant.call(**values)
main.py:37: in obj_get
    ct = await DbObj.all().prefetch_related('my_rels1', 'my_rels2')
..\venv\lib\site-packages\tortoise\queryset.py:966: in _execute
    instance_list = await self._db.executor_class(
..\venv\lib\site-packages\tortoise\backends\base\executor.py:176: in execute_select
    await self._execute_prefetch_queries(instance_list)
..\venv\lib\site-packages\tortoise\backends\base\executor.py:567: in _execute_prefetch_queries
    await asyncio.gather(*prefetch_tasks)
..\venv\lib\site-packages\tortoise\backends\base\executor.py:555: in _do_prefetch
    return await self._prefetch_m2m_relation(instance_id_list, field, related_query)
..\venv\lib\site-packages\tortoise\backends\base\executor.py:470: in _prefetch_m2m_relation
    _, raw_results = await self.db.execute_query(query.get_sql())
..\venv\lib\site-packages\tortoise\backends\sqlite\client.py:30: in translate_exceptions_
    return await func(self, query, *args)
..\venv\lib\site-packages\tortoise\backends\sqlite\client.py:133: in execute_query
    async with self.acquire_connection() as connection:
..\venv\lib\site-packages\tortoise\backends\base\client.py:213: in __aenter__
    await self.lock.acquire()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio.locks.Lock object at 0x0000023238D27F10 [unlocked]>

    async def acquire(self):
        """Acquire a lock.
    
        This method blocks until the lock is unlocked, then sets it to
        locked and returns True.
        """
        if (not self._locked and (self._waiters is None or
                all(w.cancelled() for w in self._waiters))):
            self._locked = True
            return True
    
        if self._waiters is None:
            self._waiters = collections.deque()
        fut = self._loop.create_future()
        self._waiters.append(fut)
    
        # Finally block should be called before the CancelledError
        # handling as we don't want CancelledError to call
        # _wake_up_first() and attempt to wake up itself.
        try:
            try:
>               await fut
E               RuntimeError: Task <Task pending name='Task-11' coro=<BaseExecutor._do_prefetch() running at C:\MyTestVenv\venv\lib\site-packages\tortoise\backends\base\executor.py:555> cb=[gather.<locals>._done_callback() at C:\Program Files\Python38\lib\asyncio\tasks.py:769]> got Future <Future pending> attached to a different loop

C:\Program Files\Python38\lib\asyncio\locks.py:203: RuntimeError


============================== 1 failed in 0.63s ==============================

Process finished with exit code 1

Operating System

Windows

Operating System Details

No response

FastAPI Version

0.70.0

Python Version

Python 3.8.8

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions