Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multiprocessing produces defunct process

I use Tornado as a web server, user can submit a task through the front end page, after auditing they can start the submitted task. In this situation, i want to start an asynchronous sub process to handle the task, so i write the following code in an request handler:

def task_handler():
    // handle task here

def start_a_process_for_task():
    p = multiprocessing.Process(target=task_handler,args=())
    p.start()
    return 0

I don't care about the sub process and just start a process for it and return to the front end page and tell user the task is started. The task itself will run in the background and will record it's status or results to database so user can view on the web page later. So here i don't want to use p.join() which is blocking, but without p.join() after the task finished,the sub process becomes a defunct process and as Tornado runs as a daemon and never exits, the defunct process will never disappear.

Anyone knows how to fix this problem, thanks.

like image 518
Ke Lu Avatar asked Dec 24 '22 15:12

Ke Lu


1 Answers

The proper way to avoid defunct children is for the parent to gracefully clean up and close all resources of the exited child. This is normally done by join(), but if you want to avoid that, another approach could be to set up a global handler for the SIGCHLD signal on the parent.

SIGCHLD will be emitted whenever a child exits, and in the handler function you should either call Process.join() if you still have access to the process object, or even use os.wait() to "wait" for any child process to terminate and properly reap it. The wait time here should be 0 as you know for sure a child process has just exited. You will also be able to get the process' exit code / termination signal so it can also be a useful method to handle / log child process crashes.

Here's a quick example of doing this:

from __future__ import print_function

import os
import signal
import time
from multiprocessing import Process


def child_exited(sig, frame):
    pid, exitcode = os.wait()
    print("Child process {pid} exited with code {exitcode}".format(
        pid=pid, exitcode=exitcode
    ))


def worker():
    time.sleep(5)
    print("Process {pid} has completed it's work".format(pid=os.getpid()))


def parent():
    children = []

    # Comment out the following line to see zombie children
    signal.signal(signal.SIGCHLD, child_exited)

    for i in range(5):
        c = Process(target=worker)
        c.start()
        print("Parent forked out worker process {pid}".format(pid=c.pid))
        children.append(c)
        time.sleep(1)

    print("Forked out {c} workers, hit Ctrl+C to end...".format(c=len(children)))
    while True:
        time.sleep(5)


if __name__ == '__main__':
    parent()

One caveat is that I am not sure if this process works on non-Unix operating systems. It should work on Linux, Mac and other Unixes.

like image 76
shevron Avatar answered Jan 09 '23 02:01

shevron