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
Raised by TimerContext.__enter__ when asyncio.current_task(loop=self._loop) returns None, i.e. the timeout context manager was entered outside of any asyncio Task. Timeouts must cancel a running task, so entering one with no current task is a usage error (RuntimeError).
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 c0ef574e29)
Solutions
- Use the timeout only inside an async function running as a Task.
- Use aiohttp.ClientTimeout with the client request API rather than TimerContext directly.
- In tests, wrap usage in asyncio.ensure_future / a real coroutine task.
Example fix
// before
timer = TimeoutHandle(loop, 5).timer()
with timer: # no current task -> RuntimeError
do_sync_work()
// after
async def wrapped():
with TimeoutHandle(loop, 5).timer():
await do_async_work()
asyncio.run(wrapped()) Defensive patterns
Strategy: try-catch
Validate before calling
import asyncio
def has_current_task(loop):
return asyncio.current_task(loop=loop) is not None Try / catch
try:
with timer:
...
except RuntimeError as e:
if 'inside a task' in str(e):
# move usage into a coroutine task
raise Prevention
- Use timeouts only inside coroutines scheduled as Tasks.
- Prefer the high-level ClientTimeout API over internal TimerContext.
- In tests, always run code under asyncio.run / a real Task.
When it happens
Trigger: Using aiohttp's internal TimeoutHandle.timer() / async-timeout style context manager from a synchronous callback, in asyncio.run top-level code before a task is created, or from a thread that has no event loop task. Common when a timeout object leaks into non-async code or tests that drive the loop manually.
Common situations: Unit tests calling protocol methods directly without an enclosing task; refactors that move timeout usage out of coroutines; third-party code reusing aiohttp's TimerContext in sync helpers.
Related errors
- Connection timeout to host {url}
- timeout parameter cannot be of {type} type, please use 'time
- Session and connector have to use same event loop
- total timeout must be a positive number or None to disable,
- Concurrent call to receive() is not allowed
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/f8e9237d2fd5f959.json.
Report an issue: GitHub.