]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-39010: Fix errors logged on proactor loop restart (GH-22017) (#22034)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 3 Sep 2020 19:38:29 +0000 (12:38 -0700)
committerGitHub <noreply@github.com>
Thu, 3 Sep 2020 19:38:29 +0000 (12:38 -0700)
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 ea5a6363c3f8cc90b7c0cc573922b10f296073b6)

Co-authored-by: Ben Darnell <ben@bendarnell.com>
Co-authored-by: Ben Darnell <ben@bendarnell.com>
Lib/asyncio/proactor_events.py
Lib/asyncio/windows_events.py
Lib/test/test_asyncio/test_proactor_events.py
Lib/test/test_asyncio/test_windows_events.py
Misc/NEWS.d/next/Library/2020-08-30-10-24-26.bpo-39010._mzXJW.rst [new file with mode: 0644]

index 8338449aaa0a3ef843f18411081b6bb836b92542..6b6bf8945e588be3f67dd9b623b78df831c276bc 100644 (file)
@@ -766,6 +766,14 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
         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
index a6759b78bd801fab79bc94e9cfa4ea441653ba56..5e7cd795895d65456dc8a372421c8cc29c046bcd 100644 (file)
@@ -318,8 +318,12 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
             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
index b5d1df93efd650325d50ea6fefe0353c45b55d8c..451f86ae0274fd6a10059c98addacae88a1431da 100644 (file)
@@ -737,6 +737,7 @@ class BaseProactorEventLoopTests(test_utils.TestCase):
 
     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)
index 6b005702c9be73465283d56fa9d9eadc36e954d3..33388a87d48f3f178ab1041552e762b17b173f6e 100644 (file)
@@ -211,6 +211,26 @@ class ProactorTests(test_utils.TestCase):
         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):
 
diff --git a/Misc/NEWS.d/next/Library/2020-08-30-10-24-26.bpo-39010._mzXJW.rst b/Misc/NEWS.d/next/Library/2020-08-30-10-24-26.bpo-39010._mzXJW.rst
new file mode 100644 (file)
index 0000000..0d9015b
--- /dev/null
@@ -0,0 +1,2 @@
+Restarting a ``ProactorEventLoop`` on Windows no longer logs spurious
+``ConnectionResetErrors``.