1

While debugging another question, I discovered that if Python is launched from a shell script with &, the signal handling settings for SIGINT are changed to ignore it.

x.py contents:

import signal
print(signal.getsignal(signal.SIGINT))

noproblem.sh contents:

python3 x.py

problem.sh contents:

python3 x.py &

When running x.py directly, directly with &, or through noproblem.sh, the signal handler for SIGINT is the default signal.default_int_handler, which is responsible for raising KeyboardInterrupt:

07:14 ~ $ python3 x.py
<built-in function default_int_handler>
07:14 ~ $ python3 x.py &
[1] 126909
07:14 ~ $ <built-in function default_int_handler>

[1]+  Done                    python3 x.py
07:14 ~ $ bash noproblem.sh
<built-in function default_int_handler>

But running x.py through problem.sh, SIGINT is ignored:

07:14 ~ $ bash problem.sh
07:14 ~ $ Handlers.SIG_IGN

I couldn't find any documentation explaining why this might happen. The signal module documentation doesn't mention this behavior. Is this deliberate, or a bug?

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • The`&` in shell sends the new process created for the task to the background. Background processes do not receive signals. – MYousefi Dec 25 '20 at 07:32
  • @MYousefi: Background processes are fully capable of receiving signals, though they may not receive the same signals in the same circumstances as a foreground process. Note that your explanation would not explain why manually running `python3 x.py &` shows the default handler, or why any of this would affect Python's own signal handling settings if it were just a matter of whether signals are received. – user2357112 Dec 25 '20 at 07:35
  • 1
    Perhaps [this](https://stackoverflow.com/questions/974189/) can help. – MYousefi Dec 25 '20 at 07:47

1 Answers1

1

After further digging, I've managed to track down the cause.

First, if Python inherits an OS-level setting of SIG_IGN for SIGINT from its parent process, it does not install its default SIGINT handler. It only installs that handler if it inherits a SIG_DFL setting. We can see this in the source code for the signal module:

IntHandler = PyDict_GetItemString(d, "default_int_handler");
if (!IntHandler)
    goto finally;
Py_INCREF(IntHandler);

_Py_atomic_store_relaxed(&Handlers[0].tripped, 0);
for (i = 1; i < NSIG; i++) {
    void (*t)(int);
    t = PyOS_getsig(i);
    _Py_atomic_store_relaxed(&Handlers[i].tripped, 0);
    if (t == SIG_DFL)
        Handlers[i].func = DefaultHandler;
    else if (t == SIG_IGN)
        Handlers[i].func = IgnoreHandler;
    else
        Handlers[i].func = Py_None; /* None of our business */
    Py_INCREF(Handlers[i].func);
}
if (Handlers[SIGINT].func == DefaultHandler) {
    /* Install default int handler */
    Py_INCREF(IntHandler);
    Py_SETREF(Handlers[SIGINT].func, IntHandler);
    PyOS_setsig(SIGINT, signal_handler);
}

Second, shell scripts run with job control disabled by default, and processes started with & when job control is disabled inherit SIG_IGN settings for SIGINT and SIGQUIT. Quoting POSIX:

If job control is disabled (see the description of set -m) when the shell executes an asynchronous list, the commands in the list shall inherit from the shell a signal action of ignored (SIG_IGN) for the SIGINT and SIGQUIT signals.

I could not find an explicit standard quote saying job control is disabled by default in shell scripts, only quotes saying job control is enabled by default for interactive shells (vaguely implying the opposite setting for noninteractive shells):

-m

This option shall be supported if the implementation supports the User Portability Utilities option. All jobs shall be run in their own process groups. Immediately before the shell issues a prompt after completion of the background job, a message reporting the exit status of the background job shall be written to standard error. If a foreground job stops, the shell shall write a message to standard error to that effect, formatted as described by the jobs utility. In addition, if a job changes status other than exiting (for example, if it stops for input or output or is stopped by a SIGSTOP signal), the shell shall write a similar message immediately prior to writing the next prompt. This option is enabled by default for interactive shells.

user2357112
  • 260,549
  • 28
  • 431
  • 505