Description of the bug | 错误描述
When a vLLM EngineCore used by a router-managed local mineru-api worker dies, the current parsing task fails with EngineDeadError, but the worker continues to report HTTP 200 from /health.
As a result, mineru-router continues to consider the local worker healthy and does not restart it. Every subsequent Hybrid/VLM parsing task fails immediately with the same EngineDeadError until the container or Pod is restarted manually.
This appears to be a worker lifecycle/health-reporting issue rather than a request-specific parsing issue.
Observed sequence:
- A local GPU worker is started by
mineru-router.
- vLLM
EngineCore encounters a fatal error and exits.
- The active parsing task fails:
Hybrid processing window 1/1: pages 1-12/12 (12 pages)
Two Step Extraction: 0%| | 0/12 [00:00<?, ?it/s]
vllm.v1.engine.exceptions.EngineDeadError:
EngineCore encountered an issue. See stack trace (above) for the root cause.
- The HTTP process remains alive and
/health still returns 200:
GET /health HTTP/1.1" 200 OK
GET /tasks/<task-id> HTTP/1.1" 200 OK
- New parsing tasks are still routed to this worker and fail immediately.
- Restarting the Pod restores parsing.
In AsyncTaskManager._process_task, a task exception updates only the task status:
except Exception as exc:
task.status = TASK_FAILED
task.error = str(exc)
task.completed_at = utc_now_iso()
self._signal_task_event(task_id)
logger.exception(f"Async task failed: {task_id}")
It does not set last_worker_error, so AsyncTaskManager.is_healthy() continues to return true after EngineDeadError.
The Router already contains recovery logic that restarts a managed local worker after repeated health-check failures. That recovery path is not activated because the internal worker health endpoint remains healthy.
The same behavior is present in the mineru-3.3.1-released source:
mineru/cli/fast_api.py: _process_task catches the exception without marking the worker unhealthy.
mineru/cli/fast_api.py: /health relies on AsyncTaskManager.is_healthy().
mineru/cli/router.py: the local worker is restarted after repeated non-200 health responses.
Expected behavior
After an unrecoverable inference-engine error such as vllm.v1.engine.exceptions.EngineDeadError, one of the following should happen:
- The internal
mineru-api worker marks itself unhealthy, causing /health to return 503 so mineru-router restarts it.
- The internal worker process exits, allowing
mineru-router to detect and restart it.
- The VLM engine is recreated inside the worker.
The Router should stop routing tasks to a worker whose inference engine is permanently dead.
Suggested direction
Treat EngineDeadError as a worker-fatal error, while keeping ordinary document-specific parsing failures task-local.
For example, _process_task could record a fatal worker error when it catches EngineDeadError:
except Exception as exc:
task.status = TASK_FAILED
task.error = str(exc)
task.completed_at = utc_now_iso()
self._signal_task_event(task_id)
if isinstance(exc, EngineDeadError):
self.last_worker_error = str(exc)
self._wake_waiters()
logger.exception(f"Async task failed: {task_id}")
The exact implementation may need a backend-neutral fatal-error classification so that importing vLLM remains optional.
It would also be useful to add a regression test covering:
- a normal document parsing exception keeps the worker healthy;
EngineDeadError makes the worker health endpoint return 503;
mineru-router restarts a managed local worker after this failure.
How to reproduce the bug | 如何复现
Environment:
MinerU: 3.2.0
vLLM: 0.11.2
Python: 3.12
OS: Linux container on Kubernetes
Device: NVIDIA CUDA GPU
Router: mineru-router --local-gpus auto
Backend: hybrid-auto-engine
Steps:
- Start a Router-managed local GPU worker:
mineru-router \
--host 0.0.0.0 \
--port 8002 \
--local-gpus auto \
--gpu-memory-utilization 0.7
- Submit a PDF using the task API with
backend=hybrid-auto-engine.
- Cause or simulate a fatal vLLM EngineCore failure during inference. In production this was observed during the first
Two Step Extraction request.
- Poll the failed task and observe
EngineDeadError.
- Request
/health; it still returns HTTP 200.
- Submit another PDF. It fails immediately because the same dead engine remains attached to the worker.
- Restart the Router container. Parsing works again.
A deterministic unit/integration reproduction can replace the predictor call with one that raises vllm.v1.engine.exceptions.EngineDeadError, then assert that the worker health endpoint changes to 503.
Description of the bug | 错误描述
When a vLLM
EngineCoreused by a router-managed localmineru-apiworker dies, the current parsing task fails withEngineDeadError, but the worker continues to report HTTP 200 from/health.As a result,
mineru-routercontinues to consider the local worker healthy and does not restart it. Every subsequent Hybrid/VLM parsing task fails immediately with the sameEngineDeadErroruntil the container or Pod is restarted manually.This appears to be a worker lifecycle/health-reporting issue rather than a request-specific parsing issue.
Observed sequence:
mineru-router.EngineCoreencounters a fatal error and exits./healthstill returns 200:In
AsyncTaskManager._process_task, a task exception updates only the task status:It does not set
last_worker_error, soAsyncTaskManager.is_healthy()continues to return true afterEngineDeadError.The Router already contains recovery logic that restarts a managed local worker after repeated health-check failures. That recovery path is not activated because the internal worker health endpoint remains healthy.
The same behavior is present in the
mineru-3.3.1-releasedsource:mineru/cli/fast_api.py:_process_taskcatches the exception without marking the worker unhealthy.mineru/cli/fast_api.py:/healthrelies onAsyncTaskManager.is_healthy().mineru/cli/router.py: the local worker is restarted after repeated non-200 health responses.Expected behavior
After an unrecoverable inference-engine error such as
vllm.v1.engine.exceptions.EngineDeadError, one of the following should happen:mineru-apiworker marks itself unhealthy, causing/healthto return 503 somineru-routerrestarts it.mineru-routerto detect and restart it.The Router should stop routing tasks to a worker whose inference engine is permanently dead.
Suggested direction
Treat
EngineDeadErroras a worker-fatal error, while keeping ordinary document-specific parsing failures task-local.For example,
_process_taskcould record a fatal worker error when it catchesEngineDeadError:The exact implementation may need a backend-neutral fatal-error classification so that importing vLLM remains optional.
It would also be useful to add a regression test covering:
EngineDeadErrormakes the worker health endpoint return 503;mineru-routerrestarts a managed local worker after this failure.How to reproduce the bug | 如何复现
Environment:
Steps:
backend=hybrid-auto-engine.Two Step Extractionrequest.EngineDeadError./health; it still returns HTTP 200.A deterministic unit/integration reproduction can replace the predictor call with one that raises
vllm.v1.engine.exceptions.EngineDeadError, then assert that the worker health endpoint changes to 503.