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
- Read the chained cause (__cause__) - the real failure is the original exception, not this wrapper
- Ensure the async generator is fully consumed or properly closed (aclose()) before shutting down the loop/app
- Do not share a ThreadPoolExecutor across components with different lifetimes; pass executor=None to let the wrapper own and shut down its own executor
- 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
- Fully consume or explicitly aclose() the async generator before app/loop shutdown
- Do not share a ThreadPoolExecutor with components that may shut it down early; let the wrapper create its own
- In streaming HTTP handlers, map AsyncIteratorError to a clean response termination
- Always inspect e.__cause__ - the wrapper message hides the real exception class
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
- Iterator next() operation failed: {e!s}
- Item transformation failed: {e!s}
- Server is already running with PID {existing_pid}
- Redis semaphore dependencies are not installed. Install with
- Unsupported semaphore mode: {mode!r}. Available: {', '.join(
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/9ef6462bf56cddb5.
Report an issue: GitHub.