dabeaz / curio

Good Curio!
Other
4.01k stars 240 forks source link

Curio does not correctly handle the windows OSError that is raised when select is called with empty lists since python 3.10.5 #353

Closed stharding closed 1 year ago

stharding commented 2 years ago

Steps to reproduce:

  1. On windows, install python 3.10.5
  2. Run the following:
In [1]: import curio

In [2]: async def f():
   ...:     print("a")
   ...:     await curio.sleep(1)
   ...:     print("b")
   ...:

In [3]: curio.run(f)

This results in the following traceback:

---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
File C:\path\to\some\venv\lib\site-packages\curio\kernel.py:822, in run(corofunc, with_monitor, selector, debug, activations, *args, **kernel_extra)
    821 with kernel:
--> 822     return kernel.run(corofunc, *args)

File C:\path\to\some\venv\lib\site-packages\curio\kernel.py:146, in Kernel.run(self, corofunc, shutdown, *args)
    145 if coro or not shutdown:
--> 146     task = self._runner(coro)
    147     if task:

File C:\path\to\some\venv\lib\site-packages\curio\kernel.py:641, in Kernel._make_kernel_runtime.<locals>.kernel_run(***failed resolving arguments***)
    640 try:
--> 641     events = selector_select(timeout)
    642 except OSError as e:
    643     # If there is nothing to select, windows throws an
    644     # OSError, so just set events to an empty list.

File ~\AppData\Local\Programs\Python\Python310\lib\selectors.py:325, in SelectSelector.select(self, timeout)
    324 try:
--> 325     r, w, _ = self._select(self._readers, self._writers, [], timeout)
    326 except InterruptedError:

File ~\AppData\Local\Programs\Python\Python310\lib\selectors.py:315, in SelectSelector._select(self, r, w, _, timeout)
    314 print(r, w, _, timeout)
--> 315 r, w, x = select.select(r, w, w, timeout)
    316 return r, w + x, []

OSError: [WinError 10022] An invalid argument was supplied

During handling of the above exception, another exception occurred:

OSError                                   Traceback (most recent call last)

The reason for this is the handling of OSError in kernel.py:645

The problem is that in 3.10.5, the error codes that get set changed. From the changelog:

bpo-46775: Some Windows system error codes(>= 10000) are now mapped into the correct errno and may now raise a subclass of OSError. Patch by Dong-hee Na.

This is the linked issue in cpython: https://github.com/python/cpython/issues/90931

In this case, checking e.errno against errno.WSAEINVAL is no longer sufficent.

I propose changing this:

            try:
                events = selector_select(timeout)
            except OSError as e:
                # If there is nothing to select, windows throws an
                # OSError, so just set events to an empty list.
                if e.errno != getattr(errno, 'WSAEINVAL', None):
                    raise
                events = []

to:

            try:
                events = selector_select(timeout)
            except OSError as e:
                # If there is nothing to select, windows throws an
                # OSError, so just set events to an empty list.
                wsaeinval = getattr(errno, 'WSAEINVAL', None)
                einval = getattr(errno, 'EINVAL', None)
                if e.errno not in (wsaeinval, einval):
                    raise
                events = []

I've made this in my fork and it corrected the issue. I am happy to to submit a PR with this change.

Cheers

dabeaz commented 2 years ago

More than happy to accept a PR.