opendatalab/MinerU · error · RuntimeError
Timed out waiting for local worker {server_id} to become hea
Error message
Timed out waiting for local worker {server_id} to become healthy What it means
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.
Source
Thrown at mineru/cli/router.py:475
assert self.base_url is not None
deadline = asyncio.get_running_loop().time() + timeout_seconds
last_error: str | None = None
while asyncio.get_running_loop().time() < deadline:
if self.process is not None and self.process.poll() is not None:
raise RuntimeError(f"Local worker {self.server_id} exited before becoming healthy")
try:
response = await client.get(f"{self.base_url}{HEALTH_ENDPOINT}")
if response.status_code == 200:
return
last_error = response_detail(response)
except httpx.HTTPError as exc:
last_error = str(exc)
await asyncio.sleep(TASK_STATUS_POLL_INTERVAL_SECONDS)
message = f"Timed out waiting for local worker {self.server_id} to become healthy"
if last_error:
message = f"{message}: {last_error}"
raise RuntimeError(message)
async def restart(self, client: httpx.AsyncClient) -> None:
self.stop()
await self.start(client)
def stop(self) -> None:
process = self.process
process_group_id = self.process_group_id
self.process = None
self.process_group_id = None
try:
if process is not None or process_group_id is not None:
stop_managed_process(
process,
process_group_id=process_group_id,
shutdown_timeout_seconds=5,
use_stdin_shutdown_watcher=False,
)View on GitHub (pinned to 4fe4bde114)
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).
Example fix
# before # RuntimeError: Timed out waiting for local worker 1 to become healthy: [Errno 111] Connection refused # (first run, downloading models) # after # 1) warm cache once: # mineru -p sample.pdf -o /tmp/warmup # 2) persist the model cache in Docker: # docker run -v mineru-cache:/root/.cache ...
Defensive patterns
Strategy: retry
Validate before calling
import httpx, time
def worker_will_be_ready(base_url: str, budget_s: float) -> bool:
deadline = time.time() + budget_s
while time.time() < deadline:
try:
if httpx.get(f'{base_url}/health', timeout=2).status_code == 200:
return True
except httpx.HTTPError:
pass
time.sleep(1)
return False Try / catch
for attempt in range(3):
try:
await worker.wait_until_ready(client)
break
except RuntimeError as exc:
if 'Timed out' not in str(exc) or attempt == 2:
raise
await asyncio.sleep(10) # cold model cache warms on later attempts Prevention
- 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.
When it happens
Trigger: 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.
Common situations: See trigger scenarios.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- {payload_name} is not valid JSON
- {payload_name} must be a JSON object
- Local worker {server_id} exited before becoming healthy
- Unsupported protocol_version={protocol_version}, expected {A
- max_concurrent_requests must be a positive integer
AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14).
Data as JSON: /api/errors/00ff212c25f1fd57.
Report an issue: GitHub.