[Frontend] Disable uvicorn signal handlers instead of racing them (#50916)
Signed-off-by: Nick Hill <nickhill123@gmail.com>
Nick Hill committed
Aug 7, 2026 at 12:40 UTC
ac70ce96e0b9f69dd834bd1b0cd2d2b4c4a9db46
3 files changed
+18
-15
tests/entrypoints/openai/test_dp_supervisor.py
+1
-1
@@ -312,7 +312,7 @@ async def test_shutdown_if_supervisor_server_error_on_startup(
312
await self._shutdown_event.wait()
313
314
monkeypatch.setattr(dp_sup.asyncio, "get_running_loop", lambda: FakeLoop())
315
- monkeypatch.setattr(dp_sup.uvicorn, "Server", FakeServer)
315
+ monkeypatch.setattr(dp_sup, "NoSignalServer", FakeServer)
316
monkeypatch.setattr(DPSupervisor, "_shutdown_children", fake_shutdown_children)
317
monkeypatch.setattr(DPSupervisor, "_start_children", fake_start_children)
318
monkeypatch.setattr(DPSupervisor, "_monitor_children", fake_monitor_children)
vllm/entrypoints/launcher.py
+15
-13
@@ -2,8 +2,10 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import asyncio
5
+import contextlib
6
import signal
7
import socket
8
+from collections.abc import Generator
9
from functools import partial
10
from typing import Any
11
@@ -23,6 +25,18 @@ from vllm.utils.network_utils import find_process_using_port
25
logger = init_logger(__name__)
26
27
28
+class NoSignalServer(uvicorn.Server):
29
+ """Uvicorn server that never installs its own SIGINT/SIGTERM handlers.
30
+
31
+ Callers register their own handlers on the event loop for graceful
32
+ shutdown; uvicorn's would race with and override them (see #49668).
33
+ """
34
+
35
+ @contextlib.contextmanager
36
+ def capture_signals(self) -> Generator[None, None, None]:
37
+ yield
38
+
39
+
40
async def serve_http(
41
app: FastAPI,
42
sock: socket.socket | None,
@@ -73,7 +87,7 @@ async def serve_http(
87
config.h11_max_incomplete_event_size = h11_max_incomplete_event_size
88
config.h11_max_header_count = h11_max_header_count
89
config.load()
76
- server = uvicorn.Server(config)
90
+ server = NoSignalServer(config)
91
app.state.server = server
92
93
loop = asyncio.get_running_loop()
@@ -92,18 +106,6 @@ async def serve_http(
106
)
107
)
108
95
- # make sure uvicorn server signal handler has been registered.
96
- # Otherwise the app signal handler below will be overwritten in server.serve.
97
- # server.started is set to True after server complete server.startup.
98
- # We need to constraint the time sequence.
99
- logger.info("API server: waiting for HTTP server to start")
100
- while not server.started and not server_task.done():
101
- await asyncio.sleep(0.1)
102
- if server_task.done():
103
- # Propagate startup failure (e.g. port bind error) instead of hanging.
104
- await server_task
105
- logger.info("API server: HTTP server started")
106
-
109
shutdown_event = asyncio.Event()
110
111
def signal_handler() -> None:
vllm/entrypoints/openai/dp_supervisor.py
+2
-1
@@ -23,6 +23,7 @@ import uvloop
23
from fastapi import FastAPI, Response
24
25
import vllm.envs as envs
26
+from vllm.entrypoints.launcher import NoSignalServer
27
from vllm.logger import init_logger
28
from vllm.utils.system_utils import (
29
decorate_logs,
@@ -335,7 +336,7 @@ class DPSupervisor:
336
ssl_cert_reqs=self.args.ssl_cert_reqs,
337
ssl_ciphers=self.args.ssl_ciphers,
338
)
338
- supervisor_server = uvicorn.Server(config)
339
+ supervisor_server = NoSignalServer(config)
340
supervisor_server_task = asyncio.create_task(
341
supervisor_server.serve(),
342
name="dp-supervisor",