Skip to content

Commit 113440a

Browse files
committed
Fix incomplete writes after close in asyncio._SelectorSocketTransport
Change introduced in gh-106503 cause the transport to miss writes scheduled after it is closed. Signed-off-by: Vojtěch Boček <vbocek@gmail.com>
1 parent be8ae08 commit 113440a

File tree

3 files changed

+41
-1
lines changed

3 files changed

+41
-1
lines changed

Lib/asyncio/selector_events.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,7 @@ def can_write_eof(self):
11821182

11831183
def _call_connection_lost(self, exc):
11841184
super()._call_connection_lost(exc)
1185+
self._write_ready = None
11851186
if self._empty_waiter is not None:
11861187
self._empty_waiter.set_exception(
11871188
ConnectionError("Connection is closed by peer"))
@@ -1199,7 +1200,6 @@ def _reset_empty_waiter(self):
11991200

12001201
def close(self):
12011202
self._read_ready_cb = None
1202-
self._write_ready = None
12031203
super().close()
12041204

12051205

Lib/test/test_asyncio/test_selector_events.py

+39
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,45 @@ def test_transport_close_remove_writer(self, m_log):
10261026
transport.close()
10271027
remove_writer.assert_called_with(self.sock_fd)
10281028

1029+
def test_write_buffer_after_close(self):
1030+
# If the transport is closed while:
1031+
# * Transport write buffer is not empty
1032+
# * Transport is paused
1033+
# * Protocol has data in its buffer, like SSLProtocol in self._outgoing
1034+
# The data is still written out.
1035+
1036+
data = memoryview(b'data')
1037+
self.sock.send.return_value = 2
1038+
self.sock.send.fileno.return_value = 7
1039+
1040+
def _resume_writing():
1041+
transport.write(b"data")
1042+
self.protocol.resume_writing.side_effect = None
1043+
1044+
self.protocol.resume_writing.side_effect = _resume_writing
1045+
1046+
transport = self.socket_transport()
1047+
transport._high_water = 1
1048+
1049+
transport.write(data)
1050+
1051+
self.assertTrue(transport._protocol_paused)
1052+
self.assertTrue(self.sock.send.called)
1053+
self.loop.assert_writer(7, transport._write_ready)
1054+
1055+
transport.close()
1056+
1057+
# not called, we still have data in write buffer
1058+
self.assertFalse(self.protocol.connection_lost.called)
1059+
1060+
self.loop.writers[7]._run()
1061+
# during this ^ run, the _resume_writing mock above was called and added more data
1062+
1063+
self.assertEqual(transport.get_write_buffer_size(), 2)
1064+
self.loop.writers[7]._run()
1065+
1066+
self.assertEqual(transport.get_write_buffer_size(), 0)
1067+
self.assertTrue(self.protocol.connection_lost.called)
10291068

10301069
class SelectorSocketTransportBufferedProtocolTests(test_utils.TestCase):
10311070

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ Stéphane Blondon
189189
Eric Blossom
190190
Sergey Bobrov
191191
Finn Bock
192+
Vojtěch Boček
192193
Paul Boddie
193194
Matthew Boedicker
194195
Robin Boerdijk

0 commit comments

Comments
 (0)