From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sun, 4 Sep 2022 19:09:24 +0000 (-0500) Subject: 🐛 Allow exit code for dependencies with `yield` to always execute, by removing capaci... X-Git-Tag: 0.82.0~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f8460a8b54fd4975ca64c7fbe8d516740781f0df;p=thirdparty%2Ffastapi%2Ffastapi.git 🐛 Allow exit code for dependencies with `yield` to always execute, by removing capacity limiter for them, to e.g. allow closing DB connections without deadlocks (#5122) Co-authored-by: Sebastián Ramírez --- diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py index becac3f33d..c728ec1d22 100644 --- a/fastapi/concurrency.py +++ b/fastapi/concurrency.py @@ -1,6 +1,8 @@ import sys from typing import AsyncGenerator, ContextManager, TypeVar +import anyio +from anyio import CapacityLimiter from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa from starlette.concurrency import ( # noqa @@ -22,11 +24,24 @@ _T = TypeVar("_T") async def contextmanager_in_threadpool( cm: ContextManager[_T], ) -> AsyncGenerator[_T, None]: + # blocking __exit__ from running waiting on a free thread + # can create race conditions/deadlocks if the context manager itself + # has it's own internal pool (e.g. a database connection pool) + # to avoid this we let __exit__ run without a capacity limit + # since we're creating a new limiter for each call, any non-zero limit + # works (1 is arbitrary) + exit_limiter = CapacityLimiter(1) try: yield await run_in_threadpool(cm.__enter__) except Exception as e: - ok: bool = await run_in_threadpool(cm.__exit__, type(e), e, None) + ok = bool( + await anyio.to_thread.run_sync( + cm.__exit__, type(e), e, None, limiter=exit_limiter + ) + ) if not ok: raise e else: - await run_in_threadpool(cm.__exit__, None, None, None) + await anyio.to_thread.run_sync( + cm.__exit__, None, None, None, limiter=exit_limiter + )