infiniflow/ragflow · error · TimeoutError

Operation timed out after

Error message

Operation timed out after {seconds} seconds and {attempts} attempts.

What it means

TimeoutError raised by the async branch of the @timeout decorator in common/connection_utils.py:64-94. For coroutine functions, each of `attempts` iterations runs the call under asyncio.wait_for(seconds) (only when ENABLE_TIMEOUT_ASSERTION is set); after the final attempt times out and neither on_timeout nor exception overrides were supplied, the default TimeoutError is raised. Callers can instead supply on_timeout (fallback value/callback) or exception (an exception class/instance to raise instead), which take precedence over this default.

Solutions

  1. Increase seconds (e.g. via the relevant config for LLM timeout) or attempts so the operation fits the budget.
  2. If a degraded-but-working behavior is acceptable, pass on_timeout=... to the decorator instead of letting the default TimeoutError propagate.
  3. Investigate the downstream dependency (LLM endpoint latency, ES query cost) — the decorator only reports the overrun.
  4. Pass exception=SomeSpecificError if callers need to distinguish this timeout from generic TimeoutErrors.

Example fix

# before
@timeout(seconds=10)
async def chat(...): ...
# after
@timeout(seconds=120, attempts=2, exception=LLMTimeoutError)
async def chat(...): ...
Defensive patterns

Strategy: fallback

Validate before calling

if os.getenv("ENABLE_TIMEOUT_ASSERTION") and expected_p95_latency > configured_seconds:
    raise RuntimeError(f"timeout budget {configured_seconds}s < p95 latency {expected_p95_latency}s — raise @timeout seconds")

Try / catch

try:
    out = await decorated_call()
except TimeoutError as e:
    if "Operation timed out" in str(e):
        return cached_or_default_result  # fallback path
    raise

Prevention

When it happens

Trigger: Awaiting an async function decorated with @timeout(seconds=N, attempts=M, exception=None, on_timeout=None) with ENABLE_TIMEOUT_ASSERTION set, where every asyncio.wait_for attempt exceeds N seconds — e.g. an async LLM/chat-completion call hanging past its budget on all retries.

Common situations: LLM provider slow or stalled (streaming connection open but no tokens); Elasticsearch/Infinity query exceeding the configured budget under load; test environments asserting timeouts; defaults too tight for reasoning-model requests.

Understand the failure class

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/24355bd3065ad954. Report an issue: GitHub.

Appendix: source

Thrown at common/connection_utils.py:86

            for a in range(attempts):
                try:
                    if os.environ.get("ENABLE_TIMEOUT_ASSERTION"):
                        return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
                    else:
                        return await func(*args, **kwargs)
                except asyncio.TimeoutError:
                    if a < attempts - 1:
                        continue
                    if on_timeout is not None:
                        if callable(on_timeout):
                            result = on_timeout()
                            if isinstance(result, Coroutine):
                                return await result
                            return result
                        return on_timeout

                    if exception is None:
                        raise TimeoutError(f"Operation timed out after {seconds} seconds and {attempts} attempts.")

                    if isinstance(exception, BaseException):
                        raise exception

                    if isinstance(exception, type) and issubclass(exception, BaseException):
                        raise exception(f"Operation timed out after {seconds} seconds and {attempts} attempts.")

                    raise RuntimeError("Invalid exception type provided")

        if asyncio.iscoroutinefunction(func):
            return async_wrapper
        return wrapper

    return decorator


async def construct_response(code=RetCode.SUCCESS, message="success", data=None, auth=None):
    result_dict = {"code": code, "message": message, "data": data}

View on GitHub (pinned to 554fb1133a)