Skip to content

WinError 10022 for create_datagram_endpoint with local_addr=None. #119711

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

Open
lschoe opened this issue May 29, 2024 · 8 comments
Open

WinError 10022 for create_datagram_endpoint with local_addr=None. #119711

lschoe opened this issue May 29, 2024 · 8 comments
Labels
docs Documentation in the Doc dir OS-windows topic-asyncio

Comments

@lschoe
Copy link

lschoe commented May 29, 2024

Bug report

Bug description:

A problem occurs with the Windows proactor event loop when creating a datagram endpoint with local_addr=None. The problem does not occur with the selector event loop (either on Windows or Linux).

import socket
import asyncio

class MyDatagramProto(asyncio.DatagramProtocol):

   def error_received(self, exc):
       raise exc


asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
loop = asyncio.new_event_loop()
coro = loop.create_datagram_endpoint(MyDatagramProto, local_addr=None, family=socket.AF_INET)
loop.run_until_complete(coro)

print('No problem with selector loop.')
print()

asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
loop = asyncio.new_event_loop()
coro = loop.create_datagram_endpoint(MyDatagramProto, local_addr=None, family=socket.AF_INET)
loop.run_until_complete(coro)

print()
print('We got error 10022 with proactor loop.')

gives as output

No problem with selector loop.

Exception in callback _ProactorDatagramTransport._loop_reading()
handle: <Handle _ProactorDatagramTransport._loop_reading()>
Traceback (most recent call last):
  File "C:\Users\Berry\AppData\Local\Programs\Python\Python313\Lib\asyncio\events.py", line 89, in _run
    self._context.run(self._callback, *self._args)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Berry\AppData\Local\Programs\Python\Python313\Lib\asyncio\proactor_events.py", line 577, in _loop_reading
    self._protocol.error_received(exc)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "C:\Users\Berry\Desktop\scratch\bugasynciolocaddrNone.py", line 7, in error_received
    raise exc
  File "C:\Users\Berry\AppData\Local\Programs\Python\Python313\Lib\asyncio\proactor_events.py", line 574, in _loop_reading
    self._read_fut = self._loop._proactor.recvfrom(self._sock,
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
                                                   self.max_size)
                                                   ^^^^^^^^^^^^^^
  File "C:\Users\Berry\AppData\Local\Programs\Python\Python313\Lib\asyncio\windows_events.py", line 513, in recvfrom
    ov.WSARecvFrom(conn.fileno(), nbytes, flags)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OSError: [WinError 10022] An invalid argument was supplied

We got error 10022 with proactor loop.

The problem happens because the socket conn is not bound when WSARecvFrom is called. The bind() call is skipped because the local address was None, see:

if local_addr:
sock.bind(local_address)

The code above is a stripped down version of the following uvloop test case:
https://github.com/MagicStack/uvloop/blob/6c770dc3fbdd281d15c2ad46588c139696f9269c/tests/test_udp.py#L141-L160
This test passes on Linux both with the uvloop and with the asyncio (selector) loop.

CPython versions tested on:

3.8, 3.9, 3.10, 3.11, 3.12, 3.13

Operating systems tested on:

Windows

@lschoe lschoe added the type-bug An unexpected behavior, bug, or error label May 29, 2024
@github-project-automation github-project-automation bot moved this to Todo in asyncio May 29, 2024
@gvanrossum
Copy link
Member

Maybe @zooba had any insight to share here?

@zooba
Copy link
Member

zooba commented May 29, 2024

No insights in particular.

The bind() call is skipped because the local address was None

What should be happening here with a local address of None? Is it meant to pick up a default and bind to that?

@lschoe
Copy link
Author

lschoe commented May 30, 2024

Well, that's indeed the question, if something should be changed (or not) in this case.

The uvloop test case mentioned above is a rather extreme case, so one option (i) is to ignore this problem (asyncio does not have a test case with local_addr=None at all). Also, the uvloop test case does not actually use the datagram endpoint so created, but immediately closes it (which is why the selector loop and uvloop still pass this test, whether the socket is bound or not is irrelevant).

Indeed, another option (ii) is to assign a default address if local_addr is None and bind to it. The advantage could be that people don't have to worry about the details of the default address, e.g., how it varies depending on e.g. AF_INET vs AF_INET6.

Or option (iii), local_addr is None is simply rejected, raising an exception. Not sure how much existing code would be affected by this, but running code with local_addr=None seems unlikely.

I'm not sure about people's preferences here (my preference is option (ii)). My report was to highlight this difference between the event loops, which is good to document I think, as ultimately we like these loops to be substitutes for one another.

@gvanrossum
Copy link
Member

Since local_addr=None is the default, most tests for create_datagram_endpoint() actually test that case.

It is clearly intended to have the semantics that the socket is not bound in this case. The UNIX stack has clear semantics for this. (Although I'm not sure what they are -- I'm sure UNIX references like the Stevens books spell it out. But presumably it lets the kernel decide what to do.)

If the Proactor event loop doesn't support this, we should just document that -- picking one of the possible local addresses and binding to it seems inconsistent, since the library has no way to know which address the caller meant, and the UNIX semantics are different.

@lschoe
Copy link
Author

lschoe commented May 30, 2024

Yeah, you're right, asyncio actually has quite a few tests that have local_addr=None as default. I missed those when searching for local_addr;) So, the uvloop test case is not special in this sense.

Nevertheless, this problem does not arise in practice, so just documenting that the proactor event loop does not support this sounds fine.

@Eclips4 Eclips4 added docs Documentation in the Doc dir and removed type-bug An unexpected behavior, bug, or error labels May 30, 2024
@gvanrossum
Copy link
Member

Cool. Do you feel confident contributing a small docs PR?

@lschoe
Copy link
Author

lschoe commented May 31, 2024

Happy to consider that. Not sure about the audience for such a note though and where it would fit. The doc for coroutine loop.create_datagram_endpoint() seems too high-level as the scenario for the above problem is quite complicated (e.g., whether remote_addr is None also matters), so some more analysis is needed to find a suitable high-level wording here. Alternatively, aiming for a note in the source code might be appropriate as it can be kept more to the point. You see, I'm not overly confident here;) But will think about more.

@gvanrossum
Copy link
Member

I’d add a note to the public API docs for that function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir OS-windows topic-asyncio
Projects
Status: Todo
Development

No branches or pull requests

4 participants