Local API Server Lifecycle Management#
When the mineru CLI converts documents without a pre-existing server (--api-url is not provided), it automatically spawns a temporary mineru-api process, waits for it to become healthy, uses it for all parsing tasks, and then tears it down. The full lifecycle is managed by two classes in mineru/cli/api_client.py: LocalAPIServer (one-shot) and ReusableLocalAPIServer (singleton wrapper for the Gradio UI).
Startup Sequencing#
LocalAPIServer.start() performs startup in this order:
- Port allocation —
find_free_port()binds127.0.0.1:0to get a guaranteed-free ephemeral port. The server is always bound to loopback only. - Launch mode selection —
get_effective_local_api_launch_mode()resolves tosubprocess(default) orspawn.spawnis forced whenMINERU_LMDEPLOY_DEVICEis in theMINERU_SPAWN_DEVICE_LIST(currently["ascend"]), and can be overridden viaMINERU_LOCAL_API_LAUNCH_MODE. - Environment construction —
_build_local_api_server_env()setsMINERU_API_OUTPUT_ROOT,MINERU_API_MAX_CONCURRENT_REQUESTS,MINERU_API_DISABLE_ACCESS_LOG=1, and conditionallyMINERU_API_SHUTDOWN_ON_STDIN_EOF=1(non-Windows subprocess mode only). - Process launch:
- subprocess mode: Spawns
python -m mineru.cli.fast_api --host 127.0.0.1 --port <port> [extra_args]viasubprocess.Popenwithstart_new_session=True(POSIX) orCREATE_NEW_PROCESS_GROUP(Windows) . - spawn mode: Uses
multiprocessing.get_context("spawn").Processtargeting_run_local_api_via_spawn(), which callsos.setsid()then imports and callsfast_api.maindirectly. Limited to Linux only .
- subprocess mode: Spawns
atexitregistration —atexit.register(self.stop)is called once after the process is launched, guaranteeing cleanup even on unhandled exit .
In run_orchestrated_cli, the CLI waits for readiness before submitting any task: it creates a LocalAPIServer, calls start(), then awaits wait_for_local_api_ready() before planning or submitting any parse tasks.
Health Checks#
wait_for_local_api_ready() polls GET /health every 1 second until one of three outcomes:
| Outcome | Trigger | Effect |
|---|---|---|
| Ready | /health returns status == "healthy" and protocol_version == API_PROTOCOL_VERSION | Returns ServerHealth; CLI proceeds |
| Early exit | Process exits before becoming healthy | Raises ClickException("Local mineru-api exited before becoming healthy.") |
| Timeout | Deadline exceeded | Raises ClickException("Timed out waiting for local mineru-api to become healthy.") |
The startup timeout defaults to 300 seconds and is configurable via MINERU_LOCAL_API_STARTUP_TIMEOUT_SECONDS (minimum 1.0 s) .
validate_server_health_payload() enforces two conditions before any traffic is sent:
status == "healthy"(server task manager is running)protocol_version == API_PROTOCOL_VERSION(currently2) — a version mismatch raises a hard error, not a warning .
Shutdown Conditions and Sequencing#
LocalAPIServer.stop() is called from the finally block in run_orchestrated_cli and also via atexit. It delegates to stop_managed_process() (subprocess mode) or _stop_spawn_managed_process() (spawn mode), with a 10-second shutdown timeout (LOCAL_API_SHUTDOWN_TIMEOUT_SECONDS) .
subprocess mode shutdown sequence#
- If
MINERU_API_SHUTDOWN_ON_STDIN_EOF=1was set (non-Windows only): close the process'sstdinpipe and wait up to 10 s for graceful exit. - If still running: send
SIGTERMto the entire process group (os.killpg) and wait up to 10 s. - If still running: send
SIGKILLand wait up to 10 s. A warning is logged if the process still hasn't exited. - On Windows,
taskkill /PID /T(graceful) or/F(forceful) replaces the POSIX signals .
After the process stops, _cleanup_temp_dir() removes the temporary directory with up to 8 retries at 0.25-second intervals (LOCAL_API_CLEANUP_RETRIES, LOCAL_API_CLEANUP_RETRY_INTERVAL_SECONDS) . If all retries fail, a warning is logged with the path for manual cleanup.
The atexit handler is unregistered inside stop() to prevent double-invocation .
ReusableLocalAPIServer (Gradio UI)#
The Gradio UI holds a module-level ReusableLocalAPIServer singleton (_gradio_local_api_server). Unlike the CLI's one-shot LocalAPIServer, this wrapper:
ensure_started()checks if an existing server process is still running; if not, it stops the dead instance and starts a fresh one. Thread-safe via an internalthreading.Lock.configure()updatesextra_cli_args; if the process is already dead, it callsstop()and clears the reference so the nextensure_started()re-launches cleanly.
The Gradio path is not triggered when --api-url is provided (the local process is never started in that case).
Key Constants and Environment Variables#
| Name | Default | Description |
|---|---|---|
MINERU_LOCAL_API_STARTUP_TIMEOUT_SECONDS | 300 s | Max wait for /health to return healthy |
MINERU_TASK_RESULT_TIMEOUT_SECONDS | 3600 s | Max wait for a task to complete |
LOCAL_API_SHUTDOWN_TIMEOUT_SECONDS | 10 s | Per-step wait during shutdown |
LOCAL_API_CLEANUP_RETRIES | 8 | Temp dir cleanup retry count |
MINERU_LOCAL_API_LAUNCH_MODE | subprocess | Override launch mode (subprocess or spawn) |
MINERU_API_SHUTDOWN_ON_STDIN_EOF | unset | When set by the client, server watches for stdin EOF to trigger graceful shutdown |
MINERU_LMDEPLOY_DEVICE | unset | Set to ascend to auto-select spawn launch mode |