diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 21abff88b5bf401..740b4e9ddff225d 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -164,7 +164,11 @@ 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 ConnectionResetError: + # 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 d25c5fa47d1dc1d..6fb7e4d2d6288b5 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 000000000000000..cd8bebd8c09acb5 --- /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.