Eryk Sun <[email protected]> added the comment:
Python's C signal handler sets a flag and returns, and the signal is eventually
handled in the main thread. In Windows, this means the Python SIGINT handler
won't be called so long as the main thread is blocked. (In Unix the signal is
delivered on the main thread and interrupts most blocking calls.)
In Python 3, our C signal handler also signals a SIGINT kernel event object.
This gets used in functions such as time.sleep(). However, threading wait and
join methods do not support this event. In principle they could, so long as the
underlying implementation continues to use kernel semaphore objects, but that
may change. There's been pressure to adopt native condition variables instead
of using semaphores.
When you enable the default handler, that's actually the default console
control-event handler. It simply exits via ExitProcess(STATUS_CONTROL_C_EXIT).
This works because the console control event is delivered by creating a new
thread that starts at a private CtrlRoutine function in kernelbase.dll, so it
doesn't matter that the main thread may be blocked. By default SIGBREAK also
executes the default handler, so Ctrl+Break almost always works to kill a
console process. Shells such as cmd.exe usually ignore it, because it would be
annoying if Ctrl+Break also killed the shell and destroyed the console window.
Note also that Python's signal architecture cannot support CTRL_CLOSE_EVENT,
even though it's also mapped to SIGBREAK. The problem is that our C handler
simply sets a flag and returns. For the close event, the session server waits
on the control thread for up to 5 seconds and then terminates the process. Thus
the C signal handler returning immediately means our process will be killed
long before our Python handler gets called.
We may need to actually handle the event, such as ensuring that atexit
functions are called. Currently the only way to handle closing the console
window and cases where the main thread is blocked is to install our own console
control handler using ctypes or PyWin32. Usually we do this to ensure a clean,
controlled shutdown. Here's what this looks like with ctypes:
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
CTRL_C_EVENT = 0
CTRL_BREAK_EVENT = 1
CTRL_CLOSE_EVENT = 2
HANDLER_ROUTINE = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD)
kernel32.SetConsoleCtrlHandler.argtypes = (
HANDLER_ROUTINE,
wintypes.BOOL)
@HANDLER_ROUTINE
def handler(ctrl):
if ctrl == CTRL_C_EVENT:
handled = do_ctrl_c()
elif ctrl == CTRL_BREAK_EVENT:
handled = do_ctrl_break()
elif ctrl == CTRL_CLOSE_EVENT:
handled = do_ctrl_close()
else:
handled = False
# If not handled, call the next handler.
return handled
if not kernel32.SetConsoleCtrlHandler(handler, True):
raise ctypes.WinError(ctypes.get_last_error())
The do_ctrl_* functions could simply be sys.exit(1), which will ensure that
atexit handlers get called.
----------
nosy: +eryksun
_______________________________________
Python tracker <[email protected]>
<https://bugs.python.org/issue35935>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com