666ghj/MiroFish · error · ValueError

模拟不存在: {simulation_id}

Error message

模拟不存在: {simulation_id}

What it means

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.

Source

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

            
            # 先发送 SIGTERM 给整个进程组
            os.killpg(pgid, signal.SIGTERM)
            
            try:
                process.wait(timeout=timeout)
            except subprocess.TimeoutExpired:
                # 如果超时后还没结束,强制发送 SIGKILL
                logger.warning(f"进程组未响应 SIGTERM,强制终止: {simulation_id}")
                os.killpg(pgid, signal.SIGKILL)
                process.wait(timeout=5)
    
    @classmethod
    def stop_simulation(cls, simulation_id: str) -> SimulationRunState:
        """停止模拟"""
        with cls._finalization_lock(simulation_id):
            state = cls.get_run_state(simulation_id)
            if not state:
                raise ValueError(f"模拟不存在: {simulation_id}")
            if state.runner_status == RunnerStatus.STOPPED:
                return state

            pending_updater = ZepGraphMemoryManager.get_updater(simulation_id)
            retrying_finalization = (
                pending_updater is not None
                and state.runner_status in {
                    RunnerStatus.STOPPING,
                    RunnerStatus.FAILED,
                }
            )
            if (
                state.runner_status not in [
                    RunnerStatus.STARTING,
                    RunnerStatus.RUNNING,
                    RunnerStatus.PAUSED,
                    RunnerStatus.STOPPING,
                ]

View on GitHub (pinned to b5b53acc57)

Solutions

  1. 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.
  2. If you meant to stop a different run, use the ID returned by the list/start endpoint, not a cached one.
  3. Treat '模拟不存在' on stop as already-terminal: many clients can safely ignore it and refresh their view.

Example fix

// before
SimulationRunner.stop_simulation(sim_id); // may raise for finished/unknown runs

// after
state = SimulationRunner.get_run_state(sim_id)
if state is None:
    return {"success": True, "message": "already gone"}
if state.runner_status in (RunnerStatus.STOPPED, RunnerStatus.FAILED):
    return state
SimulationRunner.stop_simulation(sim_id);
Defensive patterns

Strategy: validation

Validate before calling

def stoppable(sim_id: str) -> bool:
    return SimulationRunner.get_run_state(sim_id) is not None

Try / catch

try:
    SimulationRunner.stop_simulation(sim_id)
except ValueError as e:
    if str(e).startswith("模拟不存在"):
        return {"success": True, "reason": "already gone"}
    raise

Prevention

When it happens

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

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

Related errors


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