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]
