zylon-ai/private-gpt · error · AsyncIteratorError

Async iterator conversion failed: {e!s}

Error message

Async iterator conversion failed: {e!s}

What it means

The outermost safety net of to_async_iterator: any exception escaping the conversion loop that is not caught by the inner handlers (iterator next, transform) and is not asyncio.CancelledError is re-raised as AsyncIteratorError('Async iterator conversion failed: ...') with the original exception chained. It usually indicates an infrastructure failure (executor shutdown, awaiting run_in_executor on a closed loop, chunk handling bug) rather than a data problem.

Source

Thrown at private_gpt/utils/async_utils.py:94

            for item in chunk:
                try:
                    if transform_fn:
                        # Run transform in executor if it's CPU-intensive
                        result = await loop.run_in_executor(
                            internal_executor, transform_fn, item
                        )
                        yield result
                    else:
                        yield item
                except Exception as e:
                    raise AsyncIteratorError(
                        f"Item transformation failed: {e!s}"
                    ) from e

    except asyncio.CancelledError:
        raise
    except Exception as e:
        raise AsyncIteratorError(f"Async iterator conversion failed: {e!s}") from e
    finally:
        if not executor:
            internal_executor.shutdown(wait=False)

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Read the chained cause (__cause__) - the real failure is the original exception, not this wrapper
  2. Ensure the async generator is fully consumed or properly closed (aclose()) before shutting down the loop/app
  3. Do not share a ThreadPoolExecutor across components with different lifetimes; pass executor=None to let the wrapper own and shut down its own executor
  4. In web handlers, wrap consumption in try/except AsyncIteratorError and convert to a clean 500/stream-abort response

Example fix

# before
ait = to_async_iterator(iter(docs), transform_fn=fn, executor=shared_pool)
... later: shared_pool.shutdown(wait=True)  # while ait still running

# after
ait = to_async_iterator(iter(docs), transform_fn=fn)  # wrapper owns executor
try:
    async for item in ait:
        handle(item)
except AsyncIteratorError as e:
    logger.error(f"stream failed: {e}", exc_info=e.__cause__)
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try:
    async for item in to_async_iterator(it, transform_fn=fn):
        handle(item)
except asyncio.CancelledError:
    raise  # let cancellation propagate; wrapper re-raises it untouched
except AsyncIteratorError as e:
    logger.error("stream infrastructure failure: %s", e.__cause__)
    # convert to clean abort; do not retry blindly

Prevention

When it happens

Trigger: The event loop being closed while the generator is still running; the supplied ThreadPoolExecutor being shut down externally mid-iteration; cancellation racing the executor (CancelledError is re-raised untouched, but adjacent errors surface here); internal errors like appending to a chunk after the executor raised KeyboardInterrupt-adjacent exceptions.

Common situations: Test suites that close the loop before consuming the async generator fully; FastAPI/uvicorn shutdown while a streaming response backed by to_async_iterator is in flight; sharing one executor across components that shut it down at different lifetimes.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/9ef6462bf56cddb5. Report an issue: GitHub.