docling-project/docling · error · ServiceUnavailableError
WebSocket status stream is unavailable.
Error message
WebSocket status stream is unavailable.
What it means
ServiceUnavailableError raised by WebSocketWatcher._iter_ws_updates when the WS connection drops (ConnectionClosedError/OSError) and either WS_MAX_RECONNECT_ATTEMPTS is exhausted or the overall deadline passed before a successful reconnect. Each failure logs a warning and backs off exponentially (WS_RECONNECT_BACKOFF_BASE_SECONDS * 2**attempt) before retrying.
Source
Thrown at docling/service_client/watchers.py:207
raise TaskTimeoutError(
f"Timed out waiting for task {task_id} to emit status updates."
)
return final_status
def _iter_ws_updates(
self, task_id: str, timeout: float
) -> Iterator[TaskStatusResponse]:
ws_url = self._ws_url_for_task(task_id)
deadline = time.monotonic() + timeout
for attempt in range(WS_MAX_RECONNECT_ATTEMPTS + 1):
try:
yield from self._iter_ws_connection(ws_url, task_id, deadline, timeout)
return
except (ConnectionClosedError, OSError) as exc:
remaining = deadline - time.monotonic()
if attempt >= WS_MAX_RECONNECT_ATTEMPTS or remaining <= 0:
raise ServiceUnavailableError(
"WebSocket status stream is unavailable.", detail=str(exc)
) from exc
delay = min(WS_RECONNECT_BACKOFF_BASE_SECONDS * (2**attempt), remaining)
_logger.warning(
"WebSocket connection dropped for task %s: %s — reconnecting in %.1fs",
task_id,
exc,
delay,
)
time.sleep(delay)
except (TaskTimeoutError, TaskNotFoundError, ServiceUnavailableError):
raise
except Exception as exc:
raise ServiceUnavailableError(
"WebSocket status stream is unavailable.", detail=str(exc)
) from exc
def _iter_ws_connection(View on GitHub (pinned to 61d76f1ff3)
Solutions
- Catch ServiceUnavailableError and re-attach with a fresh iter_updates/wait_for_terminal call — the task itself may still be alive server-side
- Raise websocket idle timeouts on the proxy/ingress (e.g. proxy_read_timeout for nginx)
- Increase the overall wait timeout so the reconnect backoff fits inside the deadline
Example fix
try:
status = watcher.wait_for_terminal(task_id, timeout=1800.0)
except ServiceUnavailableError:
# WS budget exhausted; task may still run — re-attach
status = watcher.wait_for_terminal(task_id, timeout=1800.0) Defensive patterns
Strategy: fallback
Type guard
def is_service_unavailable(exc: BaseException) -> bool:
return isinstance(exc, ServiceUnavailableError) Try / catch
from docling.service_client.exceptions import ServiceUnavailableError
try:
status = watcher.wait_for_terminal(task_id, timeout=timeout_s)
except ServiceUnavailableError as exc:
if 'WebSocket status stream' in str(exc):
status = poll_fallback.wait_for_terminal(task_id, timeout=timeout_s) Prevention
- Raise websocket idle timeouts on proxies/ingress in front of docling-serve
- Keep a PollingWatcher fallback ready for WS-hostile networks
- Give the watcher a deadline large enough to absorb reconnect backoff
When it happens
Trigger: Watching task status over WS /v1/status/ws/{task_id} through a proxy/LB that kills idle or long-lived connections faster than the reconnect budget covers; service restarting; network partition during a long conversion.
Common situations: Corporate proxies or ingress controllers with short websocket idle timeouts; docling-serve pod evictions mid-task; flaky Wi-Fi/VPN links during lengthy waits.
Related errors
- Service transport request failed.
- Task {task_id} was not found.
- URL must contain a valid hostname
- Cannot resolve hostname: {hostname}
- Access to restricted IP address not allowed: {ip}
AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14).
Data as JSON: /api/errors/66dd7d565c0a7b90.
Report an issue: GitHub.