(
cm: AbstractContextManager[_T],
)
| 16 | |
| 17 | @asynccontextmanager |
| 18 | async def contextmanager_in_threadpool( |
| 19 | cm: AbstractContextManager[_T], |
| 20 | ) -> AsyncGenerator[_T, None]: |
| 21 | # blocking __exit__ from running waiting on a free thread |
| 22 | # can create race conditions/deadlocks if the context manager itself |
| 23 | # has its own internal pool (e.g. a database connection pool) |
| 24 | # to avoid this we let __exit__ run without a capacity limit |
| 25 | # since we're creating a new limiter for each call, any non-zero limit |
| 26 | # works (1 is arbitrary) |
| 27 | exit_limiter = CapacityLimiter(1) |
| 28 | try: |
| 29 | yield await run_in_threadpool(cm.__enter__) |
| 30 | except Exception as e: |
| 31 | ok = bool( |
| 32 | await anyio.to_thread.run_sync( |
| 33 | cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter |
| 34 | ) |
| 35 | ) |
| 36 | if not ok: |
| 37 | raise e |
| 38 | else: |
| 39 | await anyio.to_thread.run_sync( |
| 40 | cm.__exit__, None, None, None, limiter=exit_limiter |
| 41 | ) |
no outgoing calls
no test coverage detected
searching dependent graphs…