]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🐛 Allow exit code for dependencies with `yield` to always execute, by removing capaci...
authorAdrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
Sun, 4 Sep 2022 19:09:24 +0000 (14:09 -0500)
committerGitHub <noreply@github.com>
Sun, 4 Sep 2022 19:09:24 +0000 (21:09 +0200)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
fastapi/concurrency.py

index becac3f33db769157ca35d9f196a168e7ee3e566..c728ec1d22915fc5a27c3f3e2de698711ea73b06 100644 (file)
@@ -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
+        )