现有组件功能与Amesim组件比较对齐,补全功能;仿真服务启动环境检测,旧版json工程文件适配读取

This commit is contained in:
ljz committed 2026-09-13 20:01:56 +08:00
1 parent b6c22a54f8
commit 6fc9afe41d
119 files changed
+260114 -351

No files matched your search

+115 -91
View File
@@ -1,115 +1,139 @@
"""Process-local warm-up for the numerical simulation runtime."""
"""Nonblocking startup diagnostics; editor availability is independent of GCC."""
from __future__ import annotations
from dataclasses import asdict, dataclass
from copy import deepcopy
from datetime import datetime, timezone
import logging
import os
from threading import Lock
from threading import Event, Lock, Thread
from time import perf_counter
from typing import Literal
from uuid import uuid4
from .native_codegen.self_test import CheckCancelled, CheckFailure, check_native_runtime
LOGGER = logging.getLogger(__name__)
WarmupStatus = Literal["completed", "failed", "disabled"]
@dataclass(frozen=True)
class SimulationWarmupReport:
status: WarmupStatus
duration_ms: float
error: str | None = None
def as_dict(self) -> dict[str, object]:
return asdict(self)
_WARMUP_LOCK = Lock()
_WARMUP_REPORT: SimulationWarmupReport | None = None
MAX_RETRIES = 5
RETRY_DELAY_SECONDS = 0.5
def simulation_warmup_enabled() -> bool:
raw_value = os.getenv("SIMULATIONAPP_WARMUP", "on").strip().lower()
if raw_value in {"", "1", "true", "yes", "on"}:
value = os.getenv("SIMULATIONAPP_WARMUP", "on").strip().lower()
if value in {"", "1", "true", "yes", "on"}:
return True
if raw_value in {"0", "false", "no", "off"}:
if value in {"0", "false", "no", "off"}:
return False
raise ValueError(
"SIMULATIONAPP_WARMUP must be one of: on, off, true, false, 1, 0."
)
raise ValueError("SIMULATIONAPP_WARMUP must be one of: on, off, true, false, 1, 0.")
class SimulationRuntimeCheck:
"""One bounded background check per application lifespan, with readable stages."""
def __init__(self):
self._lock = Lock()
self._stop = Event()
self._thread: Thread | None = None
self._history: list[dict] = []
self._started = perf_counter()
self._report = {"checkId": uuid4().hex, "status": "pending", "stage": "queued",
"message": "等待仿真环境启动自检", "durationMs": 0.0,
"attempt": 0, "maxRetries": MAX_RETRIES, "maxAttempts": MAX_RETRIES + 1,
"retryDelaySeconds": RETRY_DELAY_SECONDS, "startedAt": None, "finishedAt": None,
"compiler": None, "compilerVersion": None, "sundialsRoot": None,
"error": None, "failure": None}
def _run_native_warmup() -> None:
"""Check the native toolchain and XML schema without running SciPy solvers.
def snapshot(self) -> dict:
with self._lock:
report = deepcopy(self._report)
if report["status"] in {"running", "retrying"}:
report["durationMs"] = (perf_counter() - self._started) * 1000
elif report["status"] in {"completed", "failed", "disabled", "cancelled"}:
# Preserve first-failure evidence without surfacing details mid-retry.
report["attemptHistory"] = deepcopy(self._history)
return report
The model-specific executable is generated when a model is submitted.
"""
from app.simulation.native_codegen.build import toolchain
from app.system_xml import validate_system_xml_document
def _update(self, **values):
with self._lock:
if values.get("status") in {"completed", "failed", "disabled", "cancelled"}:
values["durationMs"] = (perf_counter() - self._started) * 1000
values["finishedAt"] = datetime.now(timezone.utc).isoformat()
self._report.update(values)
toolchain()
validate_system_xml_document(b"<System/>")
def start(self):
with self._lock:
if self._thread is not None:
return
self._started = perf_counter()
self._report.update(status="running", message="正在进行仿真环境首次启动自检",
startedAt=datetime.now(timezone.utc).isoformat())
self._thread = Thread(target=self._run, name="simulation-runtime-check", daemon=True)
self._thread.start()
def _run(self):
for attempt in range(1, MAX_RETRIES + 2):
if self._stop.is_set():
self._cancel()
return
attempt_started = perf_counter()
self._update(status="running", attempt=attempt, stage="queued", error=None, failure=None,
compiler=None, compilerVersion=None, sundialsRoot=None)
def warm_up_simulation_runtime() -> SimulationWarmupReport:
"""Warm one worker exactly once, returning a startup diagnostic report.
def progress(**values):
if attempt > 1 and "message" in values:
values["message"] = f"正在重试(第 {attempt - 1}/{MAX_RETRIES} 轮):{values['message']}"
self._update(**values)
Ordinary warm-up failures are reported but do not prevent the editor and
non-simulation APIs from starting. ``MemoryError`` remains fatal because
continuing a worker under memory exhaustion is unsafe.
"""
try:
if not simulation_warmup_enabled():
self._update(status="disabled", message="仿真环境启动自检已由服务器配置关闭")
return
check_native_runtime(progress, self._stop)
if self._stop.is_set():
raise CheckCancelled()
except CheckCancelled:
self._cancel()
return
except Exception as exc:
failure = deepcopy(exc.details) if isinstance(exc, CheckFailure) else {
"command": [], "cwd": None, "exitCode": None, "stdout": "", "stderr": "",
"errorType": type(exc).__name__,
}
with self._lock:
self._history.append({"attempt": attempt, "status": "failed", "stage": self._report["stage"],
"durationMs": (perf_counter() - attempt_started) * 1000,
"error": str(exc), "failure": failure})
if self._stop.is_set():
self._cancel()
return
if attempt <= MAX_RETRIES:
previous = "首次检测" if attempt == 1 else f"第 {attempt - 1} 轮重试"
message = (f"{previous}未通过,正在准备第 {attempt}/{MAX_RETRIES} 轮重试"
f"(间隔 {RETRY_DELAY_SECONDS:g} 秒);编辑器可正常使用")
self._update(status="retrying", stage="retry-wait", message=message)
LOGGER.warning("%s", message)
# Interruptible on both Windows and Linux; never sleep on the API thread.
if self._stop.wait(RETRY_DELAY_SECONDS):
self._cancel()
return
continue
self._update(status="failed", error=str(exc), failure=failure,
message=f"本次启动自检未通过:首次检测及 {MAX_RETRIES} 轮重试均失败;编辑器仍可使用")
LOGGER.error("Simulation startup self-check exhausted retries at %s; attempt history: %s",
self.snapshot()["stage"], self._history, exc_info=True)
return
else:
with self._lock:
self._history.append({"attempt": attempt, "status": "completed", "stage": "complete",
"durationMs": (perf_counter() - attempt_started) * 1000,
"error": None, "failure": None})
recovery = f"(第 {attempt - 1} 轮重试成功)" if attempt > 1 else ""
self._update(status="completed", stage="complete", error=None, failure=None,
message=f"本次启动自检通过,仿真环境可用{recovery}")
return
global _WARMUP_REPORT
def _cancel(self):
self._update(status="cancelled", message="后端正在关闭,启动自检及重试已取消")
from app.simulation.backends import numeric_engine_name
engine = numeric_engine_name()
with _WARMUP_LOCK:
if _WARMUP_REPORT is not None:
return _WARMUP_REPORT
if not simulation_warmup_enabled():
_WARMUP_REPORT = SimulationWarmupReport(
status="disabled",
duration_ms=0.0,
)
return _WARMUP_REPORT
started = perf_counter()
try:
_run_native_warmup()
except MemoryError:
raise
except Exception as exc:
_WARMUP_REPORT = SimulationWarmupReport(
status="failed",
duration_ms=(perf_counter() - started) * 1000.0,
error=f"{type(exc).__name__}: {exc}",
)
LOGGER.exception("Simulation runtime warm-up failed; startup will continue.")
else:
_WARMUP_REPORT = SimulationWarmupReport(
status="completed",
duration_ms=(perf_counter() - started) * 1000.0,
)
LOGGER.info(
"Simulation runtime warm-up (%s) completed in %.1f ms.",
engine,
_WARMUP_REPORT.duration_ms,
)
return _WARMUP_REPORT
def _reset_simulation_warmup_for_tests() -> None:
global _WARMUP_REPORT
with _WARMUP_LOCK:
_WARMUP_REPORT = None
__all__ = [
"SimulationWarmupReport",
"simulation_warmup_enabled",
"warm_up_simulation_runtime",
]
def close(self):
self._stop.set()
if self._thread is not None:
self._thread.join(timeout=1)