opendatalab/MinerU · error · RuntimeError

Local API server is already running

Error message

Local API server is already running

What it means

Raised by LocalApiServer.start() when the managed FastAPI subprocess has already been started. The class tracks liveness via self.process, and a second start() call without an intervening stop()/cleanup is a programming error, not an environmental one.

Source

Thrown at mineru/cli/api_client.py:482

    queued_ahead: int | None = None


class LocalAPIServer:
    def __init__(self, extra_cli_args: Sequence[str] = ()):
        self.temp_dir = tempfile.TemporaryDirectory(prefix="mineru-api-client-")
        self.temp_root = Path(self.temp_dir.name)
        self.output_root = self.temp_root / "output"
        self.base_url: str | None = None
        self.process: ManagedProcess | None = None
        self._atexit_registered = False
        self.extra_cli_args = tuple(extra_cli_args)
        self._launch_mode = LOCAL_API_LAUNCH_MODE_SUBPROCESS
        self._managed_process_group_id: int | None = None
        self._use_stdin_shutdown_watcher = False

    def start(self) -> str:
        if self.process is not None:
            raise RuntimeError("Local API server is already running")

        resolved_port = find_free_port()
        self.base_url = f"http://127.0.0.1:{resolved_port}"
        self._launch_mode = get_effective_local_api_launch_mode()
        _validate_local_api_launch_mode_platform(self._launch_mode)
        # On Windows, the temporary FastAPI child process can stall during
        # parsing startup when launched with stdin=PIPE and an EOF-based
        # shutdown watcher, so we only enable that path on non-Windows
        # subprocess launches.
        self._use_stdin_shutdown_watcher = (
            self._launch_mode == LOCAL_API_LAUNCH_MODE_SUBPROCESS and os.name != "nt"
        )
        env, unset_env_names = _build_local_api_server_env(
            self.output_root,
            use_stdin_shutdown_watcher=self._use_stdin_shutdown_watcher,
        )
        if self._launch_mode == LOCAL_API_LAUNCH_MODE_SUBPROCESS:
            stdin_target = subprocess.PIPE

View on GitHub (pinned to 4fe4bde114)

Solutions

  1. Call stop()/cleanup (and wait for it to finish) before starting again
  2. Reuse the running server: check client.base_url / whether the process is alive instead of calling start() again
  3. Create a fresh LocalApiServer instance for each lifecycle instead of reusing one
  4. Register the atexit/on-failure cleanup so exception paths do not leave a half-alive server object

Example fix

# before
server.start()  # ... later, same instance
server.start()  # RuntimeError

# after
if server.process is None:
    server.start()
Defensive patterns

Strategy: type-guard

Validate before calling

if server.process is not None:
    print(f"server already running at {server.base_url}")
else:
    url = server.start()

Type guard

def is_stopped(server: LocalApiServer) -> bool:
    """True when the server can be safely started."""
    return server.process is None

Try / catch

try:
    server.start()
except RuntimeError as e:
    if "already running" not in str(e):
        raise
    # reuse the existing instance

Prevention

When it happens

Trigger: Calling client.start() twice on the same LocalApiServer instance; calling start() after a previous start succeeded but stop() was never invoked (e.g. an exception path skipped cleanup); reusing a server object across retries without resetting it.

Common situations: Retry/decorator wrappers that transparently re-invoke the function that starts the server; notebooks where a cell starting the server is re-run; long-lived scripts that loop over documents and start a fresh server per iteration on the same object.

Related errors


AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14). Data as JSON: /api/errors/cf2f1550153a7535. Report an issue: GitHub.