"""One observation deadline shared with synchronous screen-client worker threads.""" from contextvars import ContextVar from dataclasses import dataclass, field from threading import Event from time import monotonic class ObservationExpired(TimeoutError): """The shared observation deadline has elapsed.""" class ObservationCancelled(RuntimeError): """The owning driver has ended this observation.""" @dataclass class ObservationBudget: deadline: float cancelled: Event = field(default_factory=Event) phase: str = "获取屏幕" last_failure: str | None = None def remaining(self) -> float: if self.cancelled.is_set(): raise ObservationCancelled("设备观测已取消,禁止继续重连") remaining = self.deadline - monotonic() if remaining <= 0: raise ObservationExpired(f"设备观测预算耗尽:{self.detail}") return remaining @property def detail(self) -> str: return self.phase + (f";最近错误:{self.last_failure}" if self.last_failure else "") current_observation: ContextVar[ObservationBudget | None] = ContextVar( "screen_observation_budget", default=None ) def check_observation(phase: str | None = None, failure: BaseException | None = None) -> None: budget = current_observation.get() if budget is not None: if failure is not None and not isinstance( failure, (ObservationExpired, ObservationCancelled) ): budget.last_failure = f"{type(failure).__name__}: {failure}" budget.remaining() if phase is not None: budget.phase = phase