{"record":{"id":"66dd7d565c0a7b90","repo":"docling-project/docling","slug":"websocket-status-stream-is-unavailable","errorCode":null,"errorMessage":"WebSocket status stream is unavailable.","messagePattern":"WebSocket status stream is unavailable\\.","errorType":"exception","errorClass":"ServiceUnavailableError","httpStatus":null,"severity":"error","filePath":"docling/service_client/watchers.py","lineNumber":207,"sourceCode":"            raise TaskTimeoutError(\n                f\"Timed out waiting for task {task_id} to emit status updates.\"\n            )\n        return final_status\n\n    def _iter_ws_updates(\n        self, task_id: str, timeout: float\n    ) -> Iterator[TaskStatusResponse]:\n        ws_url = self._ws_url_for_task(task_id)\n        deadline = time.monotonic() + timeout\n\n        for attempt in range(WS_MAX_RECONNECT_ATTEMPTS + 1):\n            try:\n                yield from self._iter_ws_connection(ws_url, task_id, deadline, timeout)\n                return\n            except (ConnectionClosedError, OSError) as exc:\n                remaining = deadline - time.monotonic()\n                if attempt >= WS_MAX_RECONNECT_ATTEMPTS or remaining <= 0:\n                    raise ServiceUnavailableError(\n                        \"WebSocket status stream is unavailable.\", detail=str(exc)\n                    ) from exc\n                delay = min(WS_RECONNECT_BACKOFF_BASE_SECONDS * (2**attempt), remaining)\n                _logger.warning(\n                    \"WebSocket connection dropped for task %s: %s — reconnecting in %.1fs\",\n                    task_id,\n                    exc,\n                    delay,\n                )\n                time.sleep(delay)\n            except (TaskTimeoutError, TaskNotFoundError, ServiceUnavailableError):\n                raise\n            except Exception as exc:\n                raise ServiceUnavailableError(\n                    \"WebSocket status stream is unavailable.\", detail=str(exc)\n                ) from exc\n\n    def _iter_ws_connection(","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/service_client/watchers.py#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"Corporate proxies or ingress controllers with short websocket idle timeouts; docling-serve pod evictions mid-task; flaky Wi-Fi/VPN links during lengthy waits.","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"],"exampleFix":"try:\n    status = watcher.wait_for_terminal(task_id, timeout=1800.0)\nexcept ServiceUnavailableError:\n    # WS budget exhausted; task may still run — re-attach\n    status = watcher.wait_for_terminal(task_id, timeout=1800.0)","handlingStrategy":"fallback","validationCode":null,"typeGuard":"def is_service_unavailable(exc: BaseException) -> bool:\n    return isinstance(exc, ServiceUnavailableError)","tryCatchPattern":"from docling.service_client.exceptions import ServiceUnavailableError\n\ntry:\n    status = watcher.wait_for_terminal(task_id, timeout=timeout_s)\nexcept ServiceUnavailableError as exc:\n    if 'WebSocket status stream' in str(exc):\n        status = poll_fallback.wait_for_terminal(task_id, timeout=timeout_s)","preventionTips":["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"],"tags":["websocket","network","reconnect","service-unavailable","watchers"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}