fastapi: FastAPI gets terminated when child multiprocessing process terminated

Describe the bug

Make a multiprocessing Process and start it. Right after terminate the process, fastapi itself(parent) terminated.

To Reproduce

Start command: /usr/local/bin/uvicorn worker.stts_api:app --host 127.0.0.1 --port 8445

  1. Create a file with:
from fastapi import FastAPI

app = FastAPI()


@app.post('/task/run')
def task_run(task_config: TaskOptionBody):
    proc = multiprocessing.Process(
        target=task.run,
        args=(xxxx,))
    proc.start()
    return task_id

@app.get('/task/abort')
def task_abort(task_id: str):
    proc.terminate()
    return result_OK
  1. Run task_run and while the process alive, trigger task_abort
  2. After child process terminated then parent(fastApi) terminated as well.

Expected behavior

Parent process should not be terminated after child terminated.

Environment

  • OS: Linux
  • FastAPI Version 0.54.1
  • Python version 3.8.2

Additional context

I tried same code with Flask with gunicorn, it never terminated.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 6
  • Comments: 23 (9 by maintainers)

Most upvoted comments

Hi, I have discovered this situation and came to the next conclusions:

  1. Uvicorn register signals handlers and child process inherit them (but also inherit ThreadPoolExecutor and another resources)
  2. You cannot set signals handlers not from the main thread
  3. The task function will be executed in the ThreadPoolExecutor, so as I say early - you cannot change signal handlers in this function;

The second and third conclusions is not true, the real problem was founded and described below.

But it still possible to solve this problem (without changing FastAPI or uvicorn) - you can change start_method for multiprocessing to spawn method and your child process will be clear (without inherited signals handles, thread pools and other stuff).

@app.post('/task/run')
def task_run():
    multiprocessing.set_start_method('spawn')
    proc = multiprocessing.Process(
        target=task,
        args=(10,))
    proc.start()
    
    return proc.pid

It’s work for me (python3.7, macOS 10.15.5)

@victorphoenix3 It seems like your process need to have some long running code. Just try to add “time.sleep(30)” and try to abort this within the time.

I tried your code and there is no issue. (Because the subprocess already terminated…?) 1 21742 INFO: 127.0.0.1:52778 - “POST /task/run HTTP/1.1” 200 OK INFO: 127.0.0.1:52780 - “GET /task/abort?pid=21742 HTTP/1.1” 200 OK

But after I adding “sleep 30 seconds”, and the issue comes. 1 21982 INFO: 127.0.0.1:52802 - “POST /task/run HTTP/1.1” 200 OK INFO: 127.0.0.1:52804 - “GET /task/abort?pid=21982 HTTP/1.1” 200 OK INFO: Shutting down INFO: Waiting for application shutdown. INFO: Application shutdown complete. INFO: Finished server process [21973]

@tiangolo I think you can close this, because this is not related to the FastApi or uvicorn – this is a specific behaviour of signal handling in asyncio.

To avoid RuntimeError('context has already been set') when set_spawn_method is called multiple times within a route, I moved the call into FastAPI’s startup event handler so it is only called once, as prescribed in the stdlib docs. This solved this issue for me.

import multiprocessing

...
app = FastAPI()


@app.on_event("startup")
def startup_event() -> None:
    multiprocessing.set_start_method("spawn")