{"record":{"id":"00ff212c25f1fd57","repo":"opendatalab/MinerU","slug":"timed-out-waiting-for-local-worker-server-id-to","errorCode":null,"errorMessage":"Timed out waiting for local worker {server_id} to become healthy","messagePattern":"Timed out waiting for local worker (.+?) to become healthy","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"mineru/cli/router.py","lineNumber":475,"sourceCode":"        assert self.base_url is not None\n        deadline = asyncio.get_running_loop().time() + timeout_seconds\n        last_error: str | None = None\n        while asyncio.get_running_loop().time() < deadline:\n            if self.process is not None and self.process.poll() is not None:\n                raise RuntimeError(f\"Local worker {self.server_id} exited before becoming healthy\")\n            try:\n                response = await client.get(f\"{self.base_url}{HEALTH_ENDPOINT}\")\n                if response.status_code == 200:\n                    return\n                last_error = response_detail(response)\n            except httpx.HTTPError as exc:\n                last_error = str(exc)\n            await asyncio.sleep(TASK_STATUS_POLL_INTERVAL_SECONDS)\n\n        message = f\"Timed out waiting for local worker {self.server_id} to become healthy\"\n        if last_error:\n            message = f\"{message}: {last_error}\"\n        raise RuntimeError(message)\n\n    async def restart(self, client: httpx.AsyncClient) -> None:\n        self.stop()\n        await self.start(client)\n\n    def stop(self) -> None:\n        process = self.process\n        process_group_id = self.process_group_id\n        self.process = None\n        self.process_group_id = None\n        try:\n            if process is not None or process_group_id is not None:\n                stop_managed_process(\n                    process,\n                    process_group_id=process_group_id,\n                    shutdown_timeout_seconds=5,\n                    use_stdin_shutdown_watcher=False,\n                )","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/opendatalab/MinerU/blob/4fe4bde114a23ee5dd637eae99b767f4669bf58c/mineru/cli/router.py#L457-L493","documentation":"RuntimeError raised by LocalWorker.wait_until_ready in router.py: the worker subprocess is still alive but its /health endpoint has not returned 200 within LOCAL_API_STARTUP_TIMEOUT_SECONDS. The message includes the last observed error (non-200 detail or httpx transport error). Typical when startup work — downloading model weights, warming the VLM, loading pipelines — takes longer than the fixed timeout.","triggerScenarios":"First run downloading multi-GB model files over a slow link; cold model cache on a new machine/container; slow disk (network volume) inflating load time; health checks failing with connection refused because binding is delayed; CPU-only machines loading VLM backends very slowly.","commonSituations":"See trigger scenarios.","solutions":["Pre-download/warm the models once (run the mineru CLI on a sample file) so worker startup is fast afterwards.","Increase LOCAL_API_STARTUP_TIMEOUT_SECONDS (or the corresponding constant/env in your version) if your hardware legitimately needs longer.","Read the trailing ': {last_error}' in the message — connection errors mean not-yet-listening; 5xx means the app is up but unhealthy (fix that).","Put model caches on fast local storage and keep them across container restarts (volume-mount ~/.cache or MINERU_MODEL_PATH)."],"exampleFix":"# before\n# RuntimeError: Timed out waiting for local worker 1 to become healthy: [Errno 111] Connection refused\n# (first run, downloading models)\n\n# after\n# 1) warm cache once:\n#    mineru -p sample.pdf -o /tmp/warmup\n# 2) persist the model cache in Docker:\n#    docker run -v mineru-cache:/root/.cache ... ","handlingStrategy":"retry","validationCode":"import httpx, time\n\ndef worker_will_be_ready(base_url: str, budget_s: float) -> bool:\n    deadline = time.time() + budget_s\n    while time.time() < deadline:\n        try:\n            if httpx.get(f'{base_url}/health', timeout=2).status_code == 200:\n                return True\n        except httpx.HTTPError:\n            pass\n        time.sleep(1)\n    return False","typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        await worker.wait_until_ready(client)\n        break\n    except RuntimeError as exc:\n        if 'Timed out' not in str(exc) or attempt == 2:\n            raise\n        await asyncio.sleep(10)  # cold model cache warms on later attempts","preventionTips":["Warm model caches with a one-off CLI run before starting the router in fresh environments.","Persist model cache directories across container rebuilds (volume mounts).","Size LOCAL_API_STARTUP_TIMEOUT_SECONDS to your hardware; slow disks and CPU-only VLM loading need much more headroom."],"tags":["mineru","timeout","startup","model-download","worker-communication"],"backgroundTag":null,"analyzedSha":"4fe4bde114a23ee5dd637eae99b767f4669bf58c","analyzedAt":"2026-08-14T21:29:18.456Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}