Open
Description
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