From 060eef27c39e78acdbd0fadcd5f49b2d1a26ab73 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:57:57 +0200 Subject: [PATCH] [3.13] gh-136234: Fix `SelectorSocketTransport.writelines` to be robust to connection loss (GH-136743) (GH-138702) (#139710) [3.14] gh-136234: Fix `SelectorSocketTransport.writelines` to be robust to connection loss (GH-136743) (GH-138702) (cherry picked from commit 5cd6cfe4cf6770b9424bb23f8c7c661f7a3db7b0) Co-authored-by: Kumar Aditya --- Lib/asyncio/selector_events.py | 7 +++++++ Lib/test/test_asyncio/test_selector_events.py | 16 ++++++++++++++++ ...025-07-17-16-12-23.gh-issue-136234.VmTxtj.rst | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-17-16-12-23.gh-issue-136234.VmTxtj.rst diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index df5b1e49240f..8701467d4132 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1170,6 +1170,13 @@ class _SelectorSocketTransport(_SelectorTransport): raise RuntimeError('unable to writelines; sendfile is in progress') if not list_of_data: return + + if self._conn_lost: + if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES: + logger.warning('socket.send() raised exception.') + self._conn_lost += 1 + return + self._buffer.extend([memoryview(data) for data in list_of_data]) self._write_ready() # If the entire buffer couldn't be written, register a write handler diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index 1e7761ee38d7..bfb347e4a8cf 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -817,6 +817,22 @@ class SelectorSocketTransportTests(test_utils.TestCase): self.assertTrue(self.sock.send.called) self.assertTrue(self.loop.writers) + def test_writelines_after_connection_lost(self): + # GH-136234 + transport = self.socket_transport() + self.sock.send = mock.Mock() + self.sock.send.side_effect = ConnectionResetError + transport.write(b'data1') # Will fail immediately, causing connection lost + + transport.writelines([b'data2']) + self.assertFalse(transport._buffer) + self.assertFalse(self.loop.writers) + + test_utils.run_briefly(self.loop) # Allow _call_connection_lost to run + transport.writelines([b'data2']) + self.assertFalse(transport._buffer) + self.assertFalse(self.loop.writers) + @unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg') def test_write_sendmsg_full(self): data = memoryview(b'data') diff --git a/Misc/NEWS.d/next/Library/2025-07-17-16-12-23.gh-issue-136234.VmTxtj.rst b/Misc/NEWS.d/next/Library/2025-07-17-16-12-23.gh-issue-136234.VmTxtj.rst new file mode 100644 index 000000000000..044a601c9170 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-17-16-12-23.gh-issue-136234.VmTxtj.rst @@ -0,0 +1,2 @@ +Fix :meth:`asyncio.WriteTransport.writelines` to be robust to connection +failure, by using the same behavior as :meth:`~asyncio.WriteTransport.write`. -- 2.47.3