xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/asyncio/runners.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker__all__ = ('Runner', 'run')
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard Workerimport contextvars
4*cda5da8dSAndroid Build Coastguard Workerimport enum
5*cda5da8dSAndroid Build Coastguard Workerimport functools
6*cda5da8dSAndroid Build Coastguard Workerimport threading
7*cda5da8dSAndroid Build Coastguard Workerimport signal
8*cda5da8dSAndroid Build Coastguard Workerimport sys
9*cda5da8dSAndroid Build Coastguard Workerfrom . import coroutines
10*cda5da8dSAndroid Build Coastguard Workerfrom . import events
11*cda5da8dSAndroid Build Coastguard Workerfrom . import exceptions
12*cda5da8dSAndroid Build Coastguard Workerfrom . import tasks
13*cda5da8dSAndroid Build Coastguard Worker
14*cda5da8dSAndroid Build Coastguard Worker
15*cda5da8dSAndroid Build Coastguard Workerclass _State(enum.Enum):
16*cda5da8dSAndroid Build Coastguard Worker    CREATED = "created"
17*cda5da8dSAndroid Build Coastguard Worker    INITIALIZED = "initialized"
18*cda5da8dSAndroid Build Coastguard Worker    CLOSED = "closed"
19*cda5da8dSAndroid Build Coastguard Worker
20*cda5da8dSAndroid Build Coastguard Worker
21*cda5da8dSAndroid Build Coastguard Workerclass Runner:
22*cda5da8dSAndroid Build Coastguard Worker    """A context manager that controls event loop life cycle.
23*cda5da8dSAndroid Build Coastguard Worker
24*cda5da8dSAndroid Build Coastguard Worker    The context manager always creates a new event loop,
25*cda5da8dSAndroid Build Coastguard Worker    allows to run async functions inside it,
26*cda5da8dSAndroid Build Coastguard Worker    and properly finalizes the loop at the context manager exit.
27*cda5da8dSAndroid Build Coastguard Worker
28*cda5da8dSAndroid Build Coastguard Worker    If debug is True, the event loop will be run in debug mode.
29*cda5da8dSAndroid Build Coastguard Worker    If loop_factory is passed, it is used for new event loop creation.
30*cda5da8dSAndroid Build Coastguard Worker
31*cda5da8dSAndroid Build Coastguard Worker    asyncio.run(main(), debug=True)
32*cda5da8dSAndroid Build Coastguard Worker
33*cda5da8dSAndroid Build Coastguard Worker    is a shortcut for
34*cda5da8dSAndroid Build Coastguard Worker
35*cda5da8dSAndroid Build Coastguard Worker    with asyncio.Runner(debug=True) as runner:
36*cda5da8dSAndroid Build Coastguard Worker        runner.run(main())
37*cda5da8dSAndroid Build Coastguard Worker
38*cda5da8dSAndroid Build Coastguard Worker    The run() method can be called multiple times within the runner's context.
39*cda5da8dSAndroid Build Coastguard Worker
40*cda5da8dSAndroid Build Coastguard Worker    This can be useful for interactive console (e.g. IPython),
41*cda5da8dSAndroid Build Coastguard Worker    unittest runners, console tools, -- everywhere when async code
42*cda5da8dSAndroid Build Coastguard Worker    is called from existing sync framework and where the preferred single
43*cda5da8dSAndroid Build Coastguard Worker    asyncio.run() call doesn't work.
44*cda5da8dSAndroid Build Coastguard Worker
45*cda5da8dSAndroid Build Coastguard Worker    """
46*cda5da8dSAndroid Build Coastguard Worker
47*cda5da8dSAndroid Build Coastguard Worker    # Note: the class is final, it is not intended for inheritance.
48*cda5da8dSAndroid Build Coastguard Worker
49*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, *, debug=None, loop_factory=None):
50*cda5da8dSAndroid Build Coastguard Worker        self._state = _State.CREATED
51*cda5da8dSAndroid Build Coastguard Worker        self._debug = debug
52*cda5da8dSAndroid Build Coastguard Worker        self._loop_factory = loop_factory
53*cda5da8dSAndroid Build Coastguard Worker        self._loop = None
54*cda5da8dSAndroid Build Coastguard Worker        self._context = None
55*cda5da8dSAndroid Build Coastguard Worker        self._interrupt_count = 0
56*cda5da8dSAndroid Build Coastguard Worker        self._set_event_loop = False
57*cda5da8dSAndroid Build Coastguard Worker
58*cda5da8dSAndroid Build Coastguard Worker    def __enter__(self):
59*cda5da8dSAndroid Build Coastguard Worker        self._lazy_init()
60*cda5da8dSAndroid Build Coastguard Worker        return self
61*cda5da8dSAndroid Build Coastguard Worker
62*cda5da8dSAndroid Build Coastguard Worker    def __exit__(self, exc_type, exc_val, exc_tb):
63*cda5da8dSAndroid Build Coastguard Worker        self.close()
64*cda5da8dSAndroid Build Coastguard Worker
65*cda5da8dSAndroid Build Coastguard Worker    def close(self):
66*cda5da8dSAndroid Build Coastguard Worker        """Shutdown and close event loop."""
67*cda5da8dSAndroid Build Coastguard Worker        if self._state is not _State.INITIALIZED:
68*cda5da8dSAndroid Build Coastguard Worker            return
69*cda5da8dSAndroid Build Coastguard Worker        try:
70*cda5da8dSAndroid Build Coastguard Worker            loop = self._loop
71*cda5da8dSAndroid Build Coastguard Worker            _cancel_all_tasks(loop)
72*cda5da8dSAndroid Build Coastguard Worker            loop.run_until_complete(loop.shutdown_asyncgens())
73*cda5da8dSAndroid Build Coastguard Worker            loop.run_until_complete(loop.shutdown_default_executor())
74*cda5da8dSAndroid Build Coastguard Worker        finally:
75*cda5da8dSAndroid Build Coastguard Worker            if self._set_event_loop:
76*cda5da8dSAndroid Build Coastguard Worker                events.set_event_loop(None)
77*cda5da8dSAndroid Build Coastguard Worker            loop.close()
78*cda5da8dSAndroid Build Coastguard Worker            self._loop = None
79*cda5da8dSAndroid Build Coastguard Worker            self._state = _State.CLOSED
80*cda5da8dSAndroid Build Coastguard Worker
81*cda5da8dSAndroid Build Coastguard Worker    def get_loop(self):
82*cda5da8dSAndroid Build Coastguard Worker        """Return embedded event loop."""
83*cda5da8dSAndroid Build Coastguard Worker        self._lazy_init()
84*cda5da8dSAndroid Build Coastguard Worker        return self._loop
85*cda5da8dSAndroid Build Coastguard Worker
86*cda5da8dSAndroid Build Coastguard Worker    def run(self, coro, *, context=None):
87*cda5da8dSAndroid Build Coastguard Worker        """Run a coroutine inside the embedded event loop."""
88*cda5da8dSAndroid Build Coastguard Worker        if not coroutines.iscoroutine(coro):
89*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("a coroutine was expected, got {!r}".format(coro))
90*cda5da8dSAndroid Build Coastguard Worker
91*cda5da8dSAndroid Build Coastguard Worker        if events._get_running_loop() is not None:
92*cda5da8dSAndroid Build Coastguard Worker            # fail fast with short traceback
93*cda5da8dSAndroid Build Coastguard Worker            raise RuntimeError(
94*cda5da8dSAndroid Build Coastguard Worker                "Runner.run() cannot be called from a running event loop")
95*cda5da8dSAndroid Build Coastguard Worker
96*cda5da8dSAndroid Build Coastguard Worker        self._lazy_init()
97*cda5da8dSAndroid Build Coastguard Worker
98*cda5da8dSAndroid Build Coastguard Worker        if context is None:
99*cda5da8dSAndroid Build Coastguard Worker            context = self._context
100*cda5da8dSAndroid Build Coastguard Worker        task = self._loop.create_task(coro, context=context)
101*cda5da8dSAndroid Build Coastguard Worker
102*cda5da8dSAndroid Build Coastguard Worker        if (threading.current_thread() is threading.main_thread()
103*cda5da8dSAndroid Build Coastguard Worker            and signal.getsignal(signal.SIGINT) is signal.default_int_handler
104*cda5da8dSAndroid Build Coastguard Worker        ):
105*cda5da8dSAndroid Build Coastguard Worker            sigint_handler = functools.partial(self._on_sigint, main_task=task)
106*cda5da8dSAndroid Build Coastguard Worker            try:
107*cda5da8dSAndroid Build Coastguard Worker                signal.signal(signal.SIGINT, sigint_handler)
108*cda5da8dSAndroid Build Coastguard Worker            except ValueError:
109*cda5da8dSAndroid Build Coastguard Worker                # `signal.signal` may throw if `threading.main_thread` does
110*cda5da8dSAndroid Build Coastguard Worker                # not support signals (e.g. embedded interpreter with signals
111*cda5da8dSAndroid Build Coastguard Worker                # not registered - see gh-91880)
112*cda5da8dSAndroid Build Coastguard Worker                sigint_handler = None
113*cda5da8dSAndroid Build Coastguard Worker        else:
114*cda5da8dSAndroid Build Coastguard Worker            sigint_handler = None
115*cda5da8dSAndroid Build Coastguard Worker
116*cda5da8dSAndroid Build Coastguard Worker        self._interrupt_count = 0
117*cda5da8dSAndroid Build Coastguard Worker        try:
118*cda5da8dSAndroid Build Coastguard Worker            return self._loop.run_until_complete(task)
119*cda5da8dSAndroid Build Coastguard Worker        except exceptions.CancelledError:
120*cda5da8dSAndroid Build Coastguard Worker            if self._interrupt_count > 0:
121*cda5da8dSAndroid Build Coastguard Worker                uncancel = getattr(task, "uncancel", None)
122*cda5da8dSAndroid Build Coastguard Worker                if uncancel is not None and uncancel() == 0:
123*cda5da8dSAndroid Build Coastguard Worker                    raise KeyboardInterrupt()
124*cda5da8dSAndroid Build Coastguard Worker            raise  # CancelledError
125*cda5da8dSAndroid Build Coastguard Worker        finally:
126*cda5da8dSAndroid Build Coastguard Worker            if (sigint_handler is not None
127*cda5da8dSAndroid Build Coastguard Worker                and signal.getsignal(signal.SIGINT) is sigint_handler
128*cda5da8dSAndroid Build Coastguard Worker            ):
129*cda5da8dSAndroid Build Coastguard Worker                signal.signal(signal.SIGINT, signal.default_int_handler)
130*cda5da8dSAndroid Build Coastguard Worker
131*cda5da8dSAndroid Build Coastguard Worker    def _lazy_init(self):
132*cda5da8dSAndroid Build Coastguard Worker        if self._state is _State.CLOSED:
133*cda5da8dSAndroid Build Coastguard Worker            raise RuntimeError("Runner is closed")
134*cda5da8dSAndroid Build Coastguard Worker        if self._state is _State.INITIALIZED:
135*cda5da8dSAndroid Build Coastguard Worker            return
136*cda5da8dSAndroid Build Coastguard Worker        if self._loop_factory is None:
137*cda5da8dSAndroid Build Coastguard Worker            self._loop = events.new_event_loop()
138*cda5da8dSAndroid Build Coastguard Worker            if not self._set_event_loop:
139*cda5da8dSAndroid Build Coastguard Worker                # Call set_event_loop only once to avoid calling
140*cda5da8dSAndroid Build Coastguard Worker                # attach_loop multiple times on child watchers
141*cda5da8dSAndroid Build Coastguard Worker                events.set_event_loop(self._loop)
142*cda5da8dSAndroid Build Coastguard Worker                self._set_event_loop = True
143*cda5da8dSAndroid Build Coastguard Worker        else:
144*cda5da8dSAndroid Build Coastguard Worker            self._loop = self._loop_factory()
145*cda5da8dSAndroid Build Coastguard Worker        if self._debug is not None:
146*cda5da8dSAndroid Build Coastguard Worker            self._loop.set_debug(self._debug)
147*cda5da8dSAndroid Build Coastguard Worker        self._context = contextvars.copy_context()
148*cda5da8dSAndroid Build Coastguard Worker        self._state = _State.INITIALIZED
149*cda5da8dSAndroid Build Coastguard Worker
150*cda5da8dSAndroid Build Coastguard Worker    def _on_sigint(self, signum, frame, main_task):
151*cda5da8dSAndroid Build Coastguard Worker        self._interrupt_count += 1
152*cda5da8dSAndroid Build Coastguard Worker        if self._interrupt_count == 1 and not main_task.done():
153*cda5da8dSAndroid Build Coastguard Worker            main_task.cancel()
154*cda5da8dSAndroid Build Coastguard Worker            # wakeup loop if it is blocked by select() with long timeout
155*cda5da8dSAndroid Build Coastguard Worker            self._loop.call_soon_threadsafe(lambda: None)
156*cda5da8dSAndroid Build Coastguard Worker            return
157*cda5da8dSAndroid Build Coastguard Worker        raise KeyboardInterrupt()
158*cda5da8dSAndroid Build Coastguard Worker
159*cda5da8dSAndroid Build Coastguard Worker
160*cda5da8dSAndroid Build Coastguard Workerdef run(main, *, debug=None):
161*cda5da8dSAndroid Build Coastguard Worker    """Execute the coroutine and return the result.
162*cda5da8dSAndroid Build Coastguard Worker
163*cda5da8dSAndroid Build Coastguard Worker    This function runs the passed coroutine, taking care of
164*cda5da8dSAndroid Build Coastguard Worker    managing the asyncio event loop and finalizing asynchronous
165*cda5da8dSAndroid Build Coastguard Worker    generators.
166*cda5da8dSAndroid Build Coastguard Worker
167*cda5da8dSAndroid Build Coastguard Worker    This function cannot be called when another asyncio event loop is
168*cda5da8dSAndroid Build Coastguard Worker    running in the same thread.
169*cda5da8dSAndroid Build Coastguard Worker
170*cda5da8dSAndroid Build Coastguard Worker    If debug is True, the event loop will be run in debug mode.
171*cda5da8dSAndroid Build Coastguard Worker
172*cda5da8dSAndroid Build Coastguard Worker    This function always creates a new event loop and closes it at the end.
173*cda5da8dSAndroid Build Coastguard Worker    It should be used as a main entry point for asyncio programs, and should
174*cda5da8dSAndroid Build Coastguard Worker    ideally only be called once.
175*cda5da8dSAndroid Build Coastguard Worker
176*cda5da8dSAndroid Build Coastguard Worker    Example:
177*cda5da8dSAndroid Build Coastguard Worker
178*cda5da8dSAndroid Build Coastguard Worker        async def main():
179*cda5da8dSAndroid Build Coastguard Worker            await asyncio.sleep(1)
180*cda5da8dSAndroid Build Coastguard Worker            print('hello')
181*cda5da8dSAndroid Build Coastguard Worker
182*cda5da8dSAndroid Build Coastguard Worker        asyncio.run(main())
183*cda5da8dSAndroid Build Coastguard Worker    """
184*cda5da8dSAndroid Build Coastguard Worker    if events._get_running_loop() is not None:
185*cda5da8dSAndroid Build Coastguard Worker        # fail fast with short traceback
186*cda5da8dSAndroid Build Coastguard Worker        raise RuntimeError(
187*cda5da8dSAndroid Build Coastguard Worker            "asyncio.run() cannot be called from a running event loop")
188*cda5da8dSAndroid Build Coastguard Worker
189*cda5da8dSAndroid Build Coastguard Worker    with Runner(debug=debug) as runner:
190*cda5da8dSAndroid Build Coastguard Worker        return runner.run(main)
191*cda5da8dSAndroid Build Coastguard Worker
192*cda5da8dSAndroid Build Coastguard Worker
193*cda5da8dSAndroid Build Coastguard Workerdef _cancel_all_tasks(loop):
194*cda5da8dSAndroid Build Coastguard Worker    to_cancel = tasks.all_tasks(loop)
195*cda5da8dSAndroid Build Coastguard Worker    if not to_cancel:
196*cda5da8dSAndroid Build Coastguard Worker        return
197*cda5da8dSAndroid Build Coastguard Worker
198*cda5da8dSAndroid Build Coastguard Worker    for task in to_cancel:
199*cda5da8dSAndroid Build Coastguard Worker        task.cancel()
200*cda5da8dSAndroid Build Coastguard Worker
201*cda5da8dSAndroid Build Coastguard Worker    loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
202*cda5da8dSAndroid Build Coastguard Worker
203*cda5da8dSAndroid Build Coastguard Worker    for task in to_cancel:
204*cda5da8dSAndroid Build Coastguard Worker        if task.cancelled():
205*cda5da8dSAndroid Build Coastguard Worker            continue
206*cda5da8dSAndroid Build Coastguard Worker        if task.exception() is not None:
207*cda5da8dSAndroid Build Coastguard Worker            loop.call_exception_handler({
208*cda5da8dSAndroid Build Coastguard Worker                'message': 'unhandled exception during asyncio.run() shutdown',
209*cda5da8dSAndroid Build Coastguard Worker                'exception': task.exception(),
210*cda5da8dSAndroid Build Coastguard Worker                'task': task,
211*cda5da8dSAndroid Build Coastguard Worker            })
212