From 45149d86f11ec7ab3acf2a983bfda07c18b87a37 Mon Sep 17 00:00:00 2001 From: Zap777 <124182405+devanshc777@users.noreply.github.com> Date: Thu, 5 Nov 2026 14:09:31 +0530 Subject: [PATCH] gh-160429: Ignore ConnectionResetError from shutdown() when closing proactor sockets (GH-160909) * gh-160429: Ignore ConnectionResetError from shutdown() when closing proactor sockets * Update Lib/asyncio/proactor_events.py --------- (cherry picked from commit 7b06d6f80df2729a68f78d2a346c043d10f82fd6) Co-authored-by: Zap777 <124182405+devanshc777@users.noreply.github.com> Co-authored-by: Andrew Svetlov --- Lib/asyncio/proactor_events.py | 7 ++++++- Lib/test/test_asyncio/test_proactor_events.py | 14 ++++++++++++++ .../2026-10-29-21-43-53.gh-issue-160429.eWapMs.rst | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-29-21-43-53.gh-issue-160429.eWapMs.rst diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 68945554647adb..e93b3d5bf66052 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 f23f59ac0b28f4..f0184615a65af3 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.