From f6b7bab62c9ec3bbcd4524b6a285267a1cd5b9be Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 21 Jun 2022 14:02:55 +0100 Subject: [PATCH 1/8] restore the set_event_loop calls to asyncio.run Fixes #93896 --- Lib/asyncio/runners.py | 41 +++++++++++++++++++++++++++++++------- Lib/unittest/async_case.py | 2 +- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py index f524c3b8e424f5..756adccb894f2f 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 and 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,13 +56,14 @@ 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=False): self._state = _State.CREATED self._debug = debug self._loop_factory = loop_factory self._loop = None self._context = None self._interrupt_count = 0 + self._set_policy_loop = set_policy_loop def __enter__(self): self._lazy_init() @@ -66,10 +78,14 @@ 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_policy_loop: + events.set_event_loop(None) loop.close() self._loop = None self._state = _State.CLOSED @@ -90,10 +106,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 @@ -111,13 +128,17 @@ def run(self, coro, *, context=None): self._interrupt_count = 0 try: - 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 ): @@ -129,7 +150,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._loop = _new_event_loop() else: self._loop = self._loop_factory() if self._debug is not None: @@ -159,7 +180,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. @@ -176,7 +199,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/unittest/async_case.py b/Lib/unittest/async_case.py index a90eed98f87140..98cd5785e30324 100644 --- a/Lib/unittest/async_case.py +++ b/Lib/unittest/async_case.py @@ -114,7 +114,7 @@ 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): From 428cc8aef85e6592b5c6959411309898a4e2a234 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:08:48 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst 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..0b1b1ff7f334f4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst @@ -0,0 +1 @@ +restore the set_event_loop calls to asyncio.run and AsyncTestCase removed in the 3.11 betas From 7786b44539480337c6832917943229b8a298c3a4 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 21 Jun 2022 14:57:58 +0100 Subject: [PATCH 3/8] test that asyncio.Runner does not touch the asyncio policy system --- Lib/test/test_asyncio/test_runners.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py index 7780a5f15e1b44..ac8942a588fcb6 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): @@ -60,7 +73,7 @@ async def shutdown_asyncgens(): def setUp(self): super().setUp() - policy = TestPolicy(self.new_loop) + policy = NullPolicy() asyncio.set_event_loop_policy(policy) def tearDown(self): @@ -75,6 +88,12 @@ def tearDown(self): class RunTests(BaseTest): + def setUp(self): + super().setUp() + + policy = TestPolicy(self.new_loop) + asyncio.set_event_loop_policy(policy) + def test_asyncio_run_return(self): async def main(): await asyncio.sleep(0) From abbd6b4dded38ee3c0b48a113b9df48a09146163 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 21 Jun 2022 16:07:55 +0100 Subject: [PATCH 4/8] only check policy loop in tearDown --- Lib/test/test_asyncio/test_runners.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py index ac8942a588fcb6..a986884eebb8f7 100644 --- a/Lib/test/test_asyncio/test_runners.py +++ b/Lib/test/test_asyncio/test_runners.py @@ -77,11 +77,6 @@ def setUp(self): asyncio.set_event_loop_policy(policy) def tearDown(self): - policy = asyncio.get_event_loop_policy() - if policy.loop is not None: - self.assertTrue(policy.loop.is_closed()) - self.assertTrue(policy.loop.shutdown_ag_run) - asyncio.set_event_loop_policy(None) super().tearDown() @@ -94,6 +89,15 @@ def setUp(self): policy = TestPolicy(self.new_loop) asyncio.set_event_loop_policy(policy) + def tearDown(self): + policy = asyncio.get_event_loop_policy() + if policy.loop is not None: + self.assertTrue(policy.loop.is_closed()) + self.assertTrue(policy.loop.shutdown_ag_run) + + super().tearDown() + + def test_asyncio_run_return(self): async def main(): await asyncio.sleep(0) From d11310246b2d5b40b44454a6473bd91fa5ea1443 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 22 Jun 2022 14:22:19 +0100 Subject: [PATCH 5/8] Update Lib/asyncio/runners.py --- Lib/asyncio/runners.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py index 756adccb894f2f..b63d0ab048d1ff 100644 --- a/Lib/asyncio/runners.py +++ b/Lib/asyncio/runners.py @@ -33,7 +33,7 @@ class Runner: 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, otherwise - a ProactorEventLoop will be started on Windows and a SelectorEventLoop will + 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. From 35e746fcf50e50508f3a45ccc2e806448e824427 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 22 Jun 2022 14:23:23 +0100 Subject: [PATCH 6/8] Update Lib/unittest/async_case.py --- Lib/unittest/async_case.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/unittest/async_case.py b/Lib/unittest/async_case.py index 98cd5785e30324..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, loop_factory=asyncio.new_event_loop, set_policy_loop=True) + runner = asyncio.Runner( + debug=True, + loop_factory=asyncio.new_event_loop, + set_policy_loop=True, + ) self._asyncioRunner = runner def _tearDownAsyncioRunner(self): From 1431a6343199e4a7b4bc0955bbfd30848cd683a9 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 25 Jun 2022 15:25:19 +0100 Subject: [PATCH 7/8] Update Misc/NEWS.d/next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst Co-authored-by: Bluenix --- .../next/Library/2022-06-21-13-08-46.gh-issue-93896.2BsYCX.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0b1b1ff7f334f4..fe96fb46072205 100644 --- 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 @@ -1 +1 @@ -restore the set_event_loop calls to asyncio.run and AsyncTestCase removed in the 3.11 betas +Restored missing ``set_event_loop()`` calls in ``asyncio.run()`` and ``AsyncTestCase`` accidentally removed in the 3.11 betas. From 5865ac636f3149b91dafe45f82daa1cc981ddd34 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 6 Jul 2022 16:25:00 +0100 Subject: [PATCH 8/8] Apply suggestions from code review --- Lib/asyncio/runners.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py index b63d0ab048d1ff..a022ad10713932 100644 --- a/Lib/asyncio/runners.py +++ b/Lib/asyncio/runners.py @@ -56,14 +56,18 @@ class Runner: # Note: the class is final, it is not intended for inheritance. - def __init__(self, *, debug=None, loop_factory=None, set_policy_loop=False): + 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_policy_loop = set_policy_loop + 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()