Why Do Shells Ignore Sigint and Sigquit in Backgrounded Processes

Why bash background task ignores SIGINT?

This construct, (sleep 1000 &) puts your sleep command in a grandchild subshell with no job control:

your-shell
\
a compound-list in a subshell
\
an asynchronous list in a subshell

The first subshell, ( compound-list ) (a grouping command construct), simply runs the backgrounded command & (an asynchronous list) and then exits. The asynchronous list is run in its own subshell.

That final subshell is too far removed from your initial shell for job control to be meaningful.

Per POSIX, "[i]f job control is disabled ... 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."

Thus your sleep is run with SIGINT set to ignore.

bash: Why can't I set a trap for SIGINT in a background shell?

SIGINT and SIGQUIT are ignored in backgrounded processes (unless they're backgrounded with set -m on). It's a (weird) POSIX requirement (see
2. Shell Command Language or my SO question Why do shells ignore SIGINT and SIGQUIT in backgrounded processes? for more details).

Additionally, POSIX requires that:

When a subshell is entered, traps that are not being ignored shall be
set to the default actions, except in the case of a command
substitution containing only a single trap command ..

However, even if you set the INT handler in the subshell again after it was reset, the subshell won't be able to receive it because it's ignored (you can try it or you can inspect the signal ignore mask using ps, for example).

Handling signals in bash script when it started as a background process

Bash manual states:

  • Background processes (...) are immune to keyboard-generated signals.
  • If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.

Consequently:

  • If your script runs in the foreground: when you press "Ctrl-C", a SIGINT is sent to the currently running process (i.e. the sleep command). The exit status of sleep tells Bash that it was interrupted by the SIGINT signal and bash calls your trap.
  • If your script runs in the background, then the backgrounded sleep does not receive the signal and the SIGINT trap is only executed once sleep has ended.

Sending SIGINT to foreground process works but not background

I guess what you are trying to achieve is that when script2 receives the SIGINT it continues and prints the message. Then, you need

#!/bin/bash
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...; CONT=true' SIGINT
CONT=false
while ! $CONT
do
sleep 1
done
echo "~~EXIT"

Unable to trap SIGINT signal in a background shell

The bash manpage on OSX (but it should be the same in other versions) has this to say about signal handling:

Non-builtin commands run by bash have signal handlers set to the values
inherited by the shell from its parent. When job control is not in
effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to
these inherited handlers.

and further on, under the trap command:

Signals ignored upon entry to the shell cannot
be trapped or reset.

Since scripts don't use job control by default, this means the case you're talking about.

Why does POSIX demand that system(3) ignores SIGINT and SIGQUIT?

SIGINT and SIGQUIT are terminal generated signals. By default, they're sent to the foreground process group when you press Ctrl+C or Ctrl+\ respectively.

I believe the idea for ignoring them while running a child via system is that the terminal should be as if it was temporarily owned by the child and Ctrl+C or Ctrl+\ should temporarily only affect the child and its descendants, not the parent.

SIGCHLD is blocked so that system's the SIGCHLD caused by the child terminating won't trigger a SIGCHLD handler if you have one, because such a SIGCHLD handler might reap the child started by system before system reaps it.

How do I launch a background process through a wrapper that'll undo the SIGINT ignore?

If the process is execed (=isn't just a subshell but is based on binary), you can run it through a wrapper that'll undo the SIGINT/SIGQUIT ignore:

reset_sigint.c:

#include <signal.h>
#include <unistd.h>
int main(int C, char**V)
{
sigaction(SIGINT,&(struct sigaction){.sa_handler=SIG_DFL}, 0);
execvp(V[1],V+1);
return 127;
}

To prevent the SIGINT/SIGQUIT ignore more generically for any process run as a command in the shell, you can run it with set -m on

sh -c 'set -m; ( sleep 10 )& ps -p $! -o ignored'
#compare with: sh -c '( sleep 10 )& ps -p $! -o ignored'

but that'll run the command in a separate process group as a (possibly undesirable) sideffect.



Related Topics



Leave a reply



Submit