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
- 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.
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
- 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.
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
- 模拟未在运行: {simulation_id}, status={state.runner_status}
- 模拟仍在停止中,图谱写入未在 {wait_timeout:.0f}s 内完成
- Zep图谱写入未完整完成: {error}
- 模拟停止失败
- 模拟停止未达到终态: {simulation_id}, status={state.runner_status}
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/e55e486eb8267ccd.
Report an issue: GitHub.