Eryk Sun <eryk...@gmail.com> 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 <rep...@bugs.python.org>
<https://bugs.python.org/issue35935>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to