{"record":{"id":"cf2f1550153a7535","repo":"opendatalab/MinerU","slug":"local-api-server-is-already-running","errorCode":null,"errorMessage":"Local API server is already running","messagePattern":"Local API server is already running","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"mineru/cli/api_client.py","lineNumber":482,"sourceCode":"    queued_ahead: int | None = None\n\n\nclass LocalAPIServer:\n    def __init__(self, extra_cli_args: Sequence[str] = ()):\n        self.temp_dir = tempfile.TemporaryDirectory(prefix=\"mineru-api-client-\")\n        self.temp_root = Path(self.temp_dir.name)\n        self.output_root = self.temp_root / \"output\"\n        self.base_url: str | None = None\n        self.process: ManagedProcess | None = None\n        self._atexit_registered = False\n        self.extra_cli_args = tuple(extra_cli_args)\n        self._launch_mode = LOCAL_API_LAUNCH_MODE_SUBPROCESS\n        self._managed_process_group_id: int | None = None\n        self._use_stdin_shutdown_watcher = False\n\n    def start(self) -> str:\n        if self.process is not None:\n            raise RuntimeError(\"Local API server is already running\")\n\n        resolved_port = find_free_port()\n        self.base_url = f\"http://127.0.0.1:{resolved_port}\"\n        self._launch_mode = get_effective_local_api_launch_mode()\n        _validate_local_api_launch_mode_platform(self._launch_mode)\n        # On Windows, the temporary FastAPI child process can stall during\n        # parsing startup when launched with stdin=PIPE and an EOF-based\n        # shutdown watcher, so we only enable that path on non-Windows\n        # subprocess launches.\n        self._use_stdin_shutdown_watcher = (\n            self._launch_mode == LOCAL_API_LAUNCH_MODE_SUBPROCESS and os.name != \"nt\"\n        )\n        env, unset_env_names = _build_local_api_server_env(\n            self.output_root,\n            use_stdin_shutdown_watcher=self._use_stdin_shutdown_watcher,\n        )\n        if self._launch_mode == LOCAL_API_LAUNCH_MODE_SUBPROCESS:\n            stdin_target = subprocess.PIPE","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/opendatalab/MinerU/blob/4fe4bde114a23ee5dd637eae99b767f4669bf58c/mineru/cli/api_client.py#L464-L500","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call stop()/cleanup (and wait for it to finish) before starting again","Reuse the running server: check client.base_url / whether the process is alive instead of calling start() again","Create a fresh LocalApiServer instance for each lifecycle instead of reusing one","Register the atexit/on-failure cleanup so exception paths do not leave a half-alive server object"],"exampleFix":"# before\nserver.start()  # ... later, same instance\nserver.start()  # RuntimeError\n\n# after\nif server.process is None:\n    server.start()","handlingStrategy":"type-guard","validationCode":"if server.process is not None:\n    print(f\"server already running at {server.base_url}\")\nelse:\n    url = server.start()","typeGuard":"def is_stopped(server: LocalApiServer) -> bool:\n    \"\"\"True when the server can be safely started.\"\"\"\n    return server.process is None","tryCatchPattern":"try:\n    server.start()\nexcept RuntimeError as e:\n    if \"already running\" not in str(e):\n        raise\n    # reuse the existing instance","preventionTips":["Always pair start() with a finally: stop()/cleanup()","Wrap server lifecycle in a context manager so retries cannot double-start","In notebooks, keep one server object per kernel and check .process before start"],"tags":["lifecycle","api-server","subprocess","state"],"backgroundTag":null,"analyzedSha":"4fe4bde114a23ee5dd637eae99b767f4669bf58c","analyzedAt":"2026-08-14T21:29:18.456Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}