diff --git a/tests/entrypoints/openai/test_dp_supervisor.py b/tests/entrypoints/openai/test_dp_supervisor.py index df80053deb..a31810fe05 100644 --- a/tests/entrypoints/openai/test_dp_supervisor.py +++ b/tests/entrypoints/openai/test_dp_supervisor.py @@ -312,7 +312,7 @@ async def test_shutdown_if_supervisor_server_error_on_startup( await self._shutdown_event.wait() monkeypatch.setattr(dp_sup.asyncio, "get_running_loop", lambda: FakeLoop()) - monkeypatch.setattr(dp_sup.uvicorn, "Server", FakeServer) + monkeypatch.setattr(dp_sup, "NoSignalServer", FakeServer) monkeypatch.setattr(DPSupervisor, "_shutdown_children", fake_shutdown_children) monkeypatch.setattr(DPSupervisor, "_start_children", fake_start_children) monkeypatch.setattr(DPSupervisor, "_monitor_children", fake_monitor_children) diff --git a/vllm/entrypoints/launcher.py b/vllm/entrypoints/launcher.py index 3a903af2b1..80b2105163 100644 --- a/vllm/entrypoints/launcher.py +++ b/vllm/entrypoints/launcher.py @@ -2,8 +2,10 @@ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import asyncio +import contextlib import signal import socket +from collections.abc import Generator from functools import partial from typing import Any @@ -23,6 +25,18 @@ from vllm.utils.network_utils import find_process_using_port logger = init_logger(__name__) +class NoSignalServer(uvicorn.Server): + """Uvicorn server that never installs its own SIGINT/SIGTERM handlers. + + Callers register their own handlers on the event loop for graceful + shutdown; uvicorn's would race with and override them (see #49668). + """ + + @contextlib.contextmanager + def capture_signals(self) -> Generator[None, None, None]: + yield + + async def serve_http( app: FastAPI, sock: socket.socket | None, @@ -73,7 +87,7 @@ async def serve_http( config.h11_max_incomplete_event_size = h11_max_incomplete_event_size config.h11_max_header_count = h11_max_header_count config.load() - server = uvicorn.Server(config) + server = NoSignalServer(config) app.state.server = server loop = asyncio.get_running_loop() @@ -92,18 +106,6 @@ async def serve_http( ) ) - # make sure uvicorn server signal handler has been registered. - # Otherwise the app signal handler below will be overwritten in server.serve. - # server.started is set to True after server complete server.startup. - # We need to constraint the time sequence. - logger.info("API server: waiting for HTTP server to start") - while not server.started and not server_task.done(): - await asyncio.sleep(0.1) - if server_task.done(): - # Propagate startup failure (e.g. port bind error) instead of hanging. - await server_task - logger.info("API server: HTTP server started") - shutdown_event = asyncio.Event() def signal_handler() -> None: diff --git a/vllm/entrypoints/openai/dp_supervisor.py b/vllm/entrypoints/openai/dp_supervisor.py index 8ce6233c1b..4076e1ed32 100644 --- a/vllm/entrypoints/openai/dp_supervisor.py +++ b/vllm/entrypoints/openai/dp_supervisor.py @@ -23,6 +23,7 @@ import uvloop from fastapi import FastAPI, Response import vllm.envs as envs +from vllm.entrypoints.launcher import NoSignalServer from vllm.logger import init_logger from vllm.utils.system_utils import ( decorate_logs, @@ -335,7 +336,7 @@ class DPSupervisor: ssl_cert_reqs=self.args.ssl_cert_reqs, ssl_ciphers=self.args.ssl_ciphers, ) - supervisor_server = uvicorn.Server(config) + supervisor_server = NoSignalServer(config) supervisor_server_task = asyncio.create_task( supervisor_server.serve(), name="dp-supervisor",