cocoindex-io/cocoindex · error · RuntimeError

async_to_sync_iter must not be called from a running event…

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.

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.

Appendix: 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)