1*cda5da8dSAndroid Build Coastguard Workerimport enum 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard Workerfrom types import TracebackType 4*cda5da8dSAndroid Build Coastguard Workerfrom typing import final, Optional, Type 5*cda5da8dSAndroid Build Coastguard Worker 6*cda5da8dSAndroid Build Coastguard Workerfrom . import events 7*cda5da8dSAndroid Build Coastguard Workerfrom . import exceptions 8*cda5da8dSAndroid Build Coastguard Workerfrom . import tasks 9*cda5da8dSAndroid Build Coastguard Worker 10*cda5da8dSAndroid Build Coastguard Worker 11*cda5da8dSAndroid Build Coastguard Worker__all__ = ( 12*cda5da8dSAndroid Build Coastguard Worker "Timeout", 13*cda5da8dSAndroid Build Coastguard Worker "timeout", 14*cda5da8dSAndroid Build Coastguard Worker "timeout_at", 15*cda5da8dSAndroid Build Coastguard Worker) 16*cda5da8dSAndroid Build Coastguard Worker 17*cda5da8dSAndroid Build Coastguard Worker 18*cda5da8dSAndroid Build Coastguard Workerclass _State(enum.Enum): 19*cda5da8dSAndroid Build Coastguard Worker CREATED = "created" 20*cda5da8dSAndroid Build Coastguard Worker ENTERED = "active" 21*cda5da8dSAndroid Build Coastguard Worker EXPIRING = "expiring" 22*cda5da8dSAndroid Build Coastguard Worker EXPIRED = "expired" 23*cda5da8dSAndroid Build Coastguard Worker EXITED = "finished" 24*cda5da8dSAndroid Build Coastguard Worker 25*cda5da8dSAndroid Build Coastguard Worker 26*cda5da8dSAndroid Build Coastguard Worker@final 27*cda5da8dSAndroid Build Coastguard Workerclass Timeout: 28*cda5da8dSAndroid Build Coastguard Worker """Asynchronous context manager for cancelling overdue coroutines. 29*cda5da8dSAndroid Build Coastguard Worker 30*cda5da8dSAndroid Build Coastguard Worker Use `timeout()` or `timeout_at()` rather than instantiating this class directly. 31*cda5da8dSAndroid Build Coastguard Worker """ 32*cda5da8dSAndroid Build Coastguard Worker 33*cda5da8dSAndroid Build Coastguard Worker def __init__(self, when: Optional[float]) -> None: 34*cda5da8dSAndroid Build Coastguard Worker """Schedule a timeout that will trigger at a given loop time. 35*cda5da8dSAndroid Build Coastguard Worker 36*cda5da8dSAndroid Build Coastguard Worker - If `when` is `None`, the timeout will never trigger. 37*cda5da8dSAndroid Build Coastguard Worker - If `when < loop.time()`, the timeout will trigger on the next 38*cda5da8dSAndroid Build Coastguard Worker iteration of the event loop. 39*cda5da8dSAndroid Build Coastguard Worker """ 40*cda5da8dSAndroid Build Coastguard Worker self._state = _State.CREATED 41*cda5da8dSAndroid Build Coastguard Worker 42*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler: Optional[events.TimerHandle] = None 43*cda5da8dSAndroid Build Coastguard Worker self._task: Optional[tasks.Task] = None 44*cda5da8dSAndroid Build Coastguard Worker self._when = when 45*cda5da8dSAndroid Build Coastguard Worker 46*cda5da8dSAndroid Build Coastguard Worker def when(self) -> Optional[float]: 47*cda5da8dSAndroid Build Coastguard Worker """Return the current deadline.""" 48*cda5da8dSAndroid Build Coastguard Worker return self._when 49*cda5da8dSAndroid Build Coastguard Worker 50*cda5da8dSAndroid Build Coastguard Worker def reschedule(self, when: Optional[float]) -> None: 51*cda5da8dSAndroid Build Coastguard Worker """Reschedule the timeout.""" 52*cda5da8dSAndroid Build Coastguard Worker assert self._state is not _State.CREATED 53*cda5da8dSAndroid Build Coastguard Worker if self._state is not _State.ENTERED: 54*cda5da8dSAndroid Build Coastguard Worker raise RuntimeError( 55*cda5da8dSAndroid Build Coastguard Worker f"Cannot change state of {self._state.value} Timeout", 56*cda5da8dSAndroid Build Coastguard Worker ) 57*cda5da8dSAndroid Build Coastguard Worker 58*cda5da8dSAndroid Build Coastguard Worker self._when = when 59*cda5da8dSAndroid Build Coastguard Worker 60*cda5da8dSAndroid Build Coastguard Worker if self._timeout_handler is not None: 61*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler.cancel() 62*cda5da8dSAndroid Build Coastguard Worker 63*cda5da8dSAndroid Build Coastguard Worker if when is None: 64*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler = None 65*cda5da8dSAndroid Build Coastguard Worker else: 66*cda5da8dSAndroid Build Coastguard Worker loop = events.get_running_loop() 67*cda5da8dSAndroid Build Coastguard Worker if when <= loop.time(): 68*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler = loop.call_soon(self._on_timeout) 69*cda5da8dSAndroid Build Coastguard Worker else: 70*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler = loop.call_at(when, self._on_timeout) 71*cda5da8dSAndroid Build Coastguard Worker 72*cda5da8dSAndroid Build Coastguard Worker def expired(self) -> bool: 73*cda5da8dSAndroid Build Coastguard Worker """Is timeout expired during execution?""" 74*cda5da8dSAndroid Build Coastguard Worker return self._state in (_State.EXPIRING, _State.EXPIRED) 75*cda5da8dSAndroid Build Coastguard Worker 76*cda5da8dSAndroid Build Coastguard Worker def __repr__(self) -> str: 77*cda5da8dSAndroid Build Coastguard Worker info = [''] 78*cda5da8dSAndroid Build Coastguard Worker if self._state is _State.ENTERED: 79*cda5da8dSAndroid Build Coastguard Worker when = round(self._when, 3) if self._when is not None else None 80*cda5da8dSAndroid Build Coastguard Worker info.append(f"when={when}") 81*cda5da8dSAndroid Build Coastguard Worker info_str = ' '.join(info) 82*cda5da8dSAndroid Build Coastguard Worker return f"<Timeout [{self._state.value}]{info_str}>" 83*cda5da8dSAndroid Build Coastguard Worker 84*cda5da8dSAndroid Build Coastguard Worker async def __aenter__(self) -> "Timeout": 85*cda5da8dSAndroid Build Coastguard Worker self._state = _State.ENTERED 86*cda5da8dSAndroid Build Coastguard Worker self._task = tasks.current_task() 87*cda5da8dSAndroid Build Coastguard Worker self._cancelling = self._task.cancelling() 88*cda5da8dSAndroid Build Coastguard Worker if self._task is None: 89*cda5da8dSAndroid Build Coastguard Worker raise RuntimeError("Timeout should be used inside a task") 90*cda5da8dSAndroid Build Coastguard Worker self.reschedule(self._when) 91*cda5da8dSAndroid Build Coastguard Worker return self 92*cda5da8dSAndroid Build Coastguard Worker 93*cda5da8dSAndroid Build Coastguard Worker async def __aexit__( 94*cda5da8dSAndroid Build Coastguard Worker self, 95*cda5da8dSAndroid Build Coastguard Worker exc_type: Optional[Type[BaseException]], 96*cda5da8dSAndroid Build Coastguard Worker exc_val: Optional[BaseException], 97*cda5da8dSAndroid Build Coastguard Worker exc_tb: Optional[TracebackType], 98*cda5da8dSAndroid Build Coastguard Worker ) -> Optional[bool]: 99*cda5da8dSAndroid Build Coastguard Worker assert self._state in (_State.ENTERED, _State.EXPIRING) 100*cda5da8dSAndroid Build Coastguard Worker 101*cda5da8dSAndroid Build Coastguard Worker if self._timeout_handler is not None: 102*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler.cancel() 103*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler = None 104*cda5da8dSAndroid Build Coastguard Worker 105*cda5da8dSAndroid Build Coastguard Worker if self._state is _State.EXPIRING: 106*cda5da8dSAndroid Build Coastguard Worker self._state = _State.EXPIRED 107*cda5da8dSAndroid Build Coastguard Worker 108*cda5da8dSAndroid Build Coastguard Worker if self._task.uncancel() <= self._cancelling and exc_type is exceptions.CancelledError: 109*cda5da8dSAndroid Build Coastguard Worker # Since there are no new cancel requests, we're 110*cda5da8dSAndroid Build Coastguard Worker # handling this. 111*cda5da8dSAndroid Build Coastguard Worker raise TimeoutError from exc_val 112*cda5da8dSAndroid Build Coastguard Worker elif self._state is _State.ENTERED: 113*cda5da8dSAndroid Build Coastguard Worker self._state = _State.EXITED 114*cda5da8dSAndroid Build Coastguard Worker 115*cda5da8dSAndroid Build Coastguard Worker return None 116*cda5da8dSAndroid Build Coastguard Worker 117*cda5da8dSAndroid Build Coastguard Worker def _on_timeout(self) -> None: 118*cda5da8dSAndroid Build Coastguard Worker assert self._state is _State.ENTERED 119*cda5da8dSAndroid Build Coastguard Worker self._task.cancel() 120*cda5da8dSAndroid Build Coastguard Worker self._state = _State.EXPIRING 121*cda5da8dSAndroid Build Coastguard Worker # drop the reference early 122*cda5da8dSAndroid Build Coastguard Worker self._timeout_handler = None 123*cda5da8dSAndroid Build Coastguard Worker 124*cda5da8dSAndroid Build Coastguard Worker 125*cda5da8dSAndroid Build Coastguard Workerdef timeout(delay: Optional[float]) -> Timeout: 126*cda5da8dSAndroid Build Coastguard Worker """Timeout async context manager. 127*cda5da8dSAndroid Build Coastguard Worker 128*cda5da8dSAndroid Build Coastguard Worker Useful in cases when you want to apply timeout logic around block 129*cda5da8dSAndroid Build Coastguard Worker of code or in cases when asyncio.wait_for is not suitable. For example: 130*cda5da8dSAndroid Build Coastguard Worker 131*cda5da8dSAndroid Build Coastguard Worker >>> async with asyncio.timeout(10): # 10 seconds timeout 132*cda5da8dSAndroid Build Coastguard Worker ... await long_running_task() 133*cda5da8dSAndroid Build Coastguard Worker 134*cda5da8dSAndroid Build Coastguard Worker 135*cda5da8dSAndroid Build Coastguard Worker delay - value in seconds or None to disable timeout logic 136*cda5da8dSAndroid Build Coastguard Worker 137*cda5da8dSAndroid Build Coastguard Worker long_running_task() is interrupted by raising asyncio.CancelledError, 138*cda5da8dSAndroid Build Coastguard Worker the top-most affected timeout() context manager converts CancelledError 139*cda5da8dSAndroid Build Coastguard Worker into TimeoutError. 140*cda5da8dSAndroid Build Coastguard Worker """ 141*cda5da8dSAndroid Build Coastguard Worker loop = events.get_running_loop() 142*cda5da8dSAndroid Build Coastguard Worker return Timeout(loop.time() + delay if delay is not None else None) 143*cda5da8dSAndroid Build Coastguard Worker 144*cda5da8dSAndroid Build Coastguard Worker 145*cda5da8dSAndroid Build Coastguard Workerdef timeout_at(when: Optional[float]) -> Timeout: 146*cda5da8dSAndroid Build Coastguard Worker """Schedule the timeout at absolute time. 147*cda5da8dSAndroid Build Coastguard Worker 148*cda5da8dSAndroid Build Coastguard Worker Like timeout() but argument gives absolute time in the same clock system 149*cda5da8dSAndroid Build Coastguard Worker as loop.time(). 150*cda5da8dSAndroid Build Coastguard Worker 151*cda5da8dSAndroid Build Coastguard Worker Please note: it is not POSIX time but a time with 152*cda5da8dSAndroid Build Coastguard Worker undefined starting base, e.g. the time of the system power on. 153*cda5da8dSAndroid Build Coastguard Worker 154*cda5da8dSAndroid Build Coastguard Worker >>> async with asyncio.timeout_at(loop.time() + 10): 155*cda5da8dSAndroid Build Coastguard Worker ... await long_running_task() 156*cda5da8dSAndroid Build Coastguard Worker 157*cda5da8dSAndroid Build Coastguard Worker 158*cda5da8dSAndroid Build Coastguard Worker when - a deadline when timeout occurs or None to disable timeout logic 159*cda5da8dSAndroid Build Coastguard Worker 160*cda5da8dSAndroid Build Coastguard Worker long_running_task() is interrupted by raising asyncio.CancelledError, 161*cda5da8dSAndroid Build Coastguard Worker the top-most affected timeout() context manager converts CancelledError 162*cda5da8dSAndroid Build Coastguard Worker into TimeoutError. 163*cda5da8dSAndroid Build Coastguard Worker """ 164*cda5da8dSAndroid Build Coastguard Worker return Timeout(when) 165