{"record":{"id":"b30a34f8f369cafc","repo":"666ghj/MiroFish","slug":"simulation-id-status-state-runner-s","errorCode":null,"errorMessage":"模拟停止未达到终态: {simulation_id}, status={state.runner_status}","messagePattern":"模拟停止未达到终态: (.+?), status=(.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/simulation_runner.py","lineNumber":1079,"sourceCode":"                        )\n                        raise RuntimeError(state.error) from error\n                state.runner_status = RunnerStatus.STOPPED\n                state.twitter_running = False\n                state.reddit_running = False\n                state.completed_at = datetime.now().isoformat()\n                state.error = None\n                cls._save_run_state(state)\n                cls._sync_simulation_status(\n                    simulation_id,\n                    RunnerStatus.STOPPED,\n                )\n                cls._manual_stop_requests.discard(simulation_id)\n\n        state = cls.get_run_state(simulation_id) or state\n        if state.runner_status == RunnerStatus.FAILED:\n            raise RuntimeError(state.error or \"模拟停止失败\")\n        if state.runner_status != RunnerStatus.STOPPED:\n            raise RuntimeError(\n                f\"模拟停止未达到终态: {simulation_id}, status={state.runner_status}\"\n            )\n\n        logger.info(f\"模拟已停止: {simulation_id}\")\n        return state\n\n    @classmethod\n    def _read_actions_from_file(\n        cls,\n        file_path: str,\n        default_platform: Optional[str] = None,\n        platform_filter: Optional[str] = None,\n        agent_id: Optional[int] = None,\n        round_num: Optional[int] = None\n    ) -> List[AgentAction]:\n        \"\"\"\n        从单个动作文件中读取动作\n        ","sourceCodeStart":1061,"sourceCodeEnd":1097,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/simulation_runner.py#L1061-L1097","documentation":"Raised by stop_simulation when the run state's runner_status is neither FAILED nor STOPPED after the stop sequence finished — the stop did not converge to a terminal state. The message includes the simulation_id and the stuck status so you can tell whether it is stuck in STOPPING, PAUSED, etc.","triggerScenarios":"A competing finalizer (monitor thread) transitioned the state back to a non-terminal value after the synchronous path set STOPPED; a bug in a status writer leaves runner_status as STOPPING/PAUSED; restart-recovery reconstructed a state that no path ever finalizes.","commonSituations":"Races between two stop requests or between stop and the monitor's finalization; partially updated run-state files after a crash; custom code that sets runner_status without completing the terminal transition.","solutions":["Read the status in the message: STOPPING usually means a finalizer is still running — poll get_run_state for a bounded period before concluding it is stuck.","If genuinely stuck, inspect logs of the monitor/finalizer thread and the run-state file for the simulation, then force-cleanup: kill the process group and mark the state STOPPED via the recovery path.","Report the stuck status value — it identifies which code path failed to reach a terminal state."],"exampleFix":"// before\ntry:\n    SimulationRunner.stop_simulation(sim_id)\nexcept RuntimeError as e:\n    raise  // 模拟停止未达到终态 ... status=RunnerStatus.STOPPING\n\n// after\ntry:\n    SimulationRunner.stop_simulation(sim_id)\nexcept RuntimeError as e:\n    if \"未达到终态\" not in str(e):\n        raise\n    state = SimulationRunner.wait_terminal_state(sim_id, poll=5.0, max_wait=300)\n    if state.runner_status not in (RunnerStatus.STOPPED, RunnerStatus.FAILED):\n        SimulationRunner.force_cleanup(sim_id)  # last resort","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    SimulationRunner.stop_simulation(sim_id)\nexcept RuntimeError as e:\n    if \"未达到终态\" not in str(e):\n        raise\n    state = bounded_poll_terminal(sim_id, max_wait=300)\n    if state.runner_status not in (RunnerStatus.STOPPED, RunnerStatus.FAILED):\n        escalate_stuck_run(sim_id, state.runner_status)  # force cleanup path","preventionTips":["Serialize stop requests per simulation (single owner for finalization).","Poll for terminal state with a deadline instead of assuming immediate convergence.","Alert on any run whose status stays non-terminal past the stop deadline."],"tags":["simulation","stop","state-machine","race-condition","stuck-state"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}