Skip to content

Commit 20bd1cc

Browse files
committed
Fix _SelectorSocketTransport.writelines to be robust to connection loss
Closes GH-136234
1 parent 180b3eb commit 20bd1cc

File tree

3 files changed

+29
-3
lines changed

3 files changed

+29
-3
lines changed

Lib/asyncio/selector_events.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,11 @@ def _read_ready__on_eof(self):
10481048
else:
10491049
self.close()
10501050

1051+
def _write_after_conn_lost(self):
1052+
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
1053+
logger.warning('socket.send() raised exception.')
1054+
self._conn_lost += 1
1055+
10511056
def write(self, data):
10521057
if not isinstance(data, (bytes, bytearray, memoryview)):
10531058
raise TypeError(f'data argument must be a bytes-like object, '
@@ -1060,9 +1065,7 @@ def write(self, data):
10601065
return
10611066

10621067
if self._conn_lost:
1063-
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
1064-
logger.warning('socket.send() raised exception.')
1065-
self._conn_lost += 1
1068+
self._write_after_conn_lost()
10661069
return
10671070

10681071
if not self._buffer:
@@ -1174,6 +1177,11 @@ def writelines(self, list_of_data):
11741177
raise RuntimeError('unable to writelines; sendfile is in progress')
11751178
if not list_of_data:
11761179
return
1180+
1181+
if self._conn_lost:
1182+
self._write_after_conn_lost()
1183+
return
1184+
11771185
self._buffer.extend([memoryview(data) for data in list_of_data])
11781186
self._write_ready()
11791187
# If the entire buffer couldn't be written, register a write handler

Lib/test/test_asyncio/test_selector_events.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,22 @@ def test_writelines_pauses_protocol(self):
854854
self.assertTrue(self.sock.send.called)
855855
self.assertTrue(self.loop.writers)
856856

857+
def test_writelines_after_connection_lost(self):
858+
# GH-136234
859+
transport = self.socket_transport()
860+
self.sock.send = mock.Mock()
861+
self.sock.send.side_effect = ConnectionResetError
862+
transport.write(b'data1') # Will fail immediately, causing connection lost
863+
864+
transport.writelines([b'data2'])
865+
self.assertFalse(transport._buffer)
866+
self.assertFalse(self.loop.writers)
867+
868+
test_utils.run_briefly(self.loop) # Allow _call_connection_lost to run
869+
transport.writelines([b'data2'])
870+
self.assertFalse(transport._buffer)
871+
self.assertFalse(self.loop.writers)
872+
857873
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
858874
def test_write_sendmsg_full(self):
859875
data = memoryview(b'data')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`asyncio.WriteTransport.writelines` to be robust to connection
2+
failure, by using the same behavior as :meth:`~asyncio.WriteTransport.write`.

0 commit comments

Comments
 (0)