diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py index 7489a50f8e48da..a022ad10713932 100644 --- a/Lib/asyncio/runners.py +++ b/Lib/asyncio/runners.py @@ -1,5 +1,6 @@ __all__ = ('Runner', 'run') +import sys import contextvars import enum import functools @@ -17,6 +18,12 @@ class _State(enum.Enum): CLOSED = "closed" +if sys.platform == "win32": + from .windows_events import ProactorEventLoop as _new_event_loop +else: + from .unix_events import SelectorEventLoop as _new_event_loop + + class Runner: """A context manager that controls event loop life cycle. @@ -25,7 +32,11 @@ class Runner: and properly finalizes the loop at the context manager exit. If debug is True, the event loop will be run in debug mode. - If loop_factory is passed, it is used for new event loop creation. + If loop_factory is passed, it is used for new event loop creation, otherwise + a ProactorEventLoop will be started on Windows or a SelectorEventLoop will + be started on unix + If set_policy_loop is True, the event loop in the default policy will be + set. asyncio.run(main(), debug=True) @@ -45,14 +56,18 @@ class Runner: # Note: the class is final, it is not intended for inheritance. - def __init__(self, *, debug=None, loop_factory=None): + def __init__(self, *, debug=None, loop_factory=None, set_policy_loop=None): self._state = _State.CREATED self._debug = debug self._loop_factory = loop_factory self._loop = None self._context = None self._interrupt_count = 0 - self._set_event_loop = False + if set_policy_loop is None and loop_factory is None: + # default to setting the policy loop, if the loop_factory is not set + self._set_policy_loop = True + else: + self._set_policy_loop = set_policy_loop def __enter__(self): self._lazy_init() @@ -67,11 +82,13 @@ def close(self): return try: loop = self._loop + if self._set_policy_loop: + events.set_event_loop(loop) _cancel_all_tasks(loop) loop.run_until_complete(loop.shutdown_asyncgens()) loop.run_until_complete(loop.shutdown_default_executor()) finally: - if self._set_event_loop: + if self._set_policy_loop: events.set_event_loop(None) loop.close() self._loop = None @@ -93,10 +110,11 @@ def run(self, coro, *, context=None): "Runner.run() cannot be called from a running event loop") self._lazy_init() + loop = self._loop if context is None: context = self._context - task = self._loop.create_task(coro, context=context) + task = loop.create_task(coro, context=context) if (threading.current_thread() is threading.main_thread() and signal.getsignal(signal.SIGINT) is signal.default_int_handler @@ -114,15 +132,17 @@ def run(self, coro, *, context=None): self._interrupt_count = 0 try: - if self._set_event_loop: - events.set_event_loop(self._loop) - return self._loop.run_until_complete(task) + if self._set_policy_loop: + events.set_event_loop(loop) + return loop.run_until_complete(task) except exceptions.CancelledError: if self._interrupt_count > 0 and task.uncancel() == 0: raise KeyboardInterrupt() else: raise # CancelledError finally: + if self._set_policy_loop: + events.set_event_loop(None) if (sigint_handler is not None and signal.getsignal(signal.SIGINT) is sigint_handler ): @@ -134,8 +154,7 @@ def _lazy_init(self): if self._state is _State.INITIALIZED: return if self._loop_factory is None: - self._loop = events.new_event_loop() - self._set_event_loop = True + self._loop = _new_event_loop() else: self._loop = self._loop_factory() if self._debug is not None: @@ -165,7 +184,9 @@ def run(main, *, debug=None): If debug is True, the event loop will be run in debug mode. - This function always creates a new event loop and closes it at the end. + This function always creates a new event loop from the default policy sets + it in the event loop policy and closes it at the end and sets the policy + loop to None. It should be used as a main entry point for asyncio programs, and should ideally only be called once. @@ -182,7 +203,11 @@ async def main(): raise RuntimeError( "asyncio.run() cannot be called from a running event loop") - with Runner(debug=debug) as runner: + with Runner( + debug=debug, + loop_factory=events.new_event_loop, + set_policy_loop=True, + ) as runner: return runner.run(main) diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py index 1f6a4983690cdc..2796e9a052365b 100644 --- a/Lib/test/test_asyncio/test_runners.py +++ b/Lib/test/test_asyncio/test_runners.py @@ -39,6 +39,19 @@ def set_event_loop(self, loop): self.loop = loop +class NullPolicy(asyncio.AbstractEventLoopPolicy): + + def get_event_loop(self): + # shouldn't ever be called by asyncio.run() + raise RuntimeError + + def new_event_loop(self): + raise RuntimeError + + def set_event_loop(self, loop): + raise RuntimeError + + class BaseTest(unittest.TestCase): def new_loop(self): @@ -57,6 +70,19 @@ async def shutdown_asyncgens(): return loop + def setUp(self): + super().setUp() + + policy = NullPolicy() + asyncio.set_event_loop_policy(policy) + + def tearDown(self): + asyncio.set_event_loop_policy(None) + super().tearDown() + + +class RunTests(BaseTest): + def setUp(self): super().setUp() @@ -69,12 +95,9 @@ def tearDown(self): self.assertTrue(policy.loop.is_closed()) self.assertTrue(policy.loop.shutdown_ag_run) - asyncio.set_event_loop_policy(None) super().tearDown() -class RunTests(BaseTest): - def test_asyncio_run_return(self): async def main(): await asyncio.sleep(0) diff --git a/Lib/unittest/async_case.py b/Lib/unittest/async_case.py index a90eed98f87140..459f6ae3041f90 100644 --- a/Lib/unittest/async_case.py +++ b/Lib/unittest/async_case.py @@ -114,7 +114,11 @@ def _callMaybeAsync(self, func, /, *args, **kwargs): def _setupAsyncioRunner(self): assert self._asyncioRunner is None, 'asyncio runner is already initialized' - runner = asyncio.Runner(debug=True) + runner = asyncio.Runner( + debug=True, + loop_factory=asyncio.new_event_loop, + set_policy_loop=True, + ) self._asyncioRunner = runner def _tearDownAsyncioRunner(self): diff --git a/Misc/NEWS.d/next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst b/Misc/NEWS.d/next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst new file mode 100644 index 00000000000000..fe96fb46072205 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst @@ -0,0 +1 @@ +Restored missing ``set_event_loop()`` calls in ``asyncio.run()`` and ``AsyncTestCase`` accidentally removed in the 3.11 betas.