Hooked up simulation to BEsim

This commit is contained in:
2026-08-01 12:25:11 +02:00
parent a38d461a36
commit 2128fb00b4
11 changed files with 8241 additions and 24 deletions

View File

@@ -4,8 +4,10 @@ from __future__ import annotations
import asyncio
import os
import signal
import shlex
import subprocess
import time
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
@@ -27,6 +29,10 @@ class OpenModelicaError(RuntimeError):
"""Raised when OMC cannot start or reports a failure."""
class SimulationCancelledError(RuntimeError):
"""Raised when a running compiled simulation is cancelled."""
ProcessExecutor = Callable[
[Sequence[str], Path, float | None, Mapping[str, str] | None],
ProcessResult,
@@ -89,6 +95,58 @@ class OpenModelicaRunner:
"""Run OMC on an asyncio worker thread."""
return await _run_on_worker(self._run, script, working_directory)
def _run_cancellable(self, script: Path, working_directory: Path, cancel_event: Event) -> ProcessResult:
command = (*self.command, str(script.resolve()))
return self._run_cancellable_process(command, working_directory, cancel_event)
def _run_cancellable_process(self, command: Sequence[str], working_directory: Path, cancel_event: Event) -> ProcessResult:
process_environment = None
if self.environment is not None:
process_environment = {**os.environ, **self.environment}
try:
process = subprocess.Popen(list(command), cwd=working_directory, env=process_environment, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=os.name != "nt")
except OSError as exc:
raise OpenModelicaError(f"simulation executable could not start: {exc}") from exc
deadline = time.monotonic() + self.timeout if self.timeout is not None else None
while True:
if cancel_event.is_set():
_terminate_process(process)
try:
stdout, stderr = process.communicate(timeout=5)
except subprocess.TimeoutExpired:
_kill_process(process)
stdout, stderr = process.communicate()
raise SimulationCancelledError("simulation was cancelled")
if deadline is not None and time.monotonic() >= deadline:
_kill_process(process)
process.communicate()
raise OpenModelicaError(f"simulation timed out after {self.timeout} seconds")
try:
stdout, stderr = process.communicate(timeout=0.05)
break
except subprocess.TimeoutExpired:
continue
result = ProcessResult(command=tuple(command), return_code=process.returncode, stdout=stdout, stderr=stderr)
if result.return_code != 0:
details = result.stderr.strip() or result.stdout.strip()
suffix = f": {details}" if details else ""
raise OpenModelicaError(f"simulation exited with status {result.return_code}{suffix}")
return result
def _terminate_process(process: subprocess.Popen[str]) -> None:
if os.name == "nt":
process.terminate()
else:
os.killpg(process.pid, signal.SIGTERM)
def _kill_process(process: subprocess.Popen[str]) -> None:
if os.name == "nt":
process.kill()
else:
os.killpg(process.pid, signal.SIGKILL)
def _command_parts(command: str | Sequence[str]) -> tuple[str, ...]:
if isinstance(command, str):
@@ -140,7 +198,7 @@ async def _run_on_worker(
def invoke() -> None:
try:
results.append(operation(*args, **kwargs))
except BaseException as error:
except Exception as error: # noqa: BLE001 - worker must relay operation failures
errors.append(error)
finally:
finished.set()