Skip to content

Commit 93ac6f3

Browse files
miss-islingtonjsbronderkumaraditya303
authored
[3.14] gh-135444: fix asyncio.DatagramTransport.sendto to account for datagram header size when data cannot be sent (GH-135445) (#137245)
gh-135444: fix `asyncio.DatagramTransport.sendto` to account for datagram header size when data cannot be sent (GH-135445) (cherry picked from commit e3ea861) Co-authored-by: Justin Bronder <jsbronder@cold-front.org> Co-authored-by: Kumar Aditya <kumaraditya@python.org>
1 parent bfcd5f2 commit 93ac6f3

File tree

5 files changed

+53
-5
lines changed

5 files changed

+53
-5
lines changed

Lib/asyncio/proactor_events.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ def _pipe_closed(self, fut):
460460
class _ProactorDatagramTransport(_ProactorBasePipeTransport,
461461
transports.DatagramTransport):
462462
max_size = 256 * 1024
463+
_header_size = 8
464+
463465
def __init__(self, loop, sock, protocol, address=None,
464466
waiter=None, extra=None):
465467
self._address = address
@@ -499,7 +501,7 @@ def sendto(self, data, addr=None):
499501

500502
# Ensure that what we buffer is immutable.
501503
self._buffer.append((bytes(data), addr))
502-
self._buffer_size += len(data) + 8 # include header bytes
504+
self._buffer_size += len(data) + self._header_size
503505

504506
if self._write_fut is None:
505507
# No current write operations are active, kick one off
@@ -526,7 +528,7 @@ def _loop_writing(self, fut=None):
526528
return
527529

528530
data, addr = self._buffer.popleft()
529-
self._buffer_size -= len(data)
531+
self._buffer_size -= len(data) + self._header_size
530532
if self._address is not None:
531533
self._write_fut = self._loop._proactor.send(self._sock,
532534
data)

Lib/asyncio/selector_events.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ def close(self):
12191219
class _SelectorDatagramTransport(_SelectorTransport, transports.DatagramTransport):
12201220

12211221
_buffer_factory = collections.deque
1222+
_header_size = 8
12221223

12231224
def __init__(self, loop, sock, protocol, address=None,
12241225
waiter=None, extra=None):
@@ -1292,21 +1293,21 @@ def sendto(self, data, addr=None):
12921293

12931294
# Ensure that what we buffer is immutable.
12941295
self._buffer.append((bytes(data), addr))
1295-
self._buffer_size += len(data) + 8 # include header bytes
1296+
self._buffer_size += len(data) + self._header_size
12961297
self._maybe_pause_protocol()
12971298

12981299
def _sendto_ready(self):
12991300
while self._buffer:
13001301
data, addr = self._buffer.popleft()
1301-
self._buffer_size -= len(data)
1302+
self._buffer_size -= len(data) + self._header_size
13021303
try:
13031304
if self._extra['peername']:
13041305
self._sock.send(data)
13051306
else:
13061307
self._sock.sendto(data, addr)
13071308
except (BlockingIOError, InterruptedError):
13081309
self._buffer.appendleft((data, addr)) # Try again later.
1309-
self._buffer_size += len(data)
1310+
self._buffer_size += len(data) + self._header_size
13101311
break
13111312
except OSError as exc:
13121313
self._protocol.error_received(exc)

Lib/test/test_asyncio/test_proactor_events.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,8 @@ def test_sendto(self):
566566
self.assertTrue(self.proactor.sendto.called)
567567
self.proactor.sendto.assert_called_with(
568568
self.sock, data, addr=('0.0.0.0', 1234))
569+
self.assertFalse(transport._buffer)
570+
self.assertEqual(0, transport._buffer_size)
569571

570572
def test_sendto_bytearray(self):
571573
data = bytearray(b'data')

Lib/test/test_asyncio/test_selector_events.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,47 @@ def test_sendto_closing(self):
15131513
transport.sendto(b'data', (1,))
15141514
self.assertEqual(transport._conn_lost, 2)
15151515

1516+
def test_sendto_sendto_ready(self):
1517+
data = b'data'
1518+
1519+
# First queue up the buffer by having the socket blocked
1520+
self.sock.sendto.side_effect = BlockingIOError
1521+
transport = self.datagram_transport()
1522+
transport.sendto(data, ('0.0.0.0', 12345))
1523+
self.loop.assert_writer(7, transport._sendto_ready)
1524+
self.assertEqual(1, len(transport._buffer))
1525+
self.assertEqual(transport._buffer_size, len(data) + transport._header_size)
1526+
1527+
# Now let the socket send the buffer
1528+
self.sock.sendto.side_effect = None
1529+
transport._sendto_ready()
1530+
self.assertTrue(self.sock.sendto.called)
1531+
self.assertEqual(
1532+
self.sock.sendto.call_args[0], (data, ('0.0.0.0', 12345)))
1533+
self.assertFalse(self.loop.writers)
1534+
self.assertFalse(transport._buffer)
1535+
self.assertEqual(transport._buffer_size, 0)
1536+
1537+
def test_sendto_sendto_ready_blocked(self):
1538+
data = b'data'
1539+
1540+
# First queue up the buffer by having the socket blocked
1541+
self.sock.sendto.side_effect = BlockingIOError
1542+
transport = self.datagram_transport()
1543+
transport.sendto(data, ('0.0.0.0', 12345))
1544+
self.loop.assert_writer(7, transport._sendto_ready)
1545+
self.assertEqual(1, len(transport._buffer))
1546+
self.assertEqual(transport._buffer_size, len(data) + transport._header_size)
1547+
1548+
# Now try to send the buffer, it will be added to buffer again if it fails
1549+
transport._sendto_ready()
1550+
self.assertTrue(self.sock.sendto.called)
1551+
self.assertEqual(
1552+
self.sock.sendto.call_args[0], (data, ('0.0.0.0', 12345)))
1553+
self.assertTrue(self.loop.writers)
1554+
self.assertEqual(1, len(transport._buffer))
1555+
self.assertEqual(transport._buffer_size, len(data) + transport._header_size)
1556+
15161557
def test_sendto_ready(self):
15171558
data = b'data'
15181559
self.sock.sendto.return_value = len(data)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`asyncio.DatagramTransport.sendto` to account for datagram header size when
2+
data cannot be sent.

0 commit comments

Comments
 (0)