Avoid nested SIGINT handling

Eryk Sun eryksun at gmail.com
Sat Nov 13 17:41:45 EST 2021


On 11/12/21, Mladen Gogala via Python-list <python-list at python.org> wrote:
> On Thu, 11 Nov 2021 17:22:15 +1100, Chris Angelico wrote:
>
>> Threads aren't the point here - signals happen immediately.
>
> [snip: description of POSIX signals]
>
> BTW, that's the case on both Unix/Linux systems and Windows systems.

Windows is quite different. The base NT system has nothing that's
closely analogous to POSIX signals. Its asynchronous procedure call
(APC) capability is roughly similar to POSIX signals. Every thread has
two APC queues, one for kernel-mode APCs and one for user-mode APCs.
Unlike a POSIX signal, queuing an APC requires the address of a
function in the process of the target thread. This requires well-known
or registered function targets. User-mode APCs can be queued to a
thread via QueueUserAPC(). Unless forced, they execute when the thread
does an alertable wait.

Another similar feature is a window message. A thread that uses any
window-manager function is converted to a GUI thread with a message
queue. A thread's window message queue is typically processed during a
GetMessage() or PeekMessage() call. A message can be queued directly
to a thread via PostThreadMessage(), provided it's a GUI thread.

There's an overlap between Windows exceptions and POSIX signals.
Exceptions get raised either from the kernel or RaiseException().
Exceptions have stack based handlers that are set via Microsoft's
extended C __try/__except and __try/__finally statements. Process-wide
exception handlers can also be set via AddVectoredExceptionHandler()
and SetUnhandledExceptionFilter().

Windows also has console control events, which the console host
(conhost.exe or openconsole.exe) sends to process groups that are
connected to the hosted session (i.e. a TUI window/tab on a GUI
desktop). The console host generates these events in response to
typing Ctrl+C (cancel) or Ctrl+Break in the window/tab, closing the
window/tab, user logoff, and system shutdown.

    CTRL_C_EVENT (0)
    CTRL_BREAK_EVENT (1)
    CTRL_CLOSE_EVENT (2)
    CTRL_LOGOFF_EVENT (5)
    CTRL_SHUTDOWN_EVENT (6)

The Ctrl+C and Ctrl+Break events can also be generated for a process
group via GenerateConsoleCtrlEvent(ctrlEvent, processGroupId).

There's no thread or process queue for console control events. To
deliver an event, the console host sends a message to the Windows
session server (csrss.exe) that contains the control event number and
one or more target processes. The session server creates a new thread
in each target process. For example, if the user presses Ctrl+C five
times sequentially in the console window, csrss.exe creates five
threads in every process that's attached to the console. This thread
injection is so different from POSIX signals that I hesitate to group
it with signal-like features.

A console control thread begins executing at the CtrlRoutine()
function in kernelbase.dll. It sequentially iterates the list of
registered handlers. Each is called with the given control event. If a
handler returns TRUE (i.e. handled), the control thread exits. If no
handler returns TRUE, the default handler is called, which exits the
process with the exit code STATUS_CONTROL_C_EXIT (0xC000013A). A
inheritable flag can be set to disable CTRL_C_EVENT, such that
CtrlRoutine() basically does nothing for this event.

For CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT, the
process must be terminated after the configured timeout, which is 5
seconds in a default setup. The process can exit gracefully of its own
accord before the timeout.

The C runtime library in Windows emulates some signals that are
required by the language standard.

The following two are emulated for C raise() and abort():

    SIGABRT - abort, abnormal termination
    SIGTERM - terminate

The following three are based on an exception handler:

    SIGSEGV - segmentation violation
    SIGILL - illegal/invalid instruction
    SIGFPE - floating point exception

The following two are based on a console control event handler
(SIGBREAK is non-standard):

    SIGINT - interrupt
    SIGBREAK - break, close, logoff, shutdown

For SIGINT and SIGBREAK, the C runtime uses CTRL_C_EVENT and CTRL_BREAK_EVENT.

It also maps SIGBREAK to CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and
CTRL_SHUTDOWN_EVENT.  But Python's C signal handler just sets a flag
and returns, so SIGBREAK due to those events can't be handled with
Python's signal module. The process gets terminated long before the
main thread can call Python's registered SIGBREAK handler.


More information about the Python-list mailing list