#1983·outlines

caching: cache_disabled() context manager is not safe in asyncio contexts

Author: harsh4vardhanCreated Aug 6, 2026Updated Aug 6, 2026

cache_disabled() (lines 186–195) mutates a module-level global:

python
@contextlib.contextmanager
def cache_disabled():
    global _caching_enabled
    original_state = _caching_enabled
    _caching_enabled = False
    try:
        yield
    finally:
        _caching_enabled = original_state

In an asyncio event loop, when coroutine A enters this context manager and then awaits, the event loop runs other coroutines. Those coroutines see _caching_enabled = False even though they never opted in — they will skip the cache and recompute unnecessarily (or see inconsistent behaviour if the cache is expected to be active).

Minimum example (deterministic with asyncio.Event synchronization):

python
import asyncio, contextlib

_caching_enabled = True  # replica of caching.py global

@contextlib.contextmanager
def cache_disabled():    # exact code from caching.py:186-195
    global _caching_enabled
    original_state = _caching_enabled
    _caching_enabled = False
    try:
        yield
    finally:
        _caching_enabled = original_state

async def coroutine_a(entered, done):
    with cache_disabled():
        entered.set()         # signal: flag is now False
        await done.wait()     # yield — B runs here

async def coroutine_b(entered, done):
    await entered.wait()      # wait until A holds the context
    print(_caching_enabled)   # False — B never called cache_disabled()
    done.set()

async def main():
    e, d = asyncio.Event(), asyncio.Event()
    await asyncio.gather(coroutine_a(e, d), coroutine_b(e, d))

asyncio.run(main())
# prints: False

Fix: replace _caching_enabled with a contextvars.ContextVar, which isolates the flag per-coroutine execution context:

python
import contextvars
_caching_enabled: contextvars.ContextVar[bool] = contextvars.ContextVar(
    "_caching_enabled", default=True
)

@contextlib.contextmanager
def cache_disabled():
    token = _caching_enabled.set(False)
    try:
        yield
    finally:
        _caching_enabled.reset(token)