Eryk Sun <[email protected]> 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 <[email protected]>
<https://bugs.python.org/issue41437>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com