cocoindex-io/cocoindex · error · RuntimeError

async_to_sync_iter must not be called from a running event l

Error message

async_to_sync_iter must not be called from a running event loop

What it means

async_to_sync_iter bridges an async iterator into a synchronous one using a background thread, which must own the event loop; calling it from inside a running event loop would deadlock (the sync iteration would block the loop the async side needs). It therefore raises this RuntimeError eagerly.

Source

Thrown at python/cocoindex/connectorkits/async_adapters.py:139

    Raises:
        RuntimeError: If called from within a running event loop.
        Any exception raised by the async iterator is re-raised.

    Example:
        >>> async def async_generator(start: int, end: int):
        ...     for i in range(start, end):
        ...         yield i
        ...
        >>> for value in async_to_sync_iter(lambda: async_generator(0, 5)):
        ...     print(value)
    """
    try:
        _asyncio.get_running_loop()
    except RuntimeError:
        pass  # No running loop, which is what we want
    else:
        raise RuntimeError(
            "async_to_sync_iter must not be called from a running event loop"
        )

    q: _queue.Queue[tuple[bool, _T | Exception]] = _queue.Queue(maxsize=max_queue_size)
    stop_event = _threading.Event()

    def producer() -> None:
        async def run_async() -> None:
            try:
                async for item in async_iter_fn():
                    if stop_event.is_set():
                        break
                    q.put((False, item))
            except Exception as e:  # pylint: disable=broad-except
                q.put((True, e))
            finally:
                q.put((True, StopIteration()))

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Move the sync iteration out of async code — call it from a plain sync function or a separate thread
  2. Inside async code, iterate the async iterator directly with `async for`
  3. If a sync API requires the data, materialize it first with asyncio.run() in a thread outside the loop (or run_in_executor)
  4. For scripts, ensure no outer event loop is running when using the sync adapter

Example fix

// before
async def main():
    for item in async_to_sync_iter(agen()):  # RuntimeError
        ...
// after
async def main():
    async for item in agen():
        ...
Defensive patterns

Strategy: validation

Validate before calling

import asyncio
if asyncio.get_event_loop().is_running():
    raise RuntimeError("use async iteration inside async code")

Type guard

def can_use_sync_iter() -> bool:
    try:
        asyncio.get_running_loop()
        return False
    except RuntimeError:
        return True

Try / catch

try:
    for item in async_to_sync_iter(aiter):
        process(item)
except RuntimeError as e:
    if "running event loop" in str(e):
        run_in_thread(lambda: consume_sync(aiter))

Prevention

When it happens

Trigger: Calling async_to_sync_iter(...) (directly or via __iter__ of the adapter) inside an async def function or any code running on an active asyncio loop — e.g. `for x in async_to_sync_iter(aiter())` inside async code.

Common situations: Using the sync adapter inside a Jupyter notebook or FastAPI handler (both run a loop); wrapping an async iterator in sync code that was itself made async; mixing asyncio.run with an outer loop.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/3136fa72bece66d3. Report an issue: GitHub.