"""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"") 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", ]