Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-91227: Ignore ERROR_PORT_UNREACHABLE #32011

Merged
merged 19 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Lib/asyncio/windows_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,9 @@ def finish_socket_func(trans, key, ov):
try:
return ov.getresult()
except OSError as exc:
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
return b'', None
elif exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
_overlapped.ERROR_OPERATION_ABORTED):
raise ConnectionResetError(*exc.args)
else:
Expand Down Expand Up @@ -515,7 +517,9 @@ def finish_recv(trans, key, ov):
try:
return ov.getresult()
except OSError as exc:
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
return b'', None
esoma marked this conversation as resolved.
Show resolved Hide resolved
elif exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
_overlapped.ERROR_OPERATION_ABORTED):
raise ConnectionResetError(*exc.args)
else:
Expand Down
71 changes: 71 additions & 0 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,77 @@ def test_create_datagram_endpoint_sock(self):
tr.close()
self.loop.run_until_complete(pr.done)

def test_datagram_send_to_non_listening_address(self):
# see:
# https://github.com/python/cpython/issues/91227
# https://github.com/python/cpython/issues/88906
# https://bugs.python.org/issue47071
# https://bugs.python.org/issue44743
# The Proactor event loop would fail to receive datagram messages after
# sending a message to an address that wasn't listening.
loop = self.loop

class Protocol(asyncio.DatagramProtocol):

_received_datagram = None

def datagram_received(self, data, addr):
self._received_datagram.set_result(data)

async def wait_for_datagram_received(self):
self._received_datagram = loop.create_future()
result = await asyncio.wait_for(self._received_datagram, 10)
self._received_datagram = None
return result

def create_socket():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setblocking(False)
sock.bind(('127.0.0.1', 0))
return sock

socket_1 = create_socket()
transport_1, protocol_1 = loop.run_until_complete(
loop.create_datagram_endpoint(Protocol, sock=socket_1)
)
addr_1 = socket_1.getsockname()

socket_2 = create_socket()
transport_2, protocol_2 = loop.run_until_complete(
loop.create_datagram_endpoint(Protocol, sock=socket_2)
)
addr_2 = socket_2.getsockname()

# creating and immediately closing this to try to get an address that
# is not listening
socket_3 = create_socket()
transport_3, protocol_3 = loop.run_until_complete(
loop.create_datagram_endpoint(Protocol, sock=socket_3)
)
addr_3 = socket_3.getsockname()
transport_3.abort()

transport_1.sendto(b'a', addr=addr_2)
assert loop.run_until_complete(
protocol_2.wait_for_datagram_received()
) == b'a'

transport_2.sendto(b'b', addr=addr_1)
assert loop.run_until_complete(
protocol_1.wait_for_datagram_received()
) == b'b'

# this should send to an address that isn't listening
transport_1.sendto(b'c', addr=addr_3)
loop.run_until_complete(asyncio.sleep(0))

# transport 1 should still be able to receive messages after sending to
# an address that wasn't listening
transport_2.sendto(b'd', addr=addr_1)
assert loop.run_until_complete(
protocol_1.wait_for_datagram_received()
) == b'd'

def test_internal_fds(self):
loop = self.create_event_loop()
if not isinstance(loop, selector_events.BaseSelectorEventLoop):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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.
1 change: 1 addition & 0 deletions Modules/overlapped.c
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,7 @@ overlapped_exec(PyObject *module)
WINAPI_CONSTANT(F_DWORD, ERROR_OPERATION_ABORTED);
WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT);
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_BUSY);
WINAPI_CONSTANT(F_DWORD, ERROR_PORT_UNREACHABLE);
WINAPI_CONSTANT(F_DWORD, INFINITE);
WINAPI_CONSTANT(F_HANDLE, INVALID_HANDLE_VALUE);
WINAPI_CONSTANT(F_HANDLE, NULL);
Expand Down
Loading