1
__all__ = ('Runner', 'run')
9
from . import coroutines
11
from . import exceptions
13
from . import constants
15
class _State(enum.Enum):
17
INITIALIZED = "initialized"
22
"""A context manager that controls event loop life cycle.
24
The context manager always creates a new event loop,
25
allows to run async functions inside it,
26
and properly finalizes the loop at the context manager exit.
28
If debug is True, the event loop will be run in debug mode.
29
If loop_factory is passed, it is used for new event loop creation.
31
asyncio.run(main(), debug=True)
35
with asyncio.Runner(debug=True) as runner:
38
The run() method can be called multiple times within the runner's
41
This can be useful for interactive console (e.g. IPython),
42
unittest runners, console tools, -- everywhere when async code
43
is called from existing sync framework and where the preferred single
44
asyncio.run() call doesn't work.
48
# Note: the class is final, it is not intended for inheritance.
50
def __init__(self, *, debug=None, loop_factory=None):
51
self._state = _State.CREATED
53
self._loop_factory = loop_factory
56
self._interrupt_count = 0
57
self._set_event_loop = False
63
def __exit__(self, exc_type, exc_val, exc_tb):
67
"""Shutdown and close event loop."""
68
if self._state is not _State.INITIALIZED:
72
_cancel_all_tasks(loop)
73
loop.run_until_complete(loop.shutdown_asyncgens())
74
loop.run_until_complete(
75
loop.shutdown_default_executor(constants.THREAD_JOIN_TIMEOUT))
77
if self._set_event_loop:
78
events.set_event_loop(None)
81
self._state = _State.CLOSED
84
"""Return embedded event loop."""
88
def run(self, coro, *, context=None):
89
"""Run code in the embedded event loop."""
90
if events._get_running_loop() is not None:
91
# fail fast with short traceback
93
"Runner.run() cannot be called from a running event loop")
97
if not coroutines.iscoroutine(coro):
98
if inspect.isawaitable(coro):
99
async def _wrap_awaitable(awaitable):
100
return await awaitable
102
coro = _wrap_awaitable(coro)
104
raise TypeError('An asyncio.Future, a coroutine or an '
105
'awaitable is required')
108
context = self._context
110
task = self._loop.create_task(coro, context=context)
112
if (threading.current_thread() is threading.main_thread()
113
and signal.getsignal(signal.SIGINT) is signal.default_int_handler
115
sigint_handler = functools.partial(self._on_sigint, main_task=task)
117
signal.signal(signal.SIGINT, sigint_handler)
119
# `signal.signal` may throw if `threading.main_thread` does
120
# not support signals (e.g. embedded interpreter with signals
121
# not registered - see gh-91880)
122
sigint_handler = None
124
sigint_handler = None
126
self._interrupt_count = 0
128
750
return self._loop.run_until_complete(task)
129
except exceptions.CancelledError:
130
if self._interrupt_count > 0:
131
uncancel = getattr(task, "uncancel", None)
132
if uncancel is not None and uncancel() == 0:
133
raise KeyboardInterrupt()
134
raise # CancelledError
136
if (sigint_handler is not None
137
and signal.getsignal(signal.SIGINT) is sigint_handler
139
signal.signal(signal.SIGINT, signal.default_int_handler)
141
def _lazy_init(self):
142
if self._state is _State.CLOSED:
143
raise RuntimeError("Runner is closed")
144
if self._state is _State.INITIALIZED:
146
if self._loop_factory is None:
147
self._loop = events.new_event_loop()
148
if not self._set_event_loop:
149
# Call set_event_loop only once to avoid calling
150
# attach_loop multiple times on child watchers
151
events.set_event_loop(self._loop)
152
self._set_event_loop = True
154
self._loop = self._loop_factory()
155
if self._debug is not None:
156
self._loop.set_debug(self._debug)
157
self._context = contextvars.copy_context()
158
self._state = _State.INITIALIZED
160
def _on_sigint(self, signum, frame, main_task):
161
self._interrupt_count += 1
162
if self._interrupt_count == 1 and not main_task.done():
164
# wakeup loop if it is blocked by select() with long timeout
165
self._loop.call_soon_threadsafe(lambda: None)
167
raise KeyboardInterrupt()
170
def run(main, *, debug=None, loop_factory=None):
171
"""Execute the coroutine and return the result.
173
This function runs the passed coroutine, taking care of
174
managing the asyncio event loop, finalizing asynchronous
175
generators and closing the default executor.
177
This function cannot be called when another asyncio event loop is
178
running in the same thread.
180
If debug is True, the event loop will be run in debug mode.
181
If loop_factory is passed, it is used for new event loop creation.
183
This function always creates a new event loop and closes it at the end.
184
It should be used as a main entry point for asyncio programs, and should
185
ideally only be called once.
187
The executor is given a timeout duration of 5 minutes to shutdown.
188
If the executor hasn't finished within that duration, a warning is
189
emitted and the executor is closed.
194
await asyncio.sleep(1)
199
if events._get_running_loop() is not None:
200
# fail fast with short traceback
202
"asyncio.run() cannot be called from a running event loop")
204
with Runner(debug=debug, loop_factory=loop_factory) as runner:
205
750
return runner.run(main)
208
def _cancel_all_tasks(loop):
209
to_cancel = tasks.all_tasks(loop)
213
for task in to_cancel:
216
loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
218
for task in to_cancel:
221
if task.exception() is not None:
222
loop.call_exception_handler({
223
'message': 'unhandled exception during asyncio.run() shutdown',
224
'exception': task.exception(),