Skip to content

Commit 601e695

Browse files
authored
[3.14] gh-155888: Fix asyncio writelines() hanging on an empty last chunk (#155913)
1 parent 04350d9 commit 601e695

3 files changed

Lines changed: 20 additions & 1 deletion

File tree

Lib/asyncio/selector_events.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,11 @@ def writelines(self, list_of_data):
11831183
self._conn_lost += 1
11841184
return
11851185

1186-
self._buffer.extend([memoryview(data) for data in list_of_data])
1186+
# gh-155888: an empty chunk can never be drained, so never buffer it
1187+
self._buffer.extend(
1188+
[memoryview(data) for data in list_of_data if data])
1189+
if not self._buffer:
1190+
return
11871191
self._write_ready()
11881192
# If the entire buffer couldn't be written, register a write handler
11891193
if self._buffer:

Lib/test/test_asyncio/test_events.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,19 @@ def writer(data):
560560
r.close()
561561
self.assertEqual(read, data)
562562

563+
def test_writelines_empty_chunk(self):
564+
# gh-155888: an empty chunk can never be drained, so never buffer it
565+
rsock, wsock = socket.socketpair()
566+
self.addCleanup(rsock.close)
567+
568+
async def main():
569+
reader, writer = await asyncio.open_connection(sock=wsock)
570+
writer.writelines([b'data', b''])
571+
writer.close()
572+
await asyncio.wait_for(writer.wait_closed(), support.SHORT_TIMEOUT)
573+
574+
self.loop.run_until_complete(main())
575+
563576
@unittest.skipUnless(hasattr(signal, 'SIGKILL'), 'No SIGKILL')
564577
def test_add_signal_handler(self):
565578
caught = 0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`asyncio.WriteTransport.writelines` hanging the transport when the
2+
last data chunk is empty.

0 commit comments

Comments
 (0)