6

I start a process group from bash. Then I send SIGINT to the entire process group. Sometimes the SIGINT kills the processes, sometimes does not. Why does SIGINT sometimes gets ignored ?

I see different behavior depending on whether the process group is started in the background or not, on nestedness of bash shells and on Mac/Linux operating system. I would really appreciate if someone can shed some light on this.

In the following examples I use this python executable called sleep_in_pgrp.py

#!/usr/bin/env python2.7
import os;
import subprocess
os.setpgrp();
subprocess.check_call(["sleep","10000"]);

It creates a process group and starts sleep. The observed phenomena should not be related to python. I use python only because bash does not have a setpgrp command or builtin. Update: Apparently one could also run an interactive shell to create a new process group

1) Start the process group in the background and wait on the leader. SIGINT gets ignored.

Execute the following command:

$ bash -c '  { sleep_in_pgrp.py; } & wait $!  '

Bash starts python in the background and waits on it. In another terminal:

$ ps -Heo pid,ppid,tpgid,pgid,sid,user,args
   PID   PPID  TPGID   PGID    SID     COMMAND
  2507   1574   2963   2507   2507     -bash
  2963   2507   2963   2963   2507       bash -c   { sleep_in_pgrp.py; } & wait $!
  2964   2963   2963   2963   2507         bash -c   { sleep_in_pgrp.py; } & wait $!
  2965   2964   2963   2965   2507           python2.7 ./sleep_in_pgrp.py
  2966   2965   2963   2965   2507             sleep 10000

SIGINT’ing the proccess group of python does not kill any processes. What can be the reason ?

$ sudo kill -s SIGINT -- -2965

2) Start the process group in the foreground. SIGINT works.

If I remove the & wait $!, SIGINT kills the process group as expected. I do not know why but I am not surprised SIGINT killed the processes in this case.

$ bash -c '  { sleep_in_pgrp.py; }  '

In another terminal:

$ ps -Heo pid,ppid,tpgid,pgid,sid,user,args
   PID   PPID  TPGID   PGID    SID     COMMAND
  2507   1574   3352   2507   2507     -bash
  3352   2507   3352   3352   2507       bash -c   { sleep_in_pgrp.py; }
  3353   3352   3352   3353   2507         python2.7 ./sleep_in_pgrp.py
  3354   3353   3352   3353   2507           sleep 10000

SIGINT kills the process group.

$ sudo kill -s SIGINT -- -3353

3) Removing the subshell while running python in the background. SIGINT works.

I was very surprised that the shell nestedness affects the behavior here. I cannot think of any explanation why.

I remove the bash -c at the start:

$ { sleep_in_pgrp.py; } & wait $!

In another terminal:

$ ps -Heo pid,ppid,tpgid,pgid,sid,user,args
   PID   PPID  TPGID   PGID    SID     COMMAND
  2507   1574   2507   2507   2507     -bash
  3488   2507   2507   3488   2507       -bash
  3489   3488   2507   3489   2507         python2.7 ./sleep_in_pgrp.py
  3490   3489   2507   3489   2507           sleep 10000

SIGINT kills the process group.

$ sudo kill -s SIGINT -- -2507

4) Running the first command in Mac: SIGINT works.

The first 2 commands ran in a CentOs7 VM.

$ uname -a
Linux ip-10-229-193-124 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 13 10:46:25 EDT 2017 x86_64 x86_64 x86_64 GNU/Linux

I now execute the first command with backgrounded python in a subshell in mac.

$ uname -a
Darwin mbp-005063 15.6.0 Darwin Kernel Version 15.6.0: Sun Jun  4 21:43:07 PDT 2017; root:xnu-3248.70.3~1/RELEASE_X86_64 x86_64

In Mac:

$ bash -c '  { sleep_in_pgrp.py; } & wait $!  '

In another terminal:

$   PID  PPID TPGID  PGID   SESS COMMAND
18741 40096 18741 18741      0 bash -c   { sleep_in_pgrp.py; } & wait $!
18742 18741 18741 18741      0 bash -c   { sleep_in_pgrp.py; } & wait $!
18743 18742 18741 18743      0 /usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python ./sleep_in_pgrp.py
18744 18743 18741 18743      0 sleep 10000
40094  2423 18741 40094      0 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
40095 40094 18741 40095      0 /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
40096 40095 18741 40096      0 -bash
-+= 00001 root /sbin/launchd
 \-+= 02423 hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 40094 hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --server /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
     \-+= 40095 root /usr/bin/login -fpl hbaba /Applications/iTerm.app/Contents/MacOS/iTerm2 --launch_shell
       \-+= 40096 hbaba -bash
         \-+= 18741 hbaba bash -c   { sleep_in_pgrp.py; } & wait $!
           \-+- 18742 hbaba bash -c   { sleep_in_pgrp.py; } & wait $!
             \-+= 18743 hbaba /usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python ./sleep_in_pgrp.py
               \--- 18744 hbaba sleep 10000

