Skip to content

Commit

Permalink
bpo-39010: Fix errors logged on proactor loop restart (#22017)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bdarnell authored Aug 31, 2020
1 parent c3a651a commit ea5a636
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 2 deletions.
8 changes: 8 additions & 0 deletions Lib/asyncio/proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,14 @@ def _loop_self_reading(self, f=None):
try:
if f is not None:
f.result() # may raise
if self._self_reading_future is not f:
# When we scheduled this Future, we assigned it to
# _self_reading_future. If it's not there now, something has
# tried to cancel the loop while this callback was still in the
# queue (see windows_events.ProactorEventLoop.run_forever). In
# that case stop here instead of continuing to schedule a new
# iteration.
return
f = self._proactor.recv(self._ssock, 4096)
except exceptions.CancelledError:
# _close_self_pipe() has been called, stop waiting for data
Expand Down
8 changes: 6 additions & 2 deletions Lib/asyncio/windows_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,12 @@ def run_forever(self):
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 it will never be signalled
# Unregister it otherwise IocpProactor.close will wait for it forever
# 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
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_asyncio/test_proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ def test_loop_self_reading(self):

def test_loop_self_reading_fut(self):
fut = mock.Mock()
self.loop._self_reading_future = fut
self.loop._loop_self_reading(fut)
self.assertTrue(fut.result.called)
self.proactor.recv.assert_called_with(self.ssock, 4096)
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_asyncio/test_windows_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,26 @@ def test_wait_for_handle_cancel(self):
fut.cancel()
fut.cancel()

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


class WinPolicyTests(test_utils.TestCase):

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

0 comments on commit ea5a636

Please sign in to comment.