pythonsubprocesssigterm

How to propagate SIGTERM to children created via subprocess


Given the following Python scripts:

a.py:

#!/usr/bin/env python3
# a.py
import signal
import subprocess
import os

def main():
    print('Starting process {}'.format(os.getpid()))
    subprocess.check_call('./b.py')

if __name__ == '__main__':
    main()

b.py:

#!/usr/bin/env python3
# b.py
import signal
import time
import os

def cleanup(signum, frame):
    print('Cleaning up...')
    raise RuntimeError("Error")

def main():
    print('Starting process {}'.format(os.getpid()))
    signal.signal(signal.SIGINT, cleanup)
    signal.signal(signal.SIGTERM, cleanup)

    while True:
        print('Hello')
        time.sleep(1)

if __name__ == '__main__':
    main()

If I execute a.py, and then later I kill it via kill -15 <pid_of_a_py>, it kills a.py, but b.py keeps running:

$ ./a.py 
Starting process 119429
Starting process 119430
Hello
Hello
Hello
Hello
Hello
Terminated   // On a separate terminal, I ran "kill -15 119429"
$ Hello
Hello
Hello
Hello

Why is that? How can I make sure that SIGTERM is propagated from a.py to b.py? Consider also a deeper chain a.py -> b.py -> c.py -> d.py ... Where I only want to explicitly setup error handling and cleanup for the innermost script.


Solution

  • One solution is to explicitly throw SystemExit from a.py

    #!/usr/bin/env python3
    # a.py
    import signal
    import subprocess
    import os
    
    
    def cleanup(signum, frame):
        raise SystemExit(signum)
    
    def main():
        signal.signal(signal.SIGINT, cleanup)
        signal.signal(signal.SIGTERM, cleanup)
        print('Starting process {}'.format(os.getpid()))
        subprocess.check_call('./b.py')
    
    if __name__ == '__main__':
        main()
    

    Alternatively you can start the process with Popen and call Popen.send_signal to the child process when parent exits.

    EDIT:

    I've done some reading on the topic and it's an expected behaviour. kill -15 <pid> sends the signal to the specified process and only this one, the signal is not supposed to be propagated. However, you can send a signal to the process group which will kill all children as well. The syntax is kill -15 -<pgid> (note extra dash). The process group id is typically the same as the leader process id.