From a0c1fde9ec0bec2582400156bca37ad111eccb5b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 12 Oct 2023 16:31:04 +0200 Subject: [PATCH 01/10] Decompose run_forever() into parts to allow external usage. --- Lib/asyncio/base_events.py | 54 +++++++++++++++++++++++++++-------- Lib/asyncio/windows_events.py | 37 ++++++++++++------------ 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index b092c9343634e2..3b3097206adb08 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -601,29 +601,59 @@ def _check_running(self): raise RuntimeError( 'Cannot run the event loop while another loop is running') - def run_forever(self): - """Run until stop() is called.""" + def run_forever_setup(self): + """Set up an event loop so that it is ready to start actively looping + to process events. + + Returns the state that must be restored when the loop concludes. This state + should be passed in as arguments to ``run_forever_cleanup()``. + + This method is only needed if you are writing your own event loop, with + customized ``run_forever`` semantics (e.g., integrating a GUI event loop + with Python's event loop). + """ self._check_closed() self._check_running() self._set_coroutine_origin_tracking(self._debug) old_agen_hooks = sys.get_asyncgen_hooks() - try: - self._thread_id = threading.get_ident() - sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook, - finalizer=self._asyncgen_finalizer_hook) + self._thread_id = threading.get_ident() + sys.set_asyncgen_hooks( + firstiter=self._asyncgen_firstiter_hook, + finalizer=self._asyncgen_finalizer_hook + ) + + events._set_running_loop(self) + + return (old_agen_hooks,) - events._set_running_loop(self) + def run_forever_cleanup(self, orig_state): + """Clean up an event loop after the event loop finishes the looping over + events. + + Restores any state preserved by ``run_forever_setup()``. + + This method is only needed if you are writing your own event loop, with + customized ``run_forever`` semantics (e.g., integrating a GUI event loop + with Python's event loop). + """ + old_agen_hooks, = orig_state + self._stopping = False + self._thread_id = None + events._set_running_loop(None) + self._set_coroutine_origin_tracking(False) + sys.set_asyncgen_hooks(*old_agen_hooks) + + def run_forever(self): + """Run until stop() is called.""" + try: + orig_state = self.run_forever_setup() while True: self._run_once() if self._stopping: break finally: - self._stopping = False - self._thread_id = None - events._set_running_loop(None) - self._set_coroutine_origin_tracking(False) - sys.set_asyncgen_hooks(*old_agen_hooks) + self.run_forever_cleanup(orig_state) def run_until_complete(self, future): """Run until the Future is done. diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index c9a5fb841cb134..d1f2ee3ede6b12 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -314,24 +314,25 @@ def __init__(self, proactor=None): proactor = IocpProactor() super().__init__(proactor) - def run_forever(self): - try: - assert self._self_reading_future is None - self.call_soon(self._loop_self_reading) - super().run_forever() - finally: - if self._self_reading_future is not None: - ov = self._self_reading_future._ov - self._self_reading_future.cancel() - # self_reading_future was just cancelled so if it hasn't been - # finished yet, it never will be (it's possible that it has - # already finished and its callback is waiting in the queue, - # where it could still happen if the event loop is restarted). - # Unregister it otherwise IocpProactor.close will wait for it - # forever - if ov is not None: - self._proactor._unregister(ov) - self._self_reading_future = None + def run_forever_setup(self): + assert self._self_reading_future is None + self.call_soon(self._loop_self_reading) + + return super().run_forever_setup() + + def run_forever_cleanup(self, orig_state): + if self._self_reading_future is not None: + ov = self._self_reading_future._ov + self._self_reading_future.cancel() + # self_reading_future was just cancelled so if it hasn't been + # finished yet, it never will be (it's possible that it has + # already finished and its callback is waiting in the queue, + # where it could still happen if the event loop is restarted). + # Unregister it otherwise IocpProactor.close will wait for it + # forever + if ov is not None: + self._proactor._unregister(ov) + self._self_reading_future = None async def create_pipe_connection(self, protocol_factory, address): f = self._proactor.connect_pipe(address) From 67bfc8f756c8b29c9abb33a71598e60729cc6486 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 06:47:24 +0000 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-10-13-06-47-20.gh-issue-110771.opwdlc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-13-06-47-20.gh-issue-110771.opwdlc.rst diff --git a/Misc/NEWS.d/next/Library/2023-10-13-06-47-20.gh-issue-110771.opwdlc.rst b/Misc/NEWS.d/next/Library/2023-10-13-06-47-20.gh-issue-110771.opwdlc.rst new file mode 100644 index 00000000000000..a22f8a0d5e56c3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-13-06-47-20.gh-issue-110771.opwdlc.rst @@ -0,0 +1 @@ +Expose the setup and cleanup portions of ``asyncio.run_forever()`` as the standalone methods ``asyncio.run_forever_setup()`` and ``asyncio.run_forever_cleanup()``. This allows for tighter integration with GUI event loops. From 3dcf97b3ee9623c2f716c93dfdcca6c1d16c14ed Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 10:26:42 +0200 Subject: [PATCH 03/10] Add docs for run_forever_setup and run_forever_cleanup. --- Doc/library/asyncio-eventloop.rst | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 04af53b980ff9e..204cecfbc57996 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -210,6 +210,44 @@ Running and stopping the loop .. versionchanged:: 3.12 Added the *timeout* parameter. +.. method:: loop.run_forever_setup() + + Set up an event loop so that it is ready to start actively looping and + processing events. + + Returns the state that must be restored when the loop concludes. This state + should be passed in as arguments to :meth:`loop.run_forever_cleanup()`. + + .. note:: + + This method is only needed if you are writing your own ``EventLoop`` + subclass, with a customized inner event processing loop. For example, if + you are integrating Python's asyncio event loop with a GUI library's event + loop, you can use this method to ensure that Python's event loop is + correctly configured and ready to start processing individual events. Most + end users will not need to use this method directly. + + .. versionadded:: 3.13 + +.. method:: loop.run_forever_cleanup(original_state) + + Perform any cleanup necessary at the conclusion of event processing to ensure + that the event loop has been fully shut down. + + The *original_state* argument is the return value provided by the call to + :meth:`loop.run_forever_setup()` that was used to set up the event loop. + + .. note:: + + This method is only needed if you are writing your own ``EventLoop`` + subclass, with a customized inner event processing loop. For example, if + you are integrating Python's asyncio event loop with a GUI library's event + loop, you can use this method to ensure that Python's event loop has been + fully shut down at the conclusion of processing events. Most end users + will not need to use this method directly. + + .. versionadded:: 3.13 + Scheduling callbacks ^^^^^^^^^^^^^^^^^^^^ From 35271dc9947acb1b8e759a6af253de1e1ffce27d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 10:34:37 +0200 Subject: [PATCH 04/10] Ensure failures during setup don't prevent cleanup. --- Lib/asyncio/base_events.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 3b3097206adb08..ff85f41d6488e6 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -627,7 +627,7 @@ def run_forever_setup(self): return (old_agen_hooks,) - def run_forever_cleanup(self, orig_state): + def run_forever_cleanup(self, original_state): """Clean up an event loop after the event loop finishes the looping over events. @@ -637,23 +637,26 @@ def run_forever_cleanup(self, orig_state): customized ``run_forever`` semantics (e.g., integrating a GUI event loop with Python's event loop). """ - old_agen_hooks, = orig_state self._stopping = False self._thread_id = None events._set_running_loop(None) self._set_coroutine_origin_tracking(False) - sys.set_asyncgen_hooks(*old_agen_hooks) + if original_state is not None: + old_agen_hooks, = original_state + sys.set_asyncgen_hooks(*old_agen_hooks) def run_forever(self): """Run until stop() is called.""" + # Ensure original_state has a value in case setup fails. + original_state = None try: - orig_state = self.run_forever_setup() + original_state = self.run_forever_setup() while True: self._run_once() if self._stopping: break finally: - self.run_forever_cleanup(orig_state) + self.run_forever_cleanup(original_state) def run_until_complete(self, future): """Run until the Future is done. From 6672f6875eee9d281889f1fc34180c913f4faed2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 12:07:27 +0200 Subject: [PATCH 05/10] Add tests for custom event loop implementation. --- Lib/test/test_asyncio/test_base_events.py | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index abcb6f55c4b04e..e58593c1ef036a 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -922,6 +922,43 @@ def test_run_forever_pre_stopped(self): self.loop.run_forever() self.loop._selector.select.assert_called_once_with(0) + def test_custom_run_forever_integration(self): + # Test that the run_forever_setup() and run_forever_cleanup() primitives + # can be used to implement a custom run_forever loop. + self.loop._process_events = mock.Mock() + + count = 0 + + def callback(): + nonlocal count + count += 1 + + self.loop.call_soon(callback) + + # Set up the custom event loop + orig_state = self.loop.run_forever_setup() + + # Confirm the loop has been started + self.assertEqual(asyncio.get_running_loop(), self.loop) + self.assertTrue(self.loop.is_running()) + + # Our custom "event loop" just iterates 10 times before exiting. + for i in range(10): + self.loop._run_once() + + # Clean up the event loop + self.loop.run_forever_cleanup(orig_state) + + # Confirm the loop has been cleaned up + with self.assertRaises(RuntimeError): + asyncio.get_running_loop() + self.assertFalse(self.loop.is_running()) + + # Confirm the loop actually did run, processing events 10 times, + # and invoking the callback once. + self.assertEqual(self.loop._process_events.call_count, 10) + self.assertEqual(count, 1) + async def leave_unfinalized_asyncgen(self): # Create an async generator, iterate it partially, and leave it # to be garbage collected. From 54349c3decf269aac4abc75bc0459ec7283c323f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 14:17:20 +0200 Subject: [PATCH 06/10] Store pre-event loop state as a protected variable. --- Lib/asyncio/base_events.py | 44 ++++++++++------------- Lib/test/test_asyncio/test_base_events.py | 4 +-- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index ff85f41d6488e6..fbb8b422a98160 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -400,6 +400,8 @@ def __init__(self): self._clock_resolution = time.get_clock_info('monotonic').resolution self._exception_handler = None self.set_debug(coroutines._is_debug_mode()) + # The preserved state of async generator hooks. + self._old_agen_hooks = None # In debug mode, if the execution of a callback or a step of a task # exceed this duration in seconds, the slow callback/task is logged. self.slow_callback_duration = 0.1 @@ -602,21 +604,17 @@ def _check_running(self): 'Cannot run the event loop while another loop is running') def run_forever_setup(self): - """Set up an event loop so that it is ready to start actively looping - to process events. + """Prepare the run loop to process events. - Returns the state that must be restored when the loop concludes. This state - should be passed in as arguments to ``run_forever_cleanup()``. - - This method is only needed if you are writing your own event loop, with - customized ``run_forever`` semantics (e.g., integrating a GUI event loop - with Python's event loop). + This method should be used as part of the ``run_forever()`` + implementation in a custom event loop subclass (e.g., integrating a GUI + event loop with Python's event loop). """ self._check_closed() self._check_running() self._set_coroutine_origin_tracking(self._debug) - old_agen_hooks = sys.get_asyncgen_hooks() + self._old_agen_hooks = sys.get_asyncgen_hooks() self._thread_id = threading.get_ident() sys.set_asyncgen_hooks( firstiter=self._asyncgen_firstiter_hook, @@ -625,38 +623,32 @@ def run_forever_setup(self): events._set_running_loop(self) - return (old_agen_hooks,) - - def run_forever_cleanup(self, original_state): - """Clean up an event loop after the event loop finishes the looping over - events. - - Restores any state preserved by ``run_forever_setup()``. + def run_forever_cleanup(self): + """Clean up after an event loop finishes the looping over events. - This method is only needed if you are writing your own event loop, with - customized ``run_forever`` semantics (e.g., integrating a GUI event loop - with Python's event loop). + This method should be used as part of the ``run_forever()`` + implementation in a custom event loop subclass (e.g., integrating a GUI + event loop with Python's event loop). """ self._stopping = False self._thread_id = None events._set_running_loop(None) self._set_coroutine_origin_tracking(False) - if original_state is not None: - old_agen_hooks, = original_state - sys.set_asyncgen_hooks(*old_agen_hooks) + # Restore any pre-existing async generator hooks. + if self._old_agen_hooks is not None: + sys.set_asyncgen_hooks(*self._old_agen_hooks) + self._old_agen_hooks = None def run_forever(self): """Run until stop() is called.""" - # Ensure original_state has a value in case setup fails. - original_state = None try: - original_state = self.run_forever_setup() + self.run_forever_setup() while True: self._run_once() if self._stopping: break finally: - self.run_forever_cleanup(original_state) + self.run_forever_cleanup() def run_until_complete(self, future): """Run until the Future is done. diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index e58593c1ef036a..ce0a81ad7f4c04 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -936,7 +936,7 @@ def callback(): self.loop.call_soon(callback) # Set up the custom event loop - orig_state = self.loop.run_forever_setup() + self.loop.run_forever_setup() # Confirm the loop has been started self.assertEqual(asyncio.get_running_loop(), self.loop) @@ -947,7 +947,7 @@ def callback(): self.loop._run_once() # Clean up the event loop - self.loop.run_forever_cleanup(orig_state) + self.loop.run_forever_cleanup() # Confirm the loop has been cleaned up with self.assertRaises(RuntimeError): From e6226a5cb7c24bd00dec0de14cf8cc1aeae3559c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 14:17:39 +0200 Subject: [PATCH 07/10] Add a missing super() call. --- Lib/asyncio/windows_events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index d1f2ee3ede6b12..3a502844d274ec 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -321,6 +321,7 @@ def run_forever_setup(self): return super().run_forever_setup() def run_forever_cleanup(self, orig_state): + super().run_forever_cleanup() if self._self_reading_future is not None: ov = self._self_reading_future._ov self._self_reading_future.cancel() From 23852d0fcd3e1b1d24ecdc3132608238bbf198b2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 14:18:20 +0200 Subject: [PATCH 08/10] Improvements to docs, including a prototype custom event loop. --- Doc/library/asyncio-eventloop.rst | 50 +++++++++++++++++++------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 204cecfbc57996..2ba1fcb24f9cf8 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -215,36 +215,48 @@ Running and stopping the loop Set up an event loop so that it is ready to start actively looping and processing events. - Returns the state that must be restored when the loop concludes. This state - should be passed in as arguments to :meth:`loop.run_forever_cleanup()`. - .. note:: - This method is only needed if you are writing your own ``EventLoop`` - subclass, with a customized inner event processing loop. For example, if - you are integrating Python's asyncio event loop with a GUI library's event - loop, you can use this method to ensure that Python's event loop is - correctly configured and ready to start processing individual events. Most - end users will not need to use this method directly. + End users should not use this method directly. This method is only needed + if you are writing your own ``EventLoop`` subclass, with a customized + event processing loop. For example, if you are integrating Python's + asyncio event loop with a GUI library's event loop, you may need to write + a customized :meth:`loop.run_forever` implementation that accommodates + both CPython's event loop and the GUI library's event loop. You can use + this method to ensure that Python's event loop is correctly configured and + ready to start processing events. + + The specific details of a customized ``EventLoop`` subclass will depend + on the GUI library you are integrating with. However, the broad structure + of a custom ``EventLoop`` would look something like:: + + class CustomGUIEventLoop(EventLoop): + def run_forever(self): + try: + self.run_forever_setup() + gui_library.setup() + while True: + self._run_once() + gui_library.process_events() + if self._stopping: + break + finally: + self.run_forever_cleanup() + gui_library.cleanup() .. versionadded:: 3.13 -.. method:: loop.run_forever_cleanup(original_state) +.. method:: loop.run_forever_cleanup() Perform any cleanup necessary at the conclusion of event processing to ensure that the event loop has been fully shut down. - The *original_state* argument is the return value provided by the call to - :meth:`loop.run_forever_setup()` that was used to set up the event loop. - .. note:: - This method is only needed if you are writing your own ``EventLoop`` - subclass, with a customized inner event processing loop. For example, if - you are integrating Python's asyncio event loop with a GUI library's event - loop, you can use this method to ensure that Python's event loop has been - fully shut down at the conclusion of processing events. Most end users - will not need to use this method directly. + End users should not use this method directly. This method is only needed + if you are writing your own ``EventLoop`` subclass, with a customized + inner event processing loop. See :meth:`loop.run_forever_setup()` for + details on why and how to use this method. .. versionadded:: 3.13 From e7f892e529b2f8b3eb0e1d6fce5b3bb22e449385 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 14:49:54 +0200 Subject: [PATCH 09/10] Correct prototype of run_forever_cleanup on Windows Proactor. --- Lib/asyncio/windows_events.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index 3a502844d274ec..9f5269bf94c115 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -317,10 +317,9 @@ def __init__(self, proactor=None): def run_forever_setup(self): assert self._self_reading_future is None self.call_soon(self._loop_self_reading) + super().run_forever_setup() - return super().run_forever_setup() - - def run_forever_cleanup(self, orig_state): + def run_forever_cleanup(self): super().run_forever_cleanup() if self._self_reading_future is not None: ov = self._self_reading_future._ov From 3143126de9b18902ddc3767c86796fc6f5660e79 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 13 Oct 2023 15:45:14 +0200 Subject: [PATCH 10/10] Keep the setup/cleanup methods as protected API. --- Doc/library/asyncio-eventloop.rst | 50 ----------------------- Lib/asyncio/base_events.py | 20 ++++----- Lib/asyncio/windows_events.py | 8 ++-- Lib/test/test_asyncio/test_base_events.py | 4 +- 4 files changed, 16 insertions(+), 66 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 21025079e0f8c9..361e7bb9c8f2fa 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -210,56 +210,6 @@ Running and stopping the loop .. versionchanged:: 3.12 Added the *timeout* parameter. -.. method:: loop.run_forever_setup() - - Set up an event loop so that it is ready to start actively looping and - processing events. - - .. note:: - - End users should not use this method directly. This method is only needed - if you are writing your own ``EventLoop`` subclass, with a customized - event processing loop. For example, if you are integrating Python's - asyncio event loop with a GUI library's event loop, you may need to write - a customized :meth:`loop.run_forever` implementation that accommodates - both CPython's event loop and the GUI library's event loop. You can use - this method to ensure that Python's event loop is correctly configured and - ready to start processing events. - - The specific details of a customized ``EventLoop`` subclass will depend - on the GUI library you are integrating with. However, the broad structure - of a custom ``EventLoop`` would look something like:: - - class CustomGUIEventLoop(EventLoop): - def run_forever(self): - try: - self.run_forever_setup() - gui_library.setup() - while True: - self._run_once() - gui_library.process_events() - if self._stopping: - break - finally: - self.run_forever_cleanup() - gui_library.cleanup() - - .. versionadded:: 3.13 - -.. method:: loop.run_forever_cleanup() - - Perform any cleanup necessary at the conclusion of event processing to ensure - that the event loop has been fully shut down. - - .. note:: - - End users should not use this method directly. This method is only needed - if you are writing your own ``EventLoop`` subclass, with a customized - inner event processing loop. See :meth:`loop.run_forever_setup()` for - details on why and how to use this method. - - .. versionadded:: 3.13 - Scheduling callbacks ^^^^^^^^^^^^^^^^^^^^ diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 88ef4b5ba15aef..0476de631a6a52 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -603,12 +603,12 @@ def _check_running(self): raise RuntimeError( 'Cannot run the event loop while another loop is running') - def run_forever_setup(self): + def _run_forever_setup(self): """Prepare the run loop to process events. - This method should be used as part of the ``run_forever()`` - implementation in a custom event loop subclass (e.g., integrating a GUI - event loop with Python's event loop). + This method exists so that custom custom event loop subclasses (e.g., event loops + that integrate a GUI event loop with Python's event loop) have access to all the + loop setup logic. """ self._check_closed() self._check_running() @@ -623,12 +623,12 @@ def run_forever_setup(self): events._set_running_loop(self) - def run_forever_cleanup(self): + def _run_forever_cleanup(self): """Clean up after an event loop finishes the looping over events. - This method should be used as part of the ``run_forever()`` - implementation in a custom event loop subclass (e.g., integrating a GUI - event loop with Python's event loop). + This method exists so that custom custom event loop subclasses (e.g., event loops + that integrate a GUI event loop with Python's event loop) have access to all the + loop cleanup logic. """ self._stopping = False self._thread_id = None @@ -642,13 +642,13 @@ def run_forever_cleanup(self): def run_forever(self): """Run until stop() is called.""" try: - self.run_forever_setup() + self._run_forever_setup() while True: self._run_once() if self._stopping: break finally: - self.run_forever_cleanup() + self._run_forever_cleanup() def run_until_complete(self, future): """Run until the Future is done. diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index 7f7fcb9f9014b8..b62ea75fee3858 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -314,13 +314,13 @@ def __init__(self, proactor=None): proactor = IocpProactor() super().__init__(proactor) - def run_forever_setup(self): + def _run_forever_setup(self): assert self._self_reading_future is None self.call_soon(self._loop_self_reading) - super().run_forever_setup() + super()._run_forever_setup() - def run_forever_cleanup(self): - super().run_forever_cleanup() + def _run_forever_cleanup(self): + super()._run_forever_cleanup() if self._self_reading_future is not None: ov = self._self_reading_future._ov self._self_reading_future.cancel() diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index ce0a81ad7f4c04..c2080977e9d587 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -936,7 +936,7 @@ def callback(): self.loop.call_soon(callback) # Set up the custom event loop - self.loop.run_forever_setup() + self.loop._run_forever_setup() # Confirm the loop has been started self.assertEqual(asyncio.get_running_loop(), self.loop) @@ -947,7 +947,7 @@ def callback(): self.loop._run_once() # Clean up the event loop - self.loop.run_forever_cleanup() + self.loop._run_forever_cleanup() # Confirm the loop has been cleaned up with self.assertRaises(RuntimeError):