@@ -555,12 +555,93 @@ class SelectEventLoopTests(BaseSockTestsMixin,
555555 def create_event_loop (self ):
556556 return asyncio .SelectorEventLoop ()
557557
558+
558559 class ProactorEventLoopTests (BaseSockTestsMixin ,
559560 test_utils .TestCase ):
560561
561562 def create_event_loop (self ):
562563 return asyncio .ProactorEventLoop ()
563564
565+
566+ async def _basetest_datagram_send_to_non_listening_address (self ,
567+ recvfrom ):
568+ # see:
569+ # https://github.com/python/cpython/issues/91227
570+ # https://github.com/python/cpython/issues/88906
571+ # https://bugs.python.org/issue47071
572+ # https://bugs.python.org/issue44743
573+ # The Proactor event loop would fail to receive datagram messages
574+ # after sending a message to an address that wasn't listening.
575+
576+ def create_socket ():
577+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
578+ sock .setblocking (False )
579+ sock .bind (('127.0.0.1' , 0 ))
580+ return sock
581+
582+ socket_1 = create_socket ()
583+ addr_1 = socket_1 .getsockname ()
584+
585+ socket_2 = create_socket ()
586+ addr_2 = socket_2 .getsockname ()
587+
588+ # creating and immediately closing this to try to get an address
589+ # that is not listening
590+ socket_3 = create_socket ()
591+ addr_3 = socket_3 .getsockname ()
592+ socket_3 .shutdown (socket .SHUT_RDWR )
593+ socket_3 .close ()
594+
595+ socket_1_recv_task = self .loop .create_task (recvfrom (socket_1 ))
596+ socket_2_recv_task = self .loop .create_task (recvfrom (socket_2 ))
597+ await asyncio .sleep (0 )
598+
599+ await self .loop .sock_sendto (socket_1 , b'a' , addr_2 )
600+ self .assertEqual (await socket_2_recv_task , b'a' )
601+
602+ await self .loop .sock_sendto (socket_2 , b'b' , addr_1 )
603+ self .assertEqual (await socket_1_recv_task , b'b' )
604+ socket_1_recv_task = self .loop .create_task (recvfrom (socket_1 ))
605+ await asyncio .sleep (0 )
606+
607+ # this should send to an address that isn't listening
608+ await self .loop .sock_sendto (socket_1 , b'c' , addr_3 )
609+ self .assertEqual (await socket_1_recv_task , b'' )
610+ socket_1_recv_task = self .loop .create_task (recvfrom (socket_1 ))
611+ await asyncio .sleep (0 )
612+
613+ # socket 1 should still be able to receive messages after sending
614+ # to an address that wasn't listening
615+ socket_2 .sendto (b'd' , addr_1 )
616+ self .assertEqual (await socket_1_recv_task , b'd' )
617+
618+ socket_1 .shutdown (socket .SHUT_RDWR )
619+ socket_1 .close ()
620+ socket_2 .shutdown (socket .SHUT_RDWR )
621+ socket_2 .close ()
622+
623+
624+ def test_datagram_send_to_non_listening_address_recvfrom (self ):
625+ async def recvfrom (socket ):
626+ data , _ = await self .loop .sock_recvfrom (socket , 4096 )
627+ return data
628+
629+ self .loop .run_until_complete (
630+ self ._basetest_datagram_send_to_non_listening_address (
631+ recvfrom ))
632+
633+
634+ def test_datagram_send_to_non_listening_address_recvfrom_into (self ):
635+ async def recvfrom_into (socket ):
636+ buf = bytearray (4096 )
637+ length , _ = await self .loop .sock_recvfrom_into (socket , buf ,
638+ 4096 )
639+ return buf [:length ]
640+
641+ self .loop .run_until_complete (
642+ self ._basetest_datagram_send_to_non_listening_address (
643+ recvfrom_into ))
644+
564645else :
565646 import selectors
566647
0 commit comments