Source code for trader.daemon.sleeper

"""Sleeping, as an injectable seam.

The daemon spends almost all of its life asleep, which would make its tests
either slow or untrustworthy. Taking `sleep` and `monotonic` as arguments makes
them instant and exact instead.
"""

import time
from collections.abc import Callable
from typing import Protocol

from trader.daemon.signals import ShutdownFlag

__all__ = ["RealSleeper", "Sleeper"]


[docs] class Sleeper(Protocol): """Waits, but gives up early when shutdown is requested."""
[docs] def sleep(self, seconds: float, shutdown: ShutdownFlag) -> None: """Sleep up to `seconds`, returning early if `shutdown` is set.""" ...
[docs] class RealSleeper: """Sleeps in short slices so a signal is noticed promptly. One `time.sleep(3600)` would be interrupted by a signal on POSIX, but the handler only sets a flag — the sleep resumes for the remaining hour unless it is broken up. Slicing bounds the shutdown delay at `slice_seconds` regardless of how long the requested wait was. """ def __init__( self, *, slice_seconds: float = 1.0, sleep: Callable[[float], None] = time.sleep, monotonic: Callable[[], float] = time.monotonic, ) -> None: self._slice_seconds = slice_seconds self._sleep = sleep self._monotonic = monotonic
[docs] def sleep(self, seconds: float, shutdown: ShutdownFlag) -> None: """Sleep up to `seconds`, in slices, stopping early on shutdown.""" if seconds <= 0: return deadline = self._monotonic() + seconds while not shutdown.is_set(): remaining = deadline - self._monotonic() if remaining <= 0: return self._sleep(min(self._slice_seconds, remaining))