666ghj/MiroFish · error · RuntimeError

模拟停止未达到终态: {simulation_id}, status={state.runner_status}

Error message

模拟停止未达到终态: {simulation_id}, status={state.runner_status}

What it means

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.

Source

Thrown at backend/app/services/simulation_runner.py:1079

                        )
                        raise RuntimeError(state.error) from error
                state.runner_status = RunnerStatus.STOPPED
                state.twitter_running = False
                state.reddit_running = False
                state.completed_at = datetime.now().isoformat()
                state.error = None
                cls._save_run_state(state)
                cls._sync_simulation_status(
                    simulation_id,
                    RunnerStatus.STOPPED,
                )
                cls._manual_stop_requests.discard(simulation_id)

        state = cls.get_run_state(simulation_id) or state
        if state.runner_status == RunnerStatus.FAILED:
            raise RuntimeError(state.error or "模拟停止失败")
        if state.runner_status != RunnerStatus.STOPPED:
            raise RuntimeError(
                f"模拟停止未达到终态: {simulation_id}, status={state.runner_status}"
            )

        logger.info(f"模拟已停止: {simulation_id}")
        return state

    @classmethod
    def _read_actions_from_file(
        cls,
        file_path: str,
        default_platform: Optional[str] = None,
        platform_filter: Optional[str] = None,
        agent_id: Optional[int] = None,
        round_num: Optional[int] = None
    ) -> List[AgentAction]:
        """
        从单个动作文件中读取动作
        

View on GitHub (pinned to b5b53acc57)

Solutions

  1. 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.
  2. 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.
  3. Report the stuck status value — it identifies which code path failed to reach a terminal state.

Example fix

// before
try:
    SimulationRunner.stop_simulation(sim_id)
except RuntimeError as e:
    raise  // 模拟停止未达到终态 ... status=RunnerStatus.STOPPING

// after
try:
    SimulationRunner.stop_simulation(sim_id)
except RuntimeError as e:
    if "未达到终态" not in str(e):
        raise
    state = SimulationRunner.wait_terminal_state(sim_id, poll=5.0, max_wait=300)
    if state.runner_status not in (RunnerStatus.STOPPED, RunnerStatus.FAILED):
        SimulationRunner.force_cleanup(sim_id)  # last resort
Defensive patterns

Strategy: retry

Try / catch

try:
    SimulationRunner.stop_simulation(sim_id)
except RuntimeError as e:
    if "未达到终态" not in str(e):
        raise
    state = bounded_poll_terminal(sim_id, max_wait=300)
    if state.runner_status not in (RunnerStatus.STOPPED, RunnerStatus.FAILED):
        escalate_stuck_run(sim_id, state.runner_status)  # force cleanup path

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/b30a34f8f369cafc. Report an issue: GitHub.