Unclosed socket due to spawn child processes using multiprocessing

Question:

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

Asked By: Xavatu

||

Answers:

So my only decision (WORKAROUND) is to set timeout_keep_alive uvicorn param onto zero and using BackgroundTask from Fastapi.
The socket closes immediately after sending the response, but processes spawns after sending response.

Answered By: Xavatu

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

Answered By: Xavatu