From 71b8fb814134226493c34a5ae8d604c599a401b0 Mon Sep 17 00:00:00 2001 From: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com> Date: Sun, 16 Aug 2026 12:42:55 +0300 Subject: [PATCH] gh-155888: Fix asyncio writelines() hanging on an empty chunk (GH-155889) (cherry picked from commit b2c299373d86b670f50d930564da22550af55308) Co-authored-by: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com> --- Lib/asyncio/selector_events.py | 5 +++++ Lib/test/test_asyncio/test_events.py | 13 +++++++++++++ .../2026-08-16-11-38-17.gh-issue-155888.pO-nAp.rst | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-16-11-38-17.gh-issue-155888.pO-nAp.rst diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index b6ea2fc0fe045ac..54e9564aa1f2b3e 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1197,8 +1197,13 @@ def writelines(self, list_of_data): return for data in list_of_data: + # gh-155888: an empty chunk can never be drained, so never buffer it + if not data: + continue self._buffer.append(memoryview(data)) self._buffer_size += len(data) + if not self._buffer: + return self._write_ready() # If the entire buffer couldn't be written, register a write handler if self._buffer: diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 888e8b4d11b978a..1ee39b0fac3ad62 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -560,6 +560,19 @@ def writer(data): r.close() self.assertEqual(read, data) + def test_writelines_empty_chunk(self): + # gh-155888: an empty chunk can never be drained, so never buffer it + rsock, wsock = socket.socketpair() + self.addCleanup(rsock.close) + + async def main(): + reader, writer = await asyncio.open_connection(sock=wsock) + writer.writelines([b'data', b'']) + writer.close() + await asyncio.wait_for(writer.wait_closed(), support.SHORT_TIMEOUT) + + self.loop.run_until_complete(main()) + @unittest.skipUnless(hasattr(signal, 'SIGKILL'), 'No SIGKILL') def test_add_signal_handler(self): caught = 0 diff --git a/Misc/NEWS.d/next/Library/2026-08-16-11-38-17.gh-issue-155888.pO-nAp.rst b/Misc/NEWS.d/next/Library/2026-08-16-11-38-17.gh-issue-155888.pO-nAp.rst new file mode 100644 index 000000000000000..df3db71b912e1c2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-16-11-38-17.gh-issue-155888.pO-nAp.rst @@ -0,0 +1,2 @@ +Fix :meth:`asyncio.WriteTransport.writelines` hanging the transport when the +last data chunk is empty.