From a0c1fde9ec0bec2582400156bca37ad111eccb5b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 12 Oct 2023 16:31:04 +0200 Subject: [PATCH] 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)