现有组件功能与Amesim组件比较对齐,补全功能;仿真服务启动环境检测,旧版json工程文件适配读取
This commit is contained in:
1 parent
b6c22a54f8
commit
6fc9afe41d
119 files changed
+260114
-351
No files matched your search
+115
-91
@@ -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)
|
||||
Reference in new issue
Block a user