mirror of
https://github.com/vllm-project/vllm.git
synced 2026-10-28 09:45:26 +08:00
[Bugfix][Frontend] Stop SSL certificate watchers when HTTP serving exits (#55699)
Signed-off-by: git-jxj <65210887+git-jxj@users.noreply.github.com> Co-authored-by: OpenAI Codex <noreply@openai.com>
This commit is contained in:
1 parent
a2ecc8ac4d
commit
01b05cd69d
2 files changed
+75
-8
No files matched your search
@@ -4,9 +4,12 @@ import asyncio
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from ssl import SSLContext
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm.entrypoints.launchers import launcher
|
||||
from vllm.entrypoints.launchers.utils.ssl import SSLCertRefresher
|
||||
|
||||
|
||||
@@ -100,3 +103,66 @@ async def test_ssl_refresher():
|
||||
await asyncio.sleep(1)
|
||||
assert ssl_context.load_cert_chain_count == cert_chain_count
|
||||
assert ssl_context.load_ca_count == ca_count
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("failure_stage", [None, "serve", "signal", "shutdown_task"])
|
||||
async def test_serve_http_stops_ssl_refresher_when_server_exits(
|
||||
monkeypatch, failure_stage
|
||||
):
|
||||
config = SimpleNamespace(
|
||||
ssl=object(),
|
||||
ssl_keyfile="key.pem",
|
||||
ssl_certfile="cert.pem",
|
||||
ssl_ca_certs="ca.pem",
|
||||
load=MagicMock(),
|
||||
)
|
||||
server_error = RuntimeError("server failed") if failure_stage == "serve" else None
|
||||
server = SimpleNamespace(serve=AsyncMock(side_effect=server_error))
|
||||
ssl_cert_refresher = MagicMock()
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
def add_signal_handler(*_args):
|
||||
if failure_stage == "signal":
|
||||
raise RuntimeError("signal failed")
|
||||
|
||||
create_task = loop.create_task
|
||||
|
||||
def create_task_or_fail(coro, **kwargs):
|
||||
if (
|
||||
failure_stage == "shutdown_task"
|
||||
and coro.cr_code.co_name == "handle_shutdown"
|
||||
):
|
||||
coro.close()
|
||||
raise RuntimeError("shutdown_task failed")
|
||||
return create_task(coro, **kwargs)
|
||||
|
||||
monkeypatch.setattr(loop, "add_signal_handler", add_signal_handler)
|
||||
monkeypatch.setattr(loop, "create_task", create_task_or_fail)
|
||||
monkeypatch.setattr(launcher.uvicorn, "Config", lambda *_args, **_kwargs: config)
|
||||
monkeypatch.setattr(launcher, "NoSignalServer", lambda _config: server)
|
||||
monkeypatch.setattr(
|
||||
launcher,
|
||||
"SSLCertRefresher",
|
||||
lambda **_kwargs: ssl_cert_refresher,
|
||||
)
|
||||
monkeypatch.setattr(launcher, "watchdog_loop", AsyncMock())
|
||||
app = SimpleNamespace(routes=[], state=SimpleNamespace(engine_client=object()))
|
||||
|
||||
shutdown = None
|
||||
try:
|
||||
if failure_stage is None:
|
||||
shutdown = await launcher.serve_http(
|
||||
app, sock=None, enable_ssl_refresh=True, port=8000
|
||||
)
|
||||
else:
|
||||
error = "server" if failure_stage == "serve" else failure_stage
|
||||
with pytest.raises(RuntimeError, match=f"{error} failed"):
|
||||
await launcher.serve_http(
|
||||
app, sock=None, enable_ssl_refresh=True, port=8000
|
||||
)
|
||||
ssl_cert_refresher.stop.assert_called_once_with()
|
||||
finally:
|
||||
if shutdown is not None:
|
||||
await shutdown
|
||||
await asyncio.sleep(0)
|
||||
@@ -259,9 +259,6 @@ async def serve_http(
|
||||
async def dummy_shutdown() -> None:
|
||||
pass
|
||||
|
||||
loop.add_signal_handler(signal.SIGINT, signal_handler)
|
||||
loop.add_signal_handler(signal.SIGTERM, signal_handler)
|
||||
|
||||
async def handle_shutdown() -> None:
|
||||
await shutdown_event.wait()
|
||||
|
||||
@@ -285,12 +282,13 @@ async def serve_http(
|
||||
server_task.cancel()
|
||||
if watchdog_task is not None:
|
||||
watchdog_task.cancel()
|
||||
if ssl_cert_refresher:
|
||||
ssl_cert_refresher.stop()
|
||||
|
||||
shutdown_task = loop.create_task(handle_shutdown())
|
||||
|
||||
shutdown_task = None
|
||||
try:
|
||||
loop.add_signal_handler(signal.SIGINT, signal_handler)
|
||||
loop.add_signal_handler(signal.SIGTERM, signal_handler)
|
||||
shutdown_task = loop.create_task(handle_shutdown())
|
||||
|
||||
await server_task
|
||||
return dummy_shutdown()
|
||||
except asyncio.CancelledError:
|
||||
@@ -306,9 +304,12 @@ async def serve_http(
|
||||
logger.info_once("[shutdown] API server: shutting down FastAPI HTTP server")
|
||||
return server.shutdown()
|
||||
finally:
|
||||
shutdown_task.cancel()
|
||||
if shutdown_task is not None:
|
||||
shutdown_task.cancel()
|
||||
if watchdog_task is not None:
|
||||
watchdog_task.cancel()
|
||||
if ssl_cert_refresher:
|
||||
ssl_cert_refresher.stop()
|
||||
|
||||
|
||||
async def watchdog_loop(server: uvicorn.Server, engine: EngineClient):
|
||||
|
||||
Reference in new issue
Block a user