xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/asyncio/timeouts.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
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