[3.14] gh-155888: Fix asyncio writelines() hanging on an empty last chunk (#155913)

kumaraditya303 <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/601e69562e8c5b290922671ad8fc53c59e54ca47
commit: 601e69562e8c5b290922671ad8fc53c59e54ca47
branch: 3.14
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-17T10:30:35+05:30
summary:

[3.14] gh-155888: Fix asyncio writelines() hanging on an empty last chunk (#155913)

files:
A Misc/NEWS.d/next/Library/2026-08-16-11-38-17.gh-issue-155888.pO-nAp.rst
M Lib/asyncio/selector_events.py
M Lib/test/test_asyncio/test_events.py

diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index 0c96e81cce0bd5a..3aac81215dc0aea 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -1183,7 +1183,11 @@ def writelines(self, list_of_data):
             self._conn_lost += 1
             return
 
-        self._buffer.extend([memoryview(data) for data in list_of_data])
+        # gh-155888: an empty chunk can never be drained, so never buffer it
+        self._buffer.extend(
+            [memoryview(data) for data in list_of_data if 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.

_______________________________________________
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.