Search code examples
pythonsocketspython-multiprocessing

Unclosed socket due to spawn child processes using multiprocessing


I don't really understand what's going on with http requests. When I started child processes and uvicorn timeout_keep_alive timed out, I tried hitting "stop" in the browser and got infinite loading and no HTTP connection log. But if I try to click other buttons or refresh the page, it works and I get two responses.

Netstat shows that socket bind on port 60862 is opened, but uvicorn log:

TRACE: 127.0.0.1:60862 - HTTP connection lost.

import multiprocessing
import os
import time

import uvicorn
from fastapi import FastAPI

app = FastAPI()
processes = []


def keep_alive_process():
    while True:
        print(f"process {os.getpid()} is alive")
        time.sleep(1)


@app.post("/start")
async def start_processes():

    for i in range(4):
        process = multiprocessing.Process(target=keep_alive_process,
                                          args=())
        processes.append(process)
        process.start()

    return {'status': 'started'}


@app.post("/stop")
async def stop_processes():

    for process in processes:
        process.kill()
    processes.clear()

    return {'status': 'stopped'}


if __name__ == '__main__':
    uvicorn.run('main:app', timeout_keep_alive=10, log_level='trace')
TRACE:    127.0.0.1:60862 - HTTP connection made
TRACE:    127.0.0.1:60862 - ASGI [2] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8010), 'client': ('127.0.0.1', 60862), 'scheme': 'http', 'root_path': '', 'headers': '<...>', 'method': 'POST', 'path': '/start', 'raw_path': b'/start', 'query_string': b''}
TRACE:    127.0.0.1:60862 - ASGI [2] Send {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
TRACE:    127.0.0.1:60862 - ASGI [2] Send {'type': 'http.response.body', 'body': '<20 bytes>'}
TRACE:    127.0.0.1:60862 - ASGI [2] Completed
process 63912 is alive
process 63913 is alive
process 63914 is alive
process 63915 is alive
INFO:     127.0.0.1:60862 - "POST /start HTTP/1.1" 200 OK
process 63912 is alive
process 63913 is alive
TRACE:    127.0.0.1:60862 - HTTP connection lost

....

process 63912 is alive
INFO:     127.0.0.1:59092 - "POST /stop HTTP/1.1" 200 OK
INFO:     127.0.0.1:59092 - "POST /stop HTTP/1.1" 200 OK
TRACE:    127.0.0.1:59092 - HTTP connection made
TRACE:    127.0.0.1:59092 - ASGI [3] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8010), 'client': ('127.0.0.1', 59092), 'scheme': 'http', 'root_path': '', 'headers': '<...>', 'method': 'POST', 'path': '/stop', 'raw_path': b'/stop', 'query_string': b''}
TRACE:    127.0.0.1:59092 - ASGI [3] Send {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
TRACE:    127.0.0.1:59092 - ASGI [3] Send {'type': 'http.response.body', 'body': '<20 bytes>'}
TRACE:    127.0.0.1:59092 - ASGI [3] Completed
TRACE:    127.0.0.1:59092 - ASGI [4] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8010), 'client': ('127.0.0.1', 59092), 'scheme': 'http', 'root_path': '', 'headers': '<...>', 'method': 'POST', 'path': '/stop', 'raw_path': b'/stop', 'query_string': b''}
TRACE:    127.0.0.1:59092 - ASGI [4] Send {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
TRACE:    127.0.0.1:59092 - ASGI [4] Send {'type': 'http.response.body', 'body': '<20 bytes>'}
TRACE:    127.0.0.1:59092 - ASGI [4] Completed
TRACE:    127.0.0.1:59092 - HTTP connection lost

Swagger image


Solution

  • The multiprocessing module uses the process forking technique, in which file and socket descriptors are inherited by child processes as usual. Therefore, the child process will use the same connected socket as the parent process.

    See https://mvblog.ru/zavershenie-dochernix-processov.html