https://github.com/python/cpython/commit/7b06d6f80df2729a68f78d2a346c043d10f82fd6
commit: 7b06d6f80df2729a68f78d2a346c043d10f82fd6
branch: main
author: Zap777 <[email protected]>
committer: asvetlov <[email protected]>
date: 2026-11-05T09:39:31+01:00
summary:

gh-160429: Ignore ConnectionResetError from shutdown() when closing proactor 
sockets (#160909)

* gh-160429: Ignore ConnectionResetError from shutdown() when closing proactor 
sockets

* Update Lib/asyncio/proactor_events.py

---------

Co-authored-by: Andrew Svetlov <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-29-21-43-53.gh-issue-160429.eWapMs.rst
M Lib/asyncio/proactor_events.py
M Lib/test/test_asyncio/test_proactor_events.py

diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py
index 6d2bcba1a1b375..fae36295f66c46 100644
--- a/Lib/asyncio/proactor_events.py
+++ b/Lib/asyncio/proactor_events.py
@@ -164,7 +164,12 @@ def _call_connection_lost(self, exc):
             # just close our end.  First calling shutdown() seems to
             # cure it, but maybe using DisconnectEx() would be better.
             if hasattr(self._sock, 'shutdown') and self._sock.fileno() != -1:
-                self._sock.shutdown(socket.SHUT_RDWR)
+                try:
+                    self._sock.shutdown(socket.SHUT_RDWR)
+                except ConnectionError:
+                    # The connection is already gone.
+                    # The peer has already reset the connection.
+                    pass
             self._sock.close()
             self._sock = None
             server = self._server
diff --git a/Lib/test/test_asyncio/test_proactor_events.py 
b/Lib/test/test_asyncio/test_proactor_events.py
index 7a4dd7eba329a0..9e3c29c2987269 100644
--- a/Lib/test/test_asyncio/test_proactor_events.py
+++ b/Lib/test/test_asyncio/test_proactor_events.py
@@ -336,6 +336,20 @@ def test_call_connection_lost(self):
         self.assertTrue(self.protocol.connection_lost.called)
         self.assertTrue(self.sock.close.called)
 
+    def test_call_connection_lost_shutdown_reset(self):
+        # gh-160429: a ConnectionResetError from shutdown() must not skip
+        # closing the socket and detaching the transport from its server.
+        server = mock.Mock()
+        tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol,
+                                      server=server)
+        self.sock.shutdown.side_effect = ConnectionResetError
+        tr._call_connection_lost(None)
+        self.protocol.connection_lost.assert_called_once_with(None)
+        self.sock.close.assert_called_once_with()
+        server._detach.assert_called_once_with(tr)
+        self.assertIsNone(tr._sock)
+        self.assertTrue(tr._called_connection_lost)
+
     def test_write_eof(self):
         tr = self.socket_transport()
         self.assertTrue(tr.can_write_eof())
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-29-21-43-53.gh-issue-160429.eWapMs.rst 
b/Misc/NEWS.d/next/Library/2026-10-29-21-43-53.gh-issue-160429.eWapMs.rst
new file mode 100644
index 00000000000000..cd8bebd8c09acb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-29-21-43-53.gh-issue-160429.eWapMs.rst
@@ -0,0 +1,3 @@
+Fix :class:`asyncio.ProactorEventLoop` socket transports leaving the socket
+open, and :meth:`asyncio.Server.wait_closed` hanging, when the peer resets
+the connection while the transport is being closed.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to