1

I have a python service which may be ended by exception - I'm ok with it. But the problem is that it spawned an child process which continues to run even it's parent got a fail.

import multiprocessing as mp
from time import sleep
import os
import atexit
import psutil

@atexit.register     # Doesn't fired at exception
def goodbye():
    current_process = psutil.Process()
    children = current_process.children(recursive=True)
    for child in children:
        print('Kill child with pid {}'.format(child.pid))
        try:
            child.terminate()
        except:
            pass
    print("You are now leaving the Python sector.")


def func():         # Child process
    while True:
        ppid = os.getppid()
        print("Parent process id:", ppid)
        if ppid == 1:
            print("Parent process has terminated")
            break
        sleep(1)

t = mp.Process(target=func, args=())
t.start()

print(9 + "0")      # Exception here
print("I'm ok")

And service continues to work (formally) until it got a kick from outside:

Parent process id: 29118
Traceback (most recent call last):
  File "stestcp.py", line 32, in <module>
    print(9 + "0")      # Exception here
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Parent process id: 29118
Parent process id: 29118
Parent process id: 29118
Parent process id: 29118
Parent process id: 29118
^CError in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/usr/lib/python3.6/multiprocessing/popen_fork.py", line 28, in poll
Process Process-1:
    pid, sts = os.waitpid(self.pid, flag)
KeyboardInterrupt
Traceback (most recent call last):
  File "/usr/lib/python3.6/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
  File "/usr/lib/python3.6/multiprocessing/process.py", line 93, in run
    self._target(*self._args, **self._kwargs)
  File "stestcp.py", line 27, in func
    sleep(1)
KeyboardInterrupt
Kill child with pid 29119
You are now leaving the Python sector.

The question is - is there any way to call some global fallback function (like atexit) when program failed with exception?

  • 1
    I would look here: https://stackoverflow.com/questions/9741351/how-to-find-exit-code-or-reason-when-atexit-callback-is-called-in-python – Andrej Kesely Jul 09 '18 at 06:30

1 Answers1

0

Thanks to Andrej Kesely, solution found. Working example now looks like:

import multiprocessing as mp
from time import sleep
import sys
import os
import atexit
import psutil

class ExitHooks(object):
    def __init__(self):
        self.exit_code = None
        self.exception = None

    def hook(self):
        self._orig_exit = sys.exit
        sys.exit = self.exit
        sys.excepthook = self.exc_handler

    def exit(self, code=0):
        self.exit_code = code
        self._orig_exit(code)

    def exc_handler(self, exc_type, exc, *args):   # Called at exception
        self.exception = exc
        goodbye()

hooks = ExitHooks()
hooks.hook()

@atexit.register     # Doesn't fired at exception
def goodbye():
    if hooks.exit_code is not None:
        print("death by sys.exit(%d)" % hooks.exit_code)
    elif hooks.exception is not None:
        print("death by exception: %s" % hooks.exception)
    else:
        print("natural death")
    current_process = psutil.Process()
    children = current_process.children(recursive=True)
    for child in children:
        print('Kill child with pid {}'.format(child.pid))
        try:
            child.terminate()
        except:
            pass
    print("You are now leaving the Python sector.")


def func():         # Child process
    while True:
        ppid = os.getppid()
        print("Parent process id:", ppid)
        if ppid == 1:
            print("Parent process has terminated")
            break
        sleep(1)

t = mp.Process(target=func, args=())
t.start()

sleep(2)
print(9 + "0")      # Exception here