Why do shells ignore SIGINT and SIGQUIT in backgrounded processes?
Asked Answered
H

1

21

If I background a processes in a script or a -c snippet, the backgrounded processes ignores SIGINT and SIGQUIT:

Example:

$ alias ps='ps -o pid,ppid,pgrp,sid,stat,tty,ignored,blocked,caught,wchan,min_flt,pmem,args --forest'
$ sh -c 'sleep 1000 & sleep 1000 | sleep 1000' & \
  sleep 0.01; ps |grep -v -e ps -e grep 
  PID  PPID  PGRP   SID STAT TT                IGNORED          BLOCKED           CAUGHT WCHAN   MINFL %MEM COMMAND
 6197  2143  6197  6197 Ss   pts/28   0000000000380004 0000000000010000 000000004b817efb wait    10039  0.0 -bash
 7593  6197  7593  6197 S    pts/28   0000000000000000 0000000000000000 0000000000010002 wait      148  0.0  \_ sh -c sleep 1000 & sleep 1000 | sleep 1000
 7595  7593  7593  6197 S    pts/28   0000000000000006 0000000000000000 0000000000000000 hrtime     85  0.0  |   \_ sleep 1000
 7596  7593  7593  6197 S    pts/28   0000000000000000 0000000000000000 0000000000000000 hrtime     85  0.0  |   \_ sleep 1000
 7597  7593  7593  6197 S    pts/28   0000000000000000 0000000000000000 0000000000000000 hrtime     85  0.0  |   \_ sleep 1000

This means that if I run kill -INT -$! (or fg followed by Ctrl-C) from the interactive parent shell (bash), the sleep processes backgrounded from the -c snippet isn't reached and survives.

  PID  PPID  PGRP   SID STAT TT                IGNORED          BLOCKED           CAUGHT WCHAN   MINFL %MEM COMMAND
 6197  2143  6197  6197 Ss   pts/28   0000000000380004 0000000000010000 000000004b817efb wait    10103  0.0 -bash
 7595     1  7593  6197 S    pts/28   0000000000000006 0000000000000000 0000000000000000 hrtime     85  0.0 sleep 1000

What is the reason for this behavior? Can it be disabled?

Heteroclite answered 14/7, 2017 at 15:30 Comment(6)
The background process is in a different process group from the shell.Iatrics
@Iatrics It's not. See the posted ps output. The process backgrounded from the -c snippet has the same process group number, however, SIGINT and SIGQUIT are included in its ignored signal mask (0000000000000006).Heteroclite
From the bash manpage (without motivation): When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.Falgout
@WilliamPursell It's seems like an unecessary (emulatable with (trap '' INT QUIT; exec the_command) ) half-assed (Ctrl-Z can still reach the background process) emulation of processes group, but whatever the reason for it is, I found it in POSIX (pubs.opengroup.org/onlinepubs/9699919799/utilities/…) so I guess I'll have to deal with it, no matter how annoying it is.Heteroclite
Do we really need three processes and a pipe to demonstrate this? What would be the simplest example?Euryale
@PiotrDobrogost You want to show at least one process where it does happen and one where it doesn't, i.e.: sh -c sleep 1000 & sleep 1000', especially to show the ignore effect is due to what &` in a script does and not due to inheritance from the script's parent. The pipeline is clearly unnecessary in the example, but pipeline links are somewhat like backgrounding (in that they add pararelization to an otherwise conceptually singlethreaded script) so it doesn't hurt much, IMO, to throw it in there to show how signal dispositions are handled in a pipeline.Heteroclite
I
12

When a shell runs a program in the background, the background process is not supposed to be tied to the original shell any more -- the shell can exit or be killed, and the background process should continue running.

If the shell is interactive and job control is being used, it puts the background process in a separate process group, so signals sent to the shell process group don't affect it.

But when job control is not being used, which is the default in non-interactive shells, the background process is in the same process group. To avoid the background process receiving keyboard signals that are just intended for the shell, it apparently ignores those signals in those child processes.

Iatrics answered 14/7, 2017 at 15:44 Comment(9)
ok, but do you have a workaround? How can I send SIGINT to a background process? I ask this, as eg. in python, catching SIGINT is simple and easy, but other signals are awkward and a bit messy to catch.Hist
@Hist If a process establishes its own signal handlers, that will replace the default setting inherited from the shell. So you shouldn't have any problem if your Python script catches SIGINT.Iatrics
This doesn't appear to work in Python. I suspect the Python approach to setting a signal handler doesn't actually set the processes signal handler, but an internal python one, leaving the process handler untouched (with the assumption the process will get signals!)Hist
That's not possible. If Python doesn't set the process signal handler, the default would be to kill the process. The only way to override any default is to set a signal handler.Iatrics
I suppose it's possible that Python checks whether the process signal handler is set to SIG_IGN, and then it doesn't establish its own, but that seems perverse. Maybe you should ask a Python question about this.Iatrics
I suspect Python doesn't even do that... I just tried it out - SIGINT doesn't get through even after setting a handler in the normal python way.Hist
I can't think of how that would work. There isn't an ignore flag. SIG_IGN is just a special signal handler that you can set; if a program sets its own signal handler, it replaces that, and the signal won't be ignored.Iatrics
Let us continue this discussion in chat.Hist
If the shell is interactive and job control is being used, it puts the background process in a separate process group, so signals sent to the shell process group don't affect it. Then why in such a case the background process still have SIGINT and SIGQUIT masked? Tested with { seq 100000000 & } | less +F in one terminal and ps -O ppid,pgrp,sid,ignored --forest -C bash,seq,less in another with bash 4.4.12.Euryale

© 2022 - 2024 — McMap. All rights reserved.