some fixes
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import math
|
||||
import threading
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -26,6 +27,12 @@ logger = logging.getLogger(__name__)
|
||||
# of all entries (1.75 s today) plus the cost of close()/open() themselves.
|
||||
_REOPEN_BACKOFF_SECONDS: tuple[float, ...] = (0.25, 0.5, 1.0)
|
||||
|
||||
# Hard ceiling on the wall-clock time a single acquire_collection() may spend in
|
||||
# the recovery path (backoff sleeps + close()/open() cost across every retry).
|
||||
# Bounding this keeps a shutdown that arrives mid-recovery comfortably under the
|
||||
# systemd TimeoutStopSec so the unit is never SIGKILLed for hanging on exit.
|
||||
_MAX_RECOVERY_WALL_SECONDS: float = 10.0
|
||||
|
||||
_INPUT_S_PARAMETERS_BY_OUTPUT: dict[int, tuple[str, ...]] = {
|
||||
0: ("s31", "s41", "s51", "s61"),
|
||||
1: ("s32", "s42", "s52", "s62"),
|
||||
@@ -101,13 +108,22 @@ class MultiDeviceLibreVnaService:
|
||||
except Exception as exc: # noqa: BLE001 — recovery path, never propagate
|
||||
logger.warning("Multi-device close() ignored transport error: %s", exc)
|
||||
|
||||
def recover(self) -> None:
|
||||
def recover(
|
||||
self,
|
||||
*,
|
||||
stop_event: threading.Event | None = None,
|
||||
deadline_monotonic: float | None = None,
|
||||
) -> None:
|
||||
"""Reopen native device transports after a failed acquisition.
|
||||
|
||||
Tries several short backoffs so a transient USB stall does not kill the
|
||||
producer on the very first retry. Raises the last error only after
|
||||
every attempt failed — the outer acquisition loop is expected to count
|
||||
these as recovery_attempts.
|
||||
|
||||
When `stop_event` is supplied the backoff waits on it instead of
|
||||
sleeping, so a shutdown request aborts the loop immediately; an optional
|
||||
`deadline_monotonic` caps the total wall-time spent here.
|
||||
"""
|
||||
if self._using_mock_backend:
|
||||
return
|
||||
@@ -115,7 +131,21 @@ class MultiDeviceLibreVnaService:
|
||||
|
||||
last_error: Exception | None = None
|
||||
for attempt_index, delay_s in enumerate(_REOPEN_BACKOFF_SECONDS, start=1):
|
||||
time.sleep(delay_s)
|
||||
# Bail out the instant a stop is requested or the recovery budget is
|
||||
# spent, rather than committing to another (re)open attempt.
|
||||
if stop_event is not None and stop_event.is_set():
|
||||
logger.info("Multi-device recover() aborted: stop requested")
|
||||
return
|
||||
if deadline_monotonic is not None and time.monotonic() >= deadline_monotonic:
|
||||
logger.warning("Multi-device recover() aborted: recovery time budget exhausted")
|
||||
break
|
||||
# Interruptible backoff: wait() returns early the moment stop is set.
|
||||
if stop_event is not None:
|
||||
if stop_event.wait(delay_s):
|
||||
logger.info("Multi-device recover() aborted: stop requested")
|
||||
return
|
||||
else:
|
||||
time.sleep(delay_s)
|
||||
try:
|
||||
self.open()
|
||||
if self._controller is not None:
|
||||
@@ -151,15 +181,27 @@ class MultiDeviceLibreVnaService:
|
||||
power_dbm=float(sweep.power_dbm),
|
||||
)
|
||||
|
||||
def acquire_collection(self, collection_id: int = 1) -> SweepCollection:
|
||||
"""Acquire one complete virtual 2x4 matrix in canonical combo order."""
|
||||
def acquire_collection(
|
||||
self,
|
||||
collection_id: int = 1,
|
||||
*,
|
||||
stop_event: threading.Event | None = None,
|
||||
) -> SweepCollection:
|
||||
"""Acquire one complete virtual 2x4 matrix in canonical combo order.
|
||||
|
||||
When `stop_event` is supplied it is threaded into the recovery path so a
|
||||
shutdown request aborts the backoff/retry loops promptly (default `None`
|
||||
preserves the original blocking behavior).
|
||||
"""
|
||||
if self._sweep_configuration is None:
|
||||
raise RuntimeError("Multi-device service is not configured")
|
||||
capture_start_ns = time.monotonic_ns()
|
||||
if self._using_mock_backend:
|
||||
collection = self._acquire_mock_collection(collection_id, capture_start_ns)
|
||||
else:
|
||||
collection = self._acquire_native_collection_with_recovery(collection_id, capture_start_ns)
|
||||
collection = self._acquire_native_collection_with_recovery(
|
||||
collection_id, capture_start_ns, stop_event=stop_event
|
||||
)
|
||||
collection.capture_end_ns = time.monotonic_ns()
|
||||
return collection
|
||||
|
||||
@@ -167,8 +209,13 @@ class MultiDeviceLibreVnaService:
|
||||
self,
|
||||
collection_id: int,
|
||||
capture_start_ns: int,
|
||||
*,
|
||||
stop_event: threading.Event | None = None,
|
||||
) -> SweepCollection:
|
||||
last_error: Exception | None = None
|
||||
# Cap total recovery wall-time across all retries so a shutdown that
|
||||
# lands mid-recovery stays well under the systemd TimeoutStopSec.
|
||||
recovery_deadline = time.monotonic() + _MAX_RECOVERY_WALL_SECONDS
|
||||
for attempt_index in range(self.recovery_attempts + 1):
|
||||
try:
|
||||
return self._acquire_native_collection(collection_id, capture_start_ns)
|
||||
@@ -176,6 +223,13 @@ class MultiDeviceLibreVnaService:
|
||||
last_error = exc
|
||||
if attempt_index >= self.recovery_attempts:
|
||||
break
|
||||
# Stop the moment shutdown is requested or the recovery budget is
|
||||
# spent — do not start another reconnect we cannot finish in time.
|
||||
if stop_event is not None and stop_event.is_set():
|
||||
break
|
||||
if time.monotonic() >= recovery_deadline:
|
||||
logger.warning("multi-device recovery time budget exhausted; giving up")
|
||||
break
|
||||
logger.warning(
|
||||
"multi-device acquisition failed, reconnecting devices (%d/%d): %s",
|
||||
attempt_index + 1,
|
||||
@@ -188,7 +242,7 @@ class MultiDeviceLibreVnaService:
|
||||
# attempt and try again on the next loop iteration, so a
|
||||
# transient USB hiccup cannot kill the whole producer.
|
||||
try:
|
||||
self.recover()
|
||||
self.recover(stop_event=stop_event, deadline_monotonic=recovery_deadline)
|
||||
except Exception as recover_exc: # noqa: BLE001
|
||||
last_error = recover_exc
|
||||
logger.warning(
|
||||
@@ -198,6 +252,10 @@ class MultiDeviceLibreVnaService:
|
||||
recover_exc,
|
||||
exc_info=True,
|
||||
)
|
||||
# If recovery was interrupted by a stop request, do not loop back
|
||||
# for another acquisition attempt; let shutdown proceed.
|
||||
if stop_event is not None and stop_event.is_set():
|
||||
break
|
||||
|
||||
assert last_error is not None
|
||||
raise last_error
|
||||
|
||||
Reference in New Issue
Block a user