{"record":{"id":"e55e486eb8267ccd","repo":"666ghj/MiroFish","slug":"simulation-id-e55e48","errorCode":null,"errorMessage":"模拟不存在: {simulation_id}","messagePattern":"模拟不存在: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/app/services/simulation_runner.py","lineNumber":970,"sourceCode":"            \n            # 先发送 SIGTERM 给整个进程组\n            os.killpg(pgid, signal.SIGTERM)\n            \n            try:\n                process.wait(timeout=timeout)\n            except subprocess.TimeoutExpired:\n                # 如果超时后还没结束，强制发送 SIGKILL\n                logger.warning(f\"进程组未响应 SIGTERM，强制终止: {simulation_id}\")\n                os.killpg(pgid, signal.SIGKILL)\n                process.wait(timeout=5)\n    \n    @classmethod\n    def stop_simulation(cls, simulation_id: str) -> SimulationRunState:\n        \"\"\"停止模拟\"\"\"\n        with cls._finalization_lock(simulation_id):\n            state = cls.get_run_state(simulation_id)\n            if not state:\n                raise ValueError(f\"模拟不存在: {simulation_id}\")\n            if state.runner_status == RunnerStatus.STOPPED:\n                return state\n\n            pending_updater = ZepGraphMemoryManager.get_updater(simulation_id)\n            retrying_finalization = (\n                pending_updater is not None\n                and state.runner_status in {\n                    RunnerStatus.STOPPING,\n                    RunnerStatus.FAILED,\n                }\n            )\n            if (\n                state.runner_status not in [\n                    RunnerStatus.STARTING,\n                    RunnerStatus.RUNNING,\n                    RunnerStatus.PAUSED,\n                    RunnerStatus.STOPPING,\n                ]","sourceCodeStart":952,"sourceCodeEnd":988,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/simulation_runner.py#L952-L988","documentation":"Thrown by SimulationRunner.stop_simulation when there is no persisted SimulationRunState for the given simulation_id. The lookup happens under the per-simulation finalization lock, so a stop request for an ID that was never started (or whose run-state file was deleted/cleaned up) fails immediately.","triggerScenarios":"Calling stop_simulation('id') for an ID that was never started in this process, after the run-state was cleaned up on completion, or after a backend restart where in-memory state was lost and no run-state file was restored.","commonSituations":"Stale UI tab or client retrying a stop after the simulation already finished and its state was reaped; typo'd or truncated simulation_id in the URL; calling stop against a different backend instance than the one that started the run.","solutions":["Check the simulation exists first: GET the simulation detail endpoint (or SimulationRunner.get_run_state(simulation_id)) and only call stop when runner_status is an active state.","If you meant to stop a different run, use the ID returned by the list/start endpoint, not a cached one.","Treat '模拟不存在' on stop as already-terminal: many clients can safely ignore it and refresh their view."],"exampleFix":"// before\nSimulationRunner.stop_simulation(sim_id); // may raise for finished/unknown runs\n\n// after\nstate = SimulationRunner.get_run_state(sim_id)\nif state is None:\n    return {\"success\": True, \"message\": \"already gone\"}\nif state.runner_status in (RunnerStatus.STOPPED, RunnerStatus.FAILED):\n    return state\nSimulationRunner.stop_simulation(sim_id);","handlingStrategy":"validation","validationCode":"def stoppable(sim_id: str) -> bool:\n    return SimulationRunner.get_run_state(sim_id) is not None","typeGuard":null,"tryCatchPattern":"try:\n    SimulationRunner.stop_simulation(sim_id)\nexcept ValueError as e:\n    if str(e).startswith(\"模拟不存在\"):\n        return {\"success\": True, \"reason\": \"already gone\"}\n    raise","preventionTips":["Drive the Stop button from live runner_status, not a cached simulation list.","Treat stop as idempotent in clients: unknown ID == nothing to stop.","Ensure stop requests hit the same backend instance that started the run."],"tags":["simulation","stop","state-not-found","lifecycle"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}