Skip to content

Commit a986b06

Browse files
bpo-39010: Fix errors logged on proactor loop restart (pythonGH-22017) (python#22035)
Stopping and restarting a proactor event loop on windows can lead to spurious errors logged (ConnectionResetError while reading from the self pipe). This fixes the issue by ensuring that we don't attempt to start multiple copies of the self-pipe reading loop. (cherry picked from commit ea5a636) Co-authored-by: Ben Darnell <ben@bendarnell.com> Co-authored-by: Ben Darnell <ben@bendarnell.com>
1 parent 9fef7c5 commit a986b06

File tree

5 files changed

+37
-2
lines changed

5 files changed

+37
-2
lines changed

Lib/asyncio/proactor_events.py

+8
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,14 @@ def _loop_self_reading(self, f=None):
766766
try:
767767
if f is not None:
768768
f.result() # may raise
769+
if self._self_reading_future is not f:
770+
# When we scheduled this Future, we assigned it to
771+
# _self_reading_future. If it's not there now, something has
772+
# tried to cancel the loop while this callback was still in the
773+
# queue (see windows_events.ProactorEventLoop.run_forever). In
774+
# that case stop here instead of continuing to schedule a new
775+
# iteration.
776+
return
769777
f = self._proactor.recv(self._ssock, 4096)
770778
except exceptions.CancelledError:
771779
# _close_self_pipe() has been called, stop waiting for data

Lib/asyncio/windows_events.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,12 @@ def run_forever(self):
318318
if self._self_reading_future is not None:
319319
ov = self._self_reading_future._ov
320320
self._self_reading_future.cancel()
321-
# self_reading_future was just cancelled so it will never be signalled
322-
# Unregister it otherwise IocpProactor.close will wait for it forever
321+
# self_reading_future was just cancelled so if it hasn't been
322+
# finished yet, it never will be (it's possible that it has
323+
# already finished and its callback is waiting in the queue,
324+
# where it could still happen if the event loop is restarted).
325+
# Unregister it otherwise IocpProactor.close will wait for it
326+
# forever
323327
if ov is not None:
324328
self._proactor._unregister(ov)
325329
self._self_reading_future = None

Lib/test/test_asyncio/test_proactor_events.py

+1
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ def test_loop_self_reading(self):
736736

737737
def test_loop_self_reading_fut(self):
738738
fut = mock.Mock()
739+
self.loop._self_reading_future = fut
739740
self.loop._loop_self_reading(fut)
740741
self.assertTrue(fut.result.called)
741742
self.proactor.recv.assert_called_with(self.ssock, 4096)

Lib/test/test_asyncio/test_windows_events.py

+20
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,26 @@ def test_wait_for_handle_cancel(self):
211211
fut.cancel()
212212
fut.cancel()
213213

214+
def test_read_self_pipe_restart(self):
215+
# Regression test for https://bugs.python.org/issue39010
216+
# Previously, restarting a proactor event loop in certain states
217+
# would lead to spurious ConnectionResetErrors being logged.
218+
self.loop.call_exception_handler = mock.Mock()
219+
# Start an operation in another thread so that the self-pipe is used.
220+
# This is theoretically timing-dependent (the task in the executor
221+
# must complete before our start/stop cycles), but in practice it
222+
# seems to work every time.
223+
f = self.loop.run_in_executor(None, lambda: None)
224+
self.loop.stop()
225+
self.loop.run_forever()
226+
self.loop.stop()
227+
self.loop.run_forever()
228+
# If we don't wait for f to complete here, we may get another
229+
# warning logged about a thread that didn't shut down cleanly.
230+
self.loop.run_until_complete(f)
231+
self.loop.close()
232+
self.assertFalse(self.loop.call_exception_handler.called)
233+
214234

215235
class WinPolicyTests(test_utils.TestCase):
216236

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Restarting a ``ProactorEventLoop`` on Windows no longer logs spurious
2+
``ConnectionResetErrors``.

0 commit comments

Comments
 (0)