python/cpython · error · RuntimeError
{self!r} is bound to a different event loop
Error message
{self!r} is bound to a different event loop What it means
All loop-bound asyncio primitives (Lock, Semaphore, Condition, Event, Queue, Barrier) lazily bind to the first event loop that uses them via _LoopBoundMixin._get_loop(). If the running loop later differs from the bound one, this RuntimeError fires. Since Python 3.10 the binding happens lazily (fixing 'attached to a different loop' at creation), so the error surfaces at first cross-loop use instead.
Source
Thrown at Lib/asyncio/mixins.py:20
import threading
from . import events
_global_lock = threading.Lock()
class _LoopBoundMixin:
_loop = None
def _get_loop(self):
loop = events._get_running_loop()
if self._loop is None:
with _global_lock:
if self._loop is None:
self._loop = loop
if loop is not self._loop:
raise RuntimeError(f'{self!r} is bound to a different event loop')
return loop
View on GitHub (pinned to bc6749cc3b)
Solutions
- Create loop-bound primitives inside the coroutine/loop that owns them, not at import time
- Scope fixtures to the loop lifetime: create the Queue/Lock in an async fixture
- For cross-thread sharing, use loop.call_soon_threadsafe / run_coroutine_threadsafe instead of sharing primitives
- Restructure to a single long-lived loop (asyncio.run once for the whole app)
Example fix
# before
lock = asyncio.Lock() # module level
async def main():
async with lock: ...
asyncio.run(main())
asyncio.run(main()) # second run -> different loop -> RuntimeError
# after
async def main():
lock = asyncio.Lock() # created inside the loop that uses it
async with lock: ...
asyncio.run(main()) Defensive patterns
Strategy: validation
Validate before calling
import asyncio prim_loop = getattr(prim, '_loop', None) running = asyncio.events._get_running_loop() conflict = prim_loop is not None and running is not None and prim_loop is not running
Type guard
def primitive_bound_to_running_loop(prim) -> bool:
import asyncio
bound = getattr(prim, '_loop', None)
running = asyncio.events._get_running_loop()
return bound is None or running is bound Try / catch
try:
async with lock:
...
except RuntimeError as e:
if 'bound to a different event loop' in str(e):
# recreate the primitive inside this loop and retry once Prevention
- Create locks/queues/events inside the loop that uses them
- Scope test fixtures to loop lifetime, not module lifetime
- Run one long-lived loop per process; use threadsafe APIs to cross into it
When it happens
Trigger: Creating an asyncio.Lock/Queue at module level and using it under two different asyncio.run() calls; sharing a primitive between the main loop and a loop in another thread; tests that create a new loop per test but share a module-level Queue.
Common situations: Pytest-asyncio with function-scoped loops against module-scoped fixtures holding locks/queues; Jupyter's auto-running loop vs a manual asyncio.run; applications that restart the event loop (watchdogs, hot reload); multi-threaded servers with one loop per thread sharing globals.
Related errors
- {htmldir!r} is not a Sphinx HTML output directory
- {name} is not part of stable ABI. Document it as `c:macro::`
- deprecated-removed:: second argument cannot be `next`
- no running event loop
- There is no current event loop in thread %r.
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/095aa3b0ac692547.
Report an issue: GitHub.