Skip to content

Exception when running uvicorn in subprocess #1960

Open
@jonathan-boudreau-work

Description

@jonathan-boudreau-work

Describe the bug
When trying to run uvicorn in a subprocess (using multiprocessing) an exception is thrown in that subprocess after you terminate it.

To Reproduce

versions
python version: 3.12.3
uvicorn version: 0.34.2
fastapi version: 0.115.12
pytest version: 8.3.5
coverage: 7.8.0

.coveragerc

[run]
concurrency = multiprocessing
parallel = true
sigterm = true
debug = multiproc
debug_file = /tmp/coverage.log

sample app

from fastapi import FastAPI

app = FastAPI()

@app.get("/items")
async def get_items_list():
    return [
        { "id": index, "name": f"item {id}" }
        for index in range(10)
    ]

sample test file

import coverage
from multiprocessing import Process
from os import environ
from app.app import app
import time
import httpx
import uvicorn


def run_app() -> None:
    coverage.process_startup()
    print('the app is run', environ, app)
    uvicorn.run(app, host="127.0.0.1", port=9991)


def test_app() -> None:
    process = Process(target=run_app)
    print('starting')
    process.start()
    time.sleep(.1)
    print('terminating')
    with httpx.Client() as client:
        response = client.get('http://localhost:9991/items')
        print('response', response.text)
    process.terminate()

Command to run:

poetry run coverage run -m pytest --capture=no

Exception thrown:

Process Process-1:
Traceback (most recent call last):
  File "/usr/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/usr/lib/python3.12/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/home/aghost-7/workspace/sample-subprocess/test/test_app.py", line 13, in run_app
    uvicorn.run(app, host="127.0.0.1", port=9991)
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/uvicorn/main.py", line 580, in run
    server.run()
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/uvicorn/server.py", line 66, in run
    return asyncio.run(self.serve(sockets=sockets))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/uvicorn/server.py", line 69, in serve
    with self.capture_signals():
  File "/usr/lib/python3.12/contextlib.py", line 144, in __exit__
    next(self.gen)
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/uvicorn/server.py", line 330, in capture_signals
    signal.raise_signal(captured_signal)
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/coverage/control.py", line 722, in _on_sigterm
    os.kill(os.getpid(), signal.SIGTERM)                # pragma: not covered
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/coverage/control.py", line 718, in _on_sigterm
    self._atexit("sigterm")
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/coverage/control.py", line 712, in _atexit
    self.stop()
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/coverage/control.py", line 691, in stop
    self._collector.stop()
  File "/home/aghost-7/.cache/pypoetry/virtualenvs/sample-subprocess-vy0N4M8T-py3.12/lib/python3.12/site-packages/coverage/collector.py", line 348, in stop
    assert self._collectors
           ^^^^^^^^^^^^^^^^
AssertionError

Expected behavior
I expect there to be no exception in the subprocess.

Additional context
There might be a conflict with uvicorn and coverage since they both attach to the global signal handlers. You can see this in uvicorn here: https://github.com/encode/uvicorn/blob/master/uvicorn/server.py#L313-L330

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions