classification
Title: Consider making Windows select.select interruptible using WSAEventSelect & WSAWaitForMultipleEvents
Type: enhancement Stage:
Components: Extension Modules, Windows Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: giampaolo.rodola, ondrej.kutal, paul.moore, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2018-10-08 14:32 by ondrej.kutal, last changed 2018-10-09 16:13 by giampaolo.rodola.

Messages (1)
msg327354 - (view) Author: ondrej.kutal (ondrej.kutal) Date: 2018-10-08 14:32
At the moment, socket select.select() function is not interruptable on Windows OS (in main thread). Following code cannot be interrupted (for example by CTRL+C):

import select, socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(False)
s.bind(('0.0.0.0', 6666))
s.listen(100)

select.select([s], [], [], None)
s.close()

However this can be achieved by replacing select() calls with use of Windows native APIs WSAEventSelect and WSAWaitForMultipleEvents (see for example https://stackoverflow.com/questions/10353017). I have tried a quick prototype in selectmodule.c, replacing

Py_BEGIN_ALLOW_THREADS
errno = 0;
n = select(max, &ifdset, &ofdset, &efdset, tvp);
Py_END_ALLOW_THREADS

with 

#ifndef MS_WINDOWS
        Py_BEGIN_ALLOW_THREADS
        errno = 0;
        n = select(max, &ifdset, &ofdset, &efdset, tvp);
        Py_END_ALLOW_THREADS
#else

        if (!_PyOS_IsMainThread()) {
            Py_BEGIN_ALLOW_THREADS
            errno = 0;
            n = select(max, &ifdset, &ofdset, &efdset, tvp);
            Py_END_ALLOW_THREADS
        }
        else
        {
            // quick prototype, only for read sockets
            WSAEVENT events[50];
            for (u_int i = 0; i < ifdset.fd_count; ++i)
            {
                events[i+1] = WSACreateEvent();
                WSAEventSelect(ifdset.fd_array[i], events[i+1], FD_ACCEPT | FD_READ); 
            }

            /* putting interrupt event as a first one in the list
            */
            events[0] = _PyOS_SigintEvent();
            ResetEvent(events[0]);

            Py_BEGIN_ALLOW_THREADS
            errno = 0;
            n = WSAWaitForMultipleEvents(ifdset.fd_count, events, FALSE, tvp ? (DWORD)_PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING) : WSA_INFINITE, FALSE);
            Py_END_ALLOW_THREADS

            if (n == 0) 
                errno = EINTR;
            else if (n == WSA_WAIT_FAILED)
                n = SOCKET_ERROR;
            else
                n = 1; /* prototype implementation, acting like just 1 socket is ready,
                       for actual number it will be probably necessary to query WSAWaitForMultipleEvents 
                       multiple times since it otherwise returns only index of first ready event...
                       */
        }
#endif

and then I was able to interrupt the script above. I noticed slight performance loss when having timeout 0, repeating select 1000 times it took ~2000 us, wile after this update it took ~3000 us.

I am just throwing it here to consider as a possibility. Clearly my code above is just proof of concept, modification would be needed (include write fd, some proper fd limit, possibly check multiple times to get actual number of ready fds, etc...).
History
Date User Action Args
2018-10-09 16:13:33giampaolo.rodolasetnosy: + giampaolo.rodola
2018-10-09 07:51:29ondrej.kutalsetcomponents: + Extension Modules, - Interpreter Core
2018-10-08 14:53:49ondrej.kutalsettitle: Consider making Windows select.select interruptable using WSAEventSelect & WSAWaitForMultipleEvents -> Consider making Windows select.select interruptible using WSAEventSelect & WSAWaitForMultipleEvents
2018-10-08 14:32:57ondrej.kutalcreate