And in this case, SIGINT also kills the process group

$ sudo kill -s INT -18743

Bash version in CentOs7 is

$ echo $BASH_VERSION
4.2.46(2)-release

In Mac the bash version is

$ echo $BASH_VERSION
4.4.12(1)-release

This answer explains how Ctrl+C sends SIGINT to the process group. That is what I am trying to do here sending SIGINT to a process group. This answer mentions that non interactive jobs require a SIGINT handler. I am not sure it explains the varying behavior I see. I am also wondering whether waiting on a background process affects the SIGINT handling by that process.

  • After writing the question down, I actually see that 2) and 3) result in the same process tree. So it is not surprising that in both cases the SIGINT can kill the process group. But maybe there are other factors there. – Hakan Baba Oct 29 '17 at 08:08
  • What happens when you add a SIGINT handler to the python code, instead of relying on whatever the shell sets up for that? – thrig Oct 29 '17 at 14:45
  • 1
    Your question would be a lot easier to read if it didn't require horizontal scrolling and de-obfuscating Python scripts. FWIW. – Satō Katsura Oct 29 '17 at 18:50
  • @SatōKatsura I was also not too happy with the formatting. Any suggestions? I could save the python code in a bash variable and reuse it. Does that sound reasonable? I do not know about the long lines. I did not want to split the long lines in ps output that show a tree. – Hakan Baba Oct 29 '17 at 18:53
  • You could also write the Python script to a file instead of inlining it. It doesn't make any difference from the point of view of the question. – Satō Katsura Oct 29 '17 at 18:54
  • @SatōKatsura great suggestion. I will edit the question with using a python file once I get to my PC. – Hakan Baba Oct 29 '17 at 18:57
  • @SatōKatsura I have removed most of the horizontal scrolling with using a named python file. The process trees look more readable except the one in Mac. FYI. – Hakan Baba Oct 31 '17 at 23:23
  • Shells set the dispositions of SIGINT and SIGQUIT to SIG_IGN for backgrounded processes. Furthermore, these two may be momentarily ignored (SIG_IGN) in a parent process during a call to the system C function. Considering that, a process-group-targetted SIGINT or SIGQUIT is not a good way to terminate a process group. A process-group-targeted SIGTERM should do it better. – Petr Skocik Oct 31 '17 at 23:32
  • @PSkocik I agree with you that SIGTERM is better for cleaning after yourself. However the use case here is not to clean-up after myself. I ran into this problem while trying to write a test for the SIGINT handling of an application of mine. After some simplifications the question formulation has changed, hence the following is approximately true: Instead of sleep 1000 I have an application that I want to ensure handles SIGINT correctly. I am calling this via jenkins. All my tests worked in local mac, once I have executed them remotely I realized I cannot SIGINT a backgrounded process group. – Hakan Baba Nov 01 '17 at 00:06

2 Answers2

0

It's so possible that this process capture SIGINT signal to use with other function.

The process only dead if the signal received is unknown for it. But, if process set a function linked to this signal, It will don't dead, unless the function linked to this signal finish the program.

By example, a simply C program:

 #include <signal.h>

 int sigreceived = 0;

 void mysignal();

 int main(){
    signal(2, mysignal); //SIGINT corresponds to 2 signal

    while(1);
    return 0;
  }

 void mysignal(){
  sigreceived=1;
 }

In this program, signal 2 is captured to call mysignal function what, instead kill the process, simply change the value of a variable

Then, this process don't kill with SIGINT

AlmuHS
  • 169
0

Remember that SIGINT whether sent by "kill -INT pid" or "kill -INT -- -pgid" or Ctrl-C always affects foreground processes (the foreground process group); everything started asynchronously with & is not affected by INT (except subshells running in background like command substitution: (cmd;cmd, ...etc ) & or $(cmd) &).

And don't forget: bash implements WCE (wait and cooperative exit), which means that a parent process waits for a child process to finish when it receives SIGINT via Ctrl-C and then decides to die only, when the child died by SIGINT.

a better explanation than mine is https://www.cons.org/cracauer/sigint.html

Archemar
  • 31,554