8000 [3.11] gh-91227: Ignore ERROR_PORT_UNREACHABLE in proactor recvfrom()… · python/cpython@6261322 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6261322

Browse files
[3.11] gh-91227: Ignore ERROR_PORT_UNREACHABLE in proactor recvfrom() (GH-32011) (GH-117210)
(cherry picked from commit f11d0d8) Co-authored-by: Erik Soma <stillusingirc@gmail.com>
1 parent a3a0ce1 commit 6261322

File tree

5 files changed

+165
-0
lines changed

5 files changed

+165
-0
lines changed

Lib/asyncio/windows_events.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ def finish_recv(trans, key, ov):
513513
try:
514514
return ov.getresult()
515515
except OSError as exc:
516+
# WSARecvFrom will report ERROR_PORT_UNREACHABLE when the same
517+
# socket is used to send to an address that is not listening.
518+
if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
519+
return b'', None
516520
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
517521
_overlapped.ERROR_OPERATION_ABORTED):
518522
raise ConnectionResetError(*exc.args)
@@ -533,6 +537,10 @@ def finish_recv(trans, key, ov):
533537
try:
534538
return ov.getresult()
535539
except OSError as exc:
540+
# WSARecvFrom will report ERROR_PORT_UNREACHABLE when the same
541+
# socket is used to send to an address that is not listening.
542+
if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
543+
return 0, None
536544
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
537545
_overlapped.ERROR_OPERATION_ABORTED):
538546
raise ConnectionResetError(*exc.args)

Lib/test/test_asyncio/test_events.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,80 @@ def test_create_datagram_endpoint_sock(self):
13521352
tr.close()
13531353
self.loop.run_until_complete(pr.done)
13541354

1355+
def test_datagram_send_to_non_listening_address(self):
1356+
# see:
1357+
# https://github.com/python/cpython/issues/91227
1358+
# https://github.com/python/cpython/issues/88906
1359+
# https://bugs.python.org/issue47071
1360+
# https://bugs.python.org/issue44743
1361+
# The Proactor event loop would fail to receive datagram messages after
1362+
# sending a message to an address that wasn't listening.
1363+
loop = self.loop
1364+
1365+
class Protocol(asyncio.DatagramProtocol):
1366+
1367+
_received_datagram = None
1368+
1369+
def datagram_received(self, data, addr):
1370+
self._received_datagram.set_result(data)
1371+
1372+
async def wait_for_datagram_received(self):
1373+
self._received_datagram = loop.create_future()
1374+
result = await asyncio.wait_for(self._received_datagram, 10)
1375+
self._received_datagram = None
1376+
return result
1377+
1378+
def create_socket():
1379+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1380+
sock.setblocking(False)
1381+
sock.bind(('127.0.0.1', 0))
1382+
return sock
1383+
1384+
socket_1 = create_socket()
1385+
transport_1, protocol_1 = loop.run_until_complete(
1386+
loop.create_datagram_endpoint(Protocol, sock=socket_1)
1387+
)
1388+
addr_1 = socket_1.getsockname()
1389+
1390+
socket_2 = create_socket()
1391+
transport_2, protocol_2 = loop.run_until_complete(
1392+
loop.create_datagram_endpoint(Protocol, sock=socket_2)
1393+
)
1394+
addr_2 = socket_2.getsockname()
1395+
1396+
# creating and immediately closing this to try to get an address that
1397+
# is not listening
1398+
socket_3 = create_socket()
1399+
transport_3, protocol_3 = loop.run_until_complete(
1400+
loop.create_datagram_endpoint(Protocol, sock=socket_3)
1401+
)
1402+
addr_3 = socket_3.getsockname()
1403+
transport_3.abort()
1404+
1405+
transport_1.sendto(b'a', addr=addr_2)
1406+
self.assertEqual(loop.run_until_complete(
1407+
protocol_2.wait_for_datagram_received()
1408+
), b'a')
1409+
1410+
transport_2.sendto(b'b', addr=addr_1)
1411+
self.assertEqual(loop.run_until_complete(
1412+
protocol_1.wait_for_datagram_received()
1413+
), b'b')
1414+
1415+
# this should send to an address that isn't listening
1416+
transport_1.sendto(b'c', addr=addr_3)
1417+
loop.run_until_complete(asyncio.sleep(0))
1418+
1419+
# transport 1 should still be able to receive messages after sending to
1420+
# an address that wasn't listening
1421+
transport_2.sendto(b'd', addr=addr_1)
1422+
self.assertEqual(loop.run_until_complete(
1423+
protocol_1.wait_for_datagram_received()
1424+
), b'd')
1425+
1426+
transport_1.close()
1427+
transport_2.close()
1428+
13551429
def test_internal_fds(self):
13561430
loop = self.create_event_loop()
13571431
if not isinstance(loop, selector_events.BaseSelectorEventLoop):

Lib/test/test_asyncio/test_sock_lowlevel.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
564645
else:
565646
import selectors
566647

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix the asyncio ProactorEventLoop implementation so that sending a datagram to an address that is not listening does not prevent receiving any more datagrams.

Modules/overlapped.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,6 +2092,7 @@ overlapped_exec(PyObject *module)
20922092
WINAPI_CONSTANT(F_DWORD, ERROR_OPERATION_ABORTED);
20932093
WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT);
20942094
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_BUSY);
2095+
WINAPI_CONSTANT(F_DWORD, ERROR_PORT_UNREACHABLE);
20952096
WINAPI_CONSTANT(F_DWORD, INFINITE);
20962097
WINAPI_CONSTANT(F_HANDLE, INVALID_HANDLE_VALUE);
20972098
WINAPI_CONSTANT(F_HANDLE, NULL);

0 commit comments

Comments
 (0)
0