Eryk Sun <eryk...@gmail.com> added the comment:

Winsock is inherently asynchronous. It implements synchronous functions by 
using an alertable wait for the completion of an asynchronous I/O request. 
Python doesn't implement anything for a console Ctrl+C event to alert the main 
thread when it's blocked in an alterable wait. NTAPI NtAlertThread will alert a 
thread in this case, but it won't help here because Winsock just rewaits when 
alerted. 

You need a user-mode asynchronous procedure call (APC) to make the waiting 
thread cancel all of its pended I/O request packets (IRPs) for the given file 
(socket) handle. Specifically, open a handle to the thread, and call 
QueueUserAPC to queue an APC to the thread that calls WinAPI CancelIo on the 
file handle. (I don't suggest using the newer CancelIoEx function from an 
arbitrary thread context in this case. It would be simpler than queuing an APC 
to the target thread, but you don't have an OVERLAPPED record to cancel a 
specific IRP, so it would cancel IRPs for all threads.)

Here's a context manager that temporarily sets a Ctrl+C handler that implements 
the above suggestion:

    import ctypes
    import threading
    import contextlib

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    CTRL_C_EVENT = 0
    THREAD_SET_CONTEXT = 0x0010

    @contextlib.contextmanager
    def ctrl_cancel_async_io(file_handle):
        apc_sync_event = threading.Event()
        hthread = kernel32.OpenThread(THREAD_SET_CONTEXT, False,
            kernel32.GetCurrentThreadId())
        if not hthread:
            raise ctypes.WinError(ctypes.get_last_error())

        @ctypes.WINFUNCTYPE(None, ctypes.c_void_p)
        def apc_cancel_io(ignored):
            kernel32.CancelIo(file_handle)
            apc_sync_event.set()

        @ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint)
        def ctrl_handler(ctrl_event):
            # For a Ctrl+C cancel event, queue an async procedure call
            # to the target thread that cancels pending async I/O for
            # the given file handle.
            if ctrl_event == CTRL_C_EVENT:
                kernel32.QueueUserAPC(apc_cancel_io, hthread, None)
                # Synchronize here in case the APC was queued to the
                # main thread, else apc_cancel_io might get interrupted
                # by a KeyboardInterrupt.
                apc_sync_event.wait()
            return False # chain to next handler

        try:
            kernel32.SetConsoleCtrlHandler(ctrl_handler, True)
            yield
        finally:
            kernel32.SetConsoleCtrlHandler(ctrl_handler, False)
            kernel32.CloseHandle(hthread)


Use it as follows in your sample code:

    with ctrl_cancel_async_io(sock.fileno()):
        sock.sendall(b"hello")
        sock.recv(1024)

Note that this requires the value of sock.fileno() to be an NT kernel handle 
for a file opened in asynchronous mode. This is the case for a socket.

HTH

----------
nosy: +eryksun

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue41437>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to