666ghj/MiroFish · error · ValueError
模拟已在运行或结束处理中: {simulation_id}
Error message
模拟已在运行或结束处理中: {simulation_id} What it means
Raised in SimulationRunner's start path under a per-simulation finalization lock: if existing run state has runner_status in {STARTING, RUNNING, PAUSED, STOPPING}, or ZepGraphMemoryManager still has an updater registered for the simulation, it raises ValueError('模拟已在运行或结束处理中'). This is a deliberate fail-closed concurrency guard against double-start and against racing the shutdown/finalization path.
Source
Thrown at backend/app/services/simulation_runner.py:437
total_simulation_hours=total_hours,
started_at=datetime.now().isoformat(),
)
# Atomically claim this simulation ID. The expensive updater/process
# startup happens after releasing the lock, while the persisted
# STARTING state makes every concurrent start fail closed.
with cls._finalization_lock(simulation_id):
existing = cls.get_run_state(simulation_id)
active_statuses = {
RunnerStatus.STARTING,
RunnerStatus.RUNNING,
RunnerStatus.PAUSED,
RunnerStatus.STOPPING,
}
if (
existing and existing.runner_status in active_statuses
) or ZepGraphMemoryManager.get_updater(simulation_id) is not None:
raise ValueError(f"模拟已在运行或结束处理中: {simulation_id}")
cls._save_run_state(state)
# 如果启用图谱记忆更新,创建更新器
if enable_graph_memory_update:
if not graph_id:
raise ValueError("启用图谱记忆更新时必须提供 graph_id")
try:
ZepGraphMemoryManager.create_updater(simulation_id, graph_id)
cls._graph_memory_enabled[simulation_id] = True
logger.info(f"已启用图谱记忆更新: simulation_id={simulation_id}, graph_id={graph_id}")
except Exception as e:
logger.error(f"创建图谱记忆更新器失败: {e}")
cls._graph_memory_enabled[simulation_id] = False
state.runner_status = RunnerStatus.FAILED
state.error = f"Zep图谱更新器初始化失败: {e}"
with cls._finalization_lock(simulation_id):
cls._save_run_state(state)View on GitHub (pinned to b5b53acc57)
Solutions
- Stop the existing run first (stop endpoint) and wait for runner_status to leave the active set before starting again.
- If the status is stale after a crash, stop/cleanup the run state (or reset runner_status to a terminal value via the recovery path) then start.
- For a PAUSED run, use the resume flow rather than start.
- If stuck in STOPPING, check and kill the simulation subprocess, then let finalization complete.
Example fix
# before
SimulationRunner.start_simulation(sim_id, ...)
# after
state = SimulationRunner.get_run_state(sim_id)
if state and state.runner_status in {'STARTING', 'RUNNING', 'PAUSED', 'STOPPING'}:
SimulationRunner.stop_simulation(sim_id)
# wait for terminal status / cleanup, then start
SimulationRunner.start_simulation(sim_id, ...) Defensive patterns
Strategy: validation
Validate before calling
active = {'STARTING', 'RUNNING', 'PAUSED', 'STOPPING'}
state = SimulationRunner.get_run_state(sim_id)
if state and state.runner_status in active:
SimulationRunner.stop_simulation(sim_id) # or resume if PAUSED
wait_for_terminal_status(sim_id) Try / catch
try:
SimulationRunner.start_simulation(sim_id, ...)
except ValueError as e:
if '已在运行' in str(e):
raise HTTPException(409, str(e)) from e # conflict: stop or resume first
raise Prevention
- Map this ValueError to HTTP 409 so clients can distinguish double-start from bad input.
- Provide crash recovery: reset stale RUNNING state on backend startup if no subprocess exists.
- Use resume for PAUSED runs; reserve start for terminal states.
When it happens
Trigger: Calling start twice (double-click, frontend retry, duplicate webhook); starting while a previous run is PAUSED or mid-STOPPING; a stale run state left RUNNING after a crash or backend kill -9; an orphaned ZepGraphMemoryManager updater left behind by a failed prior stop.
Common situations: Backend crashed without finalizing state, leaving status RUNNING on disk; user resumes from a paused run by calling start instead of resume; two API replicas both accepting a start request; STOPPING stuck because the subprocess is hung.
Related errors
- 模拟未在运行: {simulation_id}, status={state.runner_status}
- 模拟停止未达到终态: {simulation_id}, status={state.runner_status}
- Graph {graph_id} is in use by active consumer(s): {', '.join
- 等待命令响应超时 ({timeout}秒)
- 模拟不存在: {simulation_id}
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/ea12daad5413a9c8.
Report an issue: GitHub.