@@ -1583,6 +1583,60 @@ def create_socket():
15831583 transport_1 .close ()
15841584 transport_2 .close ()
15851585
1586+ def test_datagram_recvfrom_connection_reset_recovers (self ):
1587+ # gh-127057: a UDP socket that sent a datagram to an address that
1588+ # wasn't listening can raise ConnectionResetError on a later
1589+ # receive. The transport must keep working afterwards.
1590+ loop = self .loop
1591+
1592+ class Protocol (asyncio .DatagramProtocol ):
1593+ def connection_made (self , transport ):
1594+ self .transport = transport
1595+ self .errors = []
1596+ self .received = []
1597+ self .datagram_received_event = loop .create_future ()
1598+
1599+ def error_received (self , exc ):
1600+ self .errors .append (exc )
1601+
1602+ def datagram_received (self , data , addr ):
1603+ self .received .append (data )
1604+ if not self .datagram_received_event .done ():
1605+ self .datagram_received_event .set_result (None )
1606+
1607+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
1608+ sock .setblocking (False )
1609+ sock .bind (('127.0.0.1' , 0 ))
1610+ addr = sock .getsockname ()
1611+
1612+ # Bind and immediately close a second socket to get an address
1613+ # that is guaranteed not to be listening.
1614+ closed = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
1615+ closed .bind (('127.0.0.1' , 0 ))
1616+ closed_addr = closed .getsockname ()
1617+ closed .close ()
1618+
1619+ # Trigger the error before the socket is wrapped in a transport,
1620+ # so that the first read raises synchronously.
1621+ sock .sendto (b'x' , closed_addr )
1622+
1623+ transport , protocol = loop .run_until_complete (
1624+ loop .create_datagram_endpoint (Protocol , sock = sock ))
1625+
1626+ transport .sendto (b'ping' , addr )
1627+ loop .run_until_complete (asyncio .wait_for (
1628+ protocol .datagram_received_event , support .SHORT_TIMEOUT ))
1629+ self .assertEqual (protocol .received , [b'ping' ])
1630+
1631+ if sys .platform == 'win32' :
1632+ # Other platforms don't report ICMP errors on an
1633+ # unconnected UDP socket.
1634+ self .assertEqual (len (protocol .errors ), 1 )
1635+ self .assertIsInstance (protocol .errors [0 ], ConnectionResetError )
1636+
1637+ transport .close ()
1638+ test_utils .run_briefly (loop )
1639+
15861640 def test_internal_fds (self ):
15871641 loop = self .create_event_loop ()
15881642 if not isinstance (loop , selector_events .BaseSelectorEventLoop ):
0 commit comments