Files
SystemSimulationApp/app/simulation/warmup.py
T

118 lines
3.3 KiB
Python

"""Process-local warm-up for the numerical simulation runtime."""
from __future__ import annotations
from dataclasses import asdict, dataclass
import logging
import os
from threading import Lock
from time import perf_counter
from typing import Literal
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
def simulation_warmup_enabled() -> bool:
raw_value = os.getenv("SIMULATIONAPP_WARMUP", "on").strip().lower()
if raw_value in {"", "1", "true", "yes", "on"}:
return True
if raw_value in {"0", "false", "no", "off"}:
return False
raise ValueError(
"SIMULATIONAPP_WARMUP must be one of: on, off, true, false, 1, 0."
)
def _run_native_warmup() -> None:
"""Check the native toolchain and XML schema without running SciPy solvers.
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
if os.name != "nt":
raise RuntimeError("Native v1 currently supports Windows x64 build packaging only.")
toolchain()
validate_system_xml_document(b"<System/>")
def warm_up_simulation_runtime() -> SimulationWarmupReport:
"""Warm one worker exactly once, returning a startup diagnostic report.
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.
"""
global _WARMUP_REPORT
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",
]