Source code for mode.timers

"""AsyncIO Timers."""
from itertools import count
from time import perf_counter
from typing import Callable, Iterator
from .utils.logging import get_logger
from .utils.times import Seconds, want_seconds

__all__ = ['timer_intervals']

MAX_DRIFT_PERCENT: float = 0.90
MAX_DRIFT_CEILING: float = 1.2

ClockArg = Callable[[], float]

logger = get_logger(__name__)


[docs]def timer_intervals(interval: Seconds, max_drift_correction: float = 0.1, name: str = '', clock: ClockArg = perf_counter) -> Iterator[float]: """Generate timer sleep times. Example: >>> async def my_timer(interval=1.0): ... # wait interval before running first time. ... await asyncio.sleep(interval) ... for sleep_time in timer_intervals(1.0, name='my_timer'): ... # do something that takes a while. ... await asyncio.sleep(sleep_time) """ interval_s = want_seconds(interval) # Log when drift exceeds this number max_drift = min(interval_s * MAX_DRIFT_PERCENT, MAX_DRIFT_CEILING) min_interval_s = interval_s - max_drift_correction max_interval_s = interval_s + max_drift_correction # If the loop calls asyncio.sleep(interval) # it will always wake up a little bit late, and can eventually # drift, Using the algorithm below we will usually # wake up slightly early instead (usually around 0.001s), # and often actually end up being able to correct the drift # entirely around 20% of the time. Also add entropy # to timers. # first yield interval # usually callers will sleep before starting first timer epoch = clock() # time of last timer run, updated after each run. last_run_at = epoch - interval_s for i in count(): now = clock() since_epoch = now - epoch time_spent = now - last_run_at drift = interval_s - time_spent abs_drift = abs(drift) drift_time = interval_s + drift if drift_time > interval_s: sleep_time = min(drift_time, max_interval_s) elif drift_time < interval_s: sleep_time = max(drift_time, min_interval_s) else: sleep_time = interval_s if abs_drift >= max_drift: if drift < 0: logger.info( 'Timer %s woke up too late, with a drift of +%r', name, abs_drift) else: logger.info( 'Timer %s woke up too early, with a drift of -%r', name, abs_drift) else: logger.debug( 'Timer %s woke up - iteration=%r ' 'time_spent=%r drift=%r sleep_time=%r since_epoch=%r', name, i, time_spent, drift, sleep_time, since_epoch) last_run_at = clock() yield sleep_time else: # pragma: no cover pass # never exits