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.

Problems with await in async dependencies (when using dependencies and middleware simultaneously)

See original GitHub issue

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

import fastapi
import datetime as dt
import sqlalchemy as sa

from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker


class _Conn:
    radius_engine: AsyncEngine
    radius_session_maker: sessionmaker


def init_pg_connections() -> None:
    _Conn.radius_engine = create_async_engine(
        'postgresql+asyncpg://postgres:password@host:port/db_name',
        connect_args={
            'server_settings': {
                'application_name': 'test_app'
            }
        },
        pool_size=2, max_overflow=10
    )
    _Conn.radius_session_maker = sessionmaker(
        bind=_Conn.radius_engine,
        autoflush=False, autocommit=False,
        expire_on_commit=False,
        class_=AsyncSession
    )


async def get_db():
    db: AsyncSession = _Conn.radius_session_maker()
    try:
        yield db
    finally:
        print('finally get_db. Before close')
        await db.close()
        print('finally get_db. After close')


def get_app():
    app = fastapi.FastAPI(
        on_startup=[init_pg_connections]
    )

    @app.middleware('http')
    async def add_process_time_header(request: fastapi.Request, call_next):
        start_time = dt.datetime.now()
        response = await call_next(request)
        process_time = (dt.datetime.now() - start_time).total_seconds()
        response.headers["X-Process-Time"] = str(process_time)
        return response

    @app.post('/test')
    async def test(
            data: int = fastapi.Body(5, embed=True),
            db: AsyncSession = fastapi.Depends(get_db)
    ):
        res = (await db.execute(sa.text('Select * FROM radacct'))).scalars().all()
        print('test', f'{data=} {res=}')
        return 'OK'
    return app


if __name__ == '__main__':
    import uvicorn
    _app = get_app()
    uvicorn.run(_app, host='10.10.10.98', port=7776)

Description

FastAPI version>=0.74 has a very strange problem with dependency (func get_db in example).

  1. If you run this code as it is and send request from swagger every thing is ok (Connection with db will close and you see message ‘finally get_db. After close’ in console).

  2. If you run this code as it is and use curl or aiohttp to send request, then “await db.close()” is never finish and you will not see message ‘finally get_db. After close’ in console. And if you continue to send requests, then you get warnings from Garbage collector and SQLAlchemy:

The garbage collector is trying to clean up connection <AdaptedConnection <asyncpg.connection.Connection object at 0x7fd3626a4740>>. This feature is unsupported on async dbapi, since no IO can be performed at this stage to reset the connection. Please close out all connections when they are no longer used, calling "close()" or using a context manager to manage their lifetime.

/usr/lib/python3.10/ipaddress.py:45: SAWarning: The garbage collector is trying to clean up connection <AdaptedConnection <asyncpg.connection.Connection object at 0x7fd3626a4740>>. This feature is unsupported on async dbapi, since no IO can be performed at this stage to reset the connection. Please close out all connections when they are no longer used, calling "close()" or using a context manager to manage their lifetime.

return IPv4Address(address)

  1. If you comment middleware and send requests from any client (swagger, curl and aiohttp), then every thing is ok.

  2. If you run this code as it is with FastAPI version <= 0.73, then every thing is ok.

In each cases clients has no problems in getting 200 responses from server. Unfortunately i have no idea why this happening.

Requirements: anyio==3.5.0 asgiref==3.5.0 asyncpg==0.25.0 click==8.0.4 coloredlogs==15.0.1 fastapi==0.75.0 | 0.73.0 greenlet==1.1.2 h11==0.13.0 httptools==0.3.0 humanfriendly==10.0 idna==3.3 mypy==0.931 mypy-extensions==0.4.3 pydantic==1.9.0 python-dateutil==2.8.2 python-dotenv==0.19.2 PyYAML==6.0 six==1.16.0 sniffio==1.2.0 SQLAlchemy==1.4.32 sqlalchemy2-stubs==0.0.2a20 starlette==0.17.1 tomli==2.0.1 typing_extensions==4.1.1 uvicorn==0.17.5 uvloop==0.16.0 watchgod==0.7 websockets==10.2

Operating System

Linux

Operating System Details

No response

FastAPI Version

=0.74

Python Version

3.9 and 3.10

Additional Context

No response

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:9
  • Comments:34 (9 by maintainers)

github_iconTop GitHub Comments

2reactions
b1-luettjecommented, Apr 19, 2022

Same here with async_session used inside a context manager, pinning fastapi to 0.73.0 fixes it. Using asyncmy as driver.

async def get_db_session(request: Request) -> AsyncGenerator[AsyncSession, None]:
    async_sessionmaker = request.app.state.sessionmaker
    session: AsyncSession
    async with async_sessionmaker() as session:
        try:
            yield session
        except SQLAlchemyError:
            await shield(session.rollback())
            logger = get_logger()
            logger.exception("sqlalchemy_exception")
1reaction
adriangbcommented, Jun 13, 2022

BaseHTTPMiddleware is fundamentally broken, I would not count on it being fixed.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Concurrency and async / await - FastAPI
Modern versions of Python have support for "asynchronous code" using something called "coroutines", with async and await syntax.
Read more >
Async useEffect does not execute entire body when ...
The function passed into useEffect cannot be async. ... defined outside of the useEffect (that you are calling) as a dependency to useEffect...
Read more >
Built in options for running async tasks
In this post I look at the problem of running one-off tasks asynchronously on app startup in ASP.NET Core, and explore the pros...
Read more >
A practical guide to async in Rust
If you're writing an asynchronous program in Rust or using an async library for the first time, this tutorial can help you get...
Read more >
The Ultimate FastAPI Tutorial Part 9 - Asynchronous ...
Notes on Async IO and Third-Party Dependencies like SQLAlchemy. FastAPI logo. There are two main reasons why FastAPI is called “Fast”:.
Read more >

github_iconTop Related Medium Post

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