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.
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.