pythonlinuxsubprocessjackprocess-group

How to kill a forked child and its jackd subprocess in python


I am trying to implement a tray icon, that lets me control a process (jackd). Most notably

The killing is causing me headaches. I believe, when I read stdout and stderr I must do this in an extra thread/process to keep the icon responsive. So I used fork() to create a child process and called setsid() on the child. That child process then invokes jackd using the subprocess module

from subprocess import PIPE, STDOUT, Popen
def runAndLog(self, cmd):
    po = Popen(cmd, stdout=PIPE, stderr=STDOUT)
    line = po.stdout.readline()
    while line:
       print(line.rstrip())
       line = po.stdout.readline()
 self.runAndLog(["jackd","-R", "-P80", "-t5000", "-dfirewire", "-r44100", "-p256", "-n3"])

Now I was hoping that I could get my forked child process and jackd into the same process group, so I could kill both in one go, but to my surprise Popen created a new process group and a new session:

  PID  PPID  PGID   SID COMMAND
 5790  5788  5788 31258 python (parent, main)
 5791  5790  5791  5791 python (forked child after setsid - ok)
 5804  5791  5804  5804 jackd  (grandchild created by Popen)

So, killing the child won't kill the grandchild and I don't see any clean way of making the grandchild's PID (5804) known to main in order to kill it explicitly. I could probably use some signal trickery to have the dying child kill the grandchild, but I hesitate to do this.

I also cannot use setpgid on the jackd process to force it into the child's process group, because its SID is different from my child's SID.

I believe I either

but I don't know how to do either. Admittedly my knowledge about process groups is somewhat limited.

/home/martin >python --version
Python 2.7.1

Update Oct 12

I found that the new session isn't created by python but by jackd itself. When I replace jackd with xterm, everything is fine.

  PID  PPID  PGID   SID COMMAND
12239  4024 12239  4024 python (parent, main)
12244 12239 12244 12244 python (forked child after setsid - ok)
12249 12244 12244 12244 xterm  (grandchild has same PGID and SID - ok)

I also found this

these jack days calls setsid() to get itself established as a new process group leader

That only leaves the options to

Any advice on that?

.


Solution

  • Since your grandchild sets its own session and sid, you cannot expect group-kill to work properly. You have three options:

    1. Iterate over the whole tree of children processes, and kill them all starting from the parents (so that they would not restart the children). Ugly and overcomplicated solution.

    2. Use a thread in the main (root) process to control the jack child directly. The intermediate child process is probably not necessary here, as it gives no benefits. This is the most simple solution possible.

    3. If you still have to have the intermediate child process, the only way is to catch the termination signals sent to the child, and transmit them to the grandchild:

    child.py:

    import signal
    import subprocess
    
    # The signal handler for the child process, which passes it further to the grandchild.
    def handler(signum, frame):
        print('Killing self, killing the children')
    
        os.kill(proc.pid, signal.SIGTERM)
    
        # wait for the process to die gracefully for N secs.
        time.sleep(5)
    
        os.kill(proc.pid, signal.SIGKILL)
    
    cmd = ["jackd","-R", "-P80", "-t5000", "-dfirewire", "-r44100", "-p256", "-n3"]
    po = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    
    signal.signal(signal.SIGTERM, handler)
    
    for line in po.stdout:
        print(line.rstrip())
    

    Please note that the signal handler is installed before the process communication begins (line reading).

    Whenever your root process will be killing the child process "gracefully" (e.g., with SIGTERM), the signal will be passed to the grandchild, and it will be terminated/killed too. You can even extend the logic of killing and waiting and force-killing it.

    However, remember that if the child process is killed with SIGKILL, there will be no chances to catch this signal, and the grandchild will remain running orphan. Because SIGKILL is not catchable by design.