@@ -1454,6 +1454,60 @@ def create_socket():
14541454 transport_1 .close ()
14551455 transport_2 .close ()
14561456
1457+ def test_datagram_recvfrom_connection_reset_recovers (self ):
1458+ # gh-127057: a UDP socket that sent a datagram to an address that
1459+ # wasn't listening can raise ConnectionResetError on a later
1460+ # receive. The transport must keep working afterwards.
1461+ loop = self .loop
1462+
1463+ class Protocol (asyncio .DatagramProtocol ):
1464+ def connection_made (self , transport ):
1465+ self .transport = transport
1466+ self .errors = []
1467+ self .received = []
1468+ self .datagram_received_event = loop .create_future ()
1469+
1470+ def error_received (self , exc ):
1471+ self .errors .append (exc )
1472+
1473+ def datagram_received (self , data , addr ):
1474+ self .received .append (data )
1475+ if not self .datagram_received_event .done ():
1476+ self .datagram_received_event .set_result (None )
1477+
1478+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
1479+ sock .setblocking (False )
1480+ sock .bind (('127.0.0.1' , 0 ))
1481+ addr = sock .getsockname ()
1482+
1483+ # Bind and immediately close a second socket to get an address
1484+ # that is guaranteed not to be listening.
1485+ closed = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
1486+ closed .bind (('127.0.0.1' , 0 ))
1487+ closed_addr = closed .getsockname ()
1488+ closed .close ()
1489+
1490+ # Trigger the error before the socket is wrapped in a transport,
1491+ # so that the first read raises synchronously.
1492+ sock .sendto (b'x' , closed_addr )
1493+
1494+ transport , protocol = loop .run_until_complete (
1495+ loop .create_datagram_endpoint (Protocol , sock = sock ))
1496+
1497+ transport .sendto (b'ping' , addr )
1498+ loop .run_until_complete (asyncio .wait_for (
1499+ protocol .datagram_received_event , support .SHORT_TIMEOUT ))
1500+ self .assertEqual (protocol .received , [b'ping' ])
1501+
1502+ if sys .platform == 'win32' :
1503+ # Other platforms don't report ICMP errors on an
1504+ # unconnected UDP socket.
1505+ self .assertEqual (len (protocol .errors ), 1 )
1506+ self .assertIsInstance (protocol .errors [0 ], ConnectionResetError )
1507+
1508+ transport .close ()
1509+ test_utils .run_briefly (loop )
1510+
14571511 def test_internal_fds (self ):
14581512 loop = self .create_event_loop ()
14591513 if not isinstance (loop , selector_events .BaseSelectorEventLoop ):
0 commit comments