aio-libs/aiohttp · error · RuntimeError

Timeout context manager should be used inside a task

Error message

Timeout context manager should be used inside a task

What it means

BaseTimerContext.__enter__ raises RuntimeError when asyncio.current_task() returns None — meaning the timeout context manager is being used outside an asyncio task. The timeout() helper relies on task-level cancellation mechanics, so it requires an active task in the event loop.

Solutions

  1. Ensure the timeout context manager is used inside an async function that runs as a task
  2. Wrap synchronous callbacks in a coroutine before using timeout
  3. Use asyncio.run() or loop.run_until_complete() to establish a task context

Example fix

# before (no task context)
def callback():
    with timeout(10):
        do_work()  # RuntimeError

# after (inside a task)
async def fetch():
    async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
        return await session.get(url)

asyncio.run(fetch())
Defensive patterns

Strategy: validation

Validate before calling

import asyncio, sys

def ensure_task_context():
    if sys.version_info >= (3, 11):
        task = asyncio.current_task()
    else:
        task = asyncio.current_task()
    if task is None:
        raise RuntimeError('timeout() must be called from within an asyncio task')
    return task

Try / catch

try:
    with timeout(10):
        await do_work()
except RuntimeError as e:
    if 'inside a task' in str(e):
        # Re-run within a proper task
        await asyncio.ensure_future(do_work_with_timeout())
    raise

Prevention

When it happens

Trigger: Using aiohttp.timeout() or the internal TimerContext in synchronous code, in a callback scheduled with loop.call_soon (which has no task), or before the event loop is running. Also triggered when using timeout in a thread that has no asyncio task context.

Common situations: Calling client code with timeout in a non-async function; using timeout in asyncio.ensure_future callbacks that are not wrapped in a coroutine; testing async code with pytest but calling timeout synchronously in a fixture; calling from a sync callback registered via add_done_callback.

Understand the failure class

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/f8e9237d2fd5f959. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/helpers.py:705

    """Low resolution timeout context manager"""

    __slots__ = ("_loop", "_tasks", "_cancelled", "_cancelling")

    def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
        self._loop = loop
        self._tasks: list[asyncio.Task[Any]] = []
        self._cancelled = False
        self._cancelling = 0

    def assert_timeout(self) -> None:
        """Raise TimeoutError if timer has already been cancelled."""
        if self._cancelled:
            raise asyncio.TimeoutError from None

    def __enter__(self) -> BaseTimerContext:
        task = asyncio.current_task(loop=self._loop)
        if task is None:
            raise RuntimeError("Timeout context manager should be used inside a task")

        if sys.version_info >= (3, 11):
            # Remember if the task was already cancelling
            # so when we __exit__ we can decide if we should
            # raise asyncio.TimeoutError or let the cancellation propagate
            self._cancelling = task.cancelling()

        if self._cancelled:
            raise asyncio.TimeoutError from None

        self._tasks.append(task)
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,

View on GitHub (pinned to d041d4d0fd)