How to double fork from a Python web server without leaving a zombie process?

时间:2018-07-25 04:35:30

标签: python python-3.6

Python 3.6

I have a simple Python web server that, when it receives a POST request, spawns a netcat process to listen on a port.

Seems to work OK except that a zombie is left following the spawn.

(Server code below)

I send a POST request to the server like so:

curl -X POST -H "Content-Type: text/plain" --data "example data"  localhost:8000

After that I do a ps ax to see the processes running and the netcat process is there, the web server is there, AND a zombie is there.

6873 pts/0    S+     0:00 python3 nc_server.py
6876 ?        Zs     0:00 [python3] <defunct>
6877 ?        S      0:00 nc -l 54927

Why? How can I avoid the zombie?

from http.server import HTTPServer, BaseHTTPRequestHandler
import socket
import os
import io

host = '0.0.0.0'
port = 8000

def find_free_port():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(('', 0))
        return s.getsockname()[1]

def spawn_netcat(port):
    command = "/bin/nc"
    params = f"nc -l {port}"
    spawnDaemon(command, params.split())

def spawnDaemon(path_to_executable, params):

    # Do first fork
    pid = os.fork()
    if (pid != 0):
        return

    # Decouple from parent environment
    os.chdir("/opt")
    os.setsid()
    os.umask(0)

    # Do second fork
    pid = os.fork()
    if (pid != 0):
        os._exit(0)

    # exec this process into netcat.....
    os.execv(path_to_executable, params)

class Server(BaseHTTPRequestHandler):

    def do_POST(self):
        netcat_listen_port = find_free_port()
        spawn_netcat(netcat_listen_port)
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        response = io.BytesIO()
        response.write(str(netcat_listen_port).encode())
        self.wfile.write(response.getvalue())

if __name__ == "__main__":
    httpd = HTTPServer((host, port), Server)
    httpd.serve_forever()

1 个答案:

答案 0 :(得分:2)

当进程终止时,它期望其返回代码由其父级收集。在此之前,它不死(僵尸)地坐在那里,不再运行,但无法从进程表中清除。如果父级不再存在,则将重新建立一个进程的父进程到init(或至少在某些启用了systemd的系统IIRC上使用专用的收割进程)。在您的进程列表中,您会看到第一个fork的子代,它的父代周围,但未收集返回状态。

长话短说,在第一个fork()之后,您可以在父流程的return之前插入它:

os.waitid(os.P_PID, pid, os.WEXITED)

即:

# Do first fork
pid = os.fork()
if (pid != 0):
    os.waitid(os.P_PID, pid, os.WEXITED)
    return

这个孩子只有短暂的生命并且退出。在返回函数之前,它等待它这样做。在这种情况下应该没问题。否则,通常,如果您的进程产生了可能在以后任何时候退出的子级,则您将注册一个SIGCHLD处理程序来处理此问题。即使除了双叉守护程序之外,您通常也将使用Popen并使用subprocess与您的进程进行交互。