feat: serialize public hardware execution
This commit is contained in:
@@ -2,15 +2,24 @@
|
||||
|
||||
from .matrix import CanMatrix, MatrixError
|
||||
from .gateway import CanBus, CanFrame, CanGatewayError, PythonCanBus, ZlgCanBridgeBus
|
||||
from .hardware import HardwareSafetyError, SeatHardwareSettings
|
||||
from .hardware import (
|
||||
HardwareBusyError,
|
||||
HardwareExecutionCoordinator,
|
||||
HardwareSafetyError,
|
||||
PHYSICAL_BENCH_COORDINATOR,
|
||||
SeatHardwareSettings,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CanBus",
|
||||
"CanFrame",
|
||||
"CanGatewayError",
|
||||
"CanMatrix",
|
||||
"HardwareBusyError",
|
||||
"HardwareExecutionCoordinator",
|
||||
"HardwareSafetyError",
|
||||
"MatrixError",
|
||||
"PHYSICAL_BENCH_COORDINATOR",
|
||||
"PythonCanBus",
|
||||
"SeatHardwareSettings",
|
||||
"ZlgCanBridgeBus",
|
||||
|
||||
@@ -5,9 +5,12 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Iterator
|
||||
|
||||
from cognitive_agent.infrastructure.firmware import (
|
||||
FIRMWARE_PROTOCOL_SIGNATURE,
|
||||
@@ -36,6 +39,57 @@ class HardwareSafetyError(ValueError):
|
||||
"""Raised before any CAN device is opened when a safety gate fails."""
|
||||
|
||||
|
||||
class HardwareBusyError(RuntimeError):
|
||||
"""Raised when another request already owns the single physical bench."""
|
||||
|
||||
def __init__(self, retry_after_seconds: int = 5) -> None:
|
||||
self.retry_after_seconds = retry_after_seconds
|
||||
super().__init__(
|
||||
"真实座椅台架正在执行其他请求。为避免多人指令交叉,本次请求未进入CAN链路;"
|
||||
"请等待当前动作完成后重试。"
|
||||
)
|
||||
|
||||
|
||||
class HardwareExecutionCoordinator:
|
||||
"""Provide process-wide, fail-fast exclusive ownership of the physical bench."""
|
||||
|
||||
def __init__(self, retry_after_seconds: int = 5) -> None:
|
||||
self.retry_after_seconds = retry_after_seconds
|
||||
self._execution_lock = threading.Lock()
|
||||
self._state_lock = threading.Lock()
|
||||
self._active_since: float | None = None
|
||||
|
||||
@contextmanager
|
||||
def reserve(self) -> Iterator[None]:
|
||||
if not self._execution_lock.acquire(blocking=False):
|
||||
raise HardwareBusyError(self.retry_after_seconds)
|
||||
with self._state_lock:
|
||||
self._active_since = time.monotonic()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
with self._state_lock:
|
||||
self._active_since = None
|
||||
self._execution_lock.release()
|
||||
|
||||
def public_info(self) -> dict[str, Any]:
|
||||
with self._state_lock:
|
||||
active_since = self._active_since
|
||||
return {
|
||||
"busy": active_since is not None,
|
||||
"active_for_ms": (
|
||||
max(0, int((time.monotonic() - active_since) * 1000))
|
||||
if active_since is not None
|
||||
else 0
|
||||
),
|
||||
"concurrency_policy": "single_flight_fail_fast",
|
||||
"retry_after_seconds": self.retry_after_seconds,
|
||||
}
|
||||
|
||||
|
||||
PHYSICAL_BENCH_COORDINATOR = HardwareExecutionCoordinator()
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool = False) -> bool:
|
||||
value = os.getenv(name)
|
||||
return default if value is None else value.strip().lower() in TRUE_VALUES
|
||||
|
||||
Reference in New Issue
Block a user