Files
sgl-project--sglang/python/sglang/test/scripted_runtime/http_server.py
T
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

261 lines
8.6 KiB
Python

from __future__ import annotations
import multiprocessing as mp
import os
import tempfile
import time
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Tuple
import requests
import zmq
from sglang.srt.entrypoints.http_server import launch_server
from sglang.srt.environ import envs
from sglang.srt.managers.io_struct import sock_recv, sock_send, wrap_as_pickle
from sglang.srt.server_args import ServerArgs
from sglang.srt.utils.network import get_free_port, get_zmq_socket_on_host
from sglang.test.scripted_runtime.io_struct import (
HookReady,
OutOfBandError,
RunScript,
ScriptFailed,
ScriptSucceeded,
Shutdown,
)
from sglang.test.scripted_runtime.utils import close_zmq_socket
DEFAULT_RUN_TIMEOUT_S: float = 120.0
SHUTDOWN_JOIN_TIMEOUT_S: float = 60.0
LISTENER_ACCEPT_TIMEOUT_S: float = 300.0
HTTP_READY_TIMEOUT_S: float = 300.0
HTTP_READY_POLL_INTERVAL_S: float = 0.5
SERVER_HOST: str = "127.0.0.1"
class ScriptedHttpServer:
def __init__(
self,
*,
ctx: zmq.Context,
socket: zmq.Socket,
server_process: mp.process.BaseProcess,
out_of_band_error_path: Path,
http_port: int,
) -> None:
self._ctx = ctx
self._socket = socket
self._server_process = server_process
self._out_of_band_error_path = out_of_band_error_path
self._base_url = f"http://{SERVER_HOST}:{http_port}"
self._shutdown_done = False
self._dirty: Optional[str] = None
@classmethod
def start(cls, **engine_kwargs: Any) -> ScriptedHttpServer:
out_of_band_error_path = _create_oob_error_file()
ctx = zmq.Context()
dispatch_port, socket = get_zmq_socket_on_host(ctx, zmq.PAIR, host=SERVER_HOST)
server_process, http_port = _spawn_server_process(
endpoint=f"tcp://{SERVER_HOST}:{dispatch_port}",
out_of_band_error_path=out_of_band_error_path,
engine_kwargs=engine_kwargs,
)
self = cls(
ctx=ctx,
socket=socket,
server_process=server_process,
out_of_band_error_path=out_of_band_error_path,
http_port=http_port,
)
try:
self._await_handshake()
self._await_http_ready()
except BaseException:
self._teardown()
raise
return self
def execute_script(
self,
script_fn: Callable,
*,
args: Tuple[Any, ...] = (),
timeout_s: float = DEFAULT_RUN_TIMEOUT_S,
) -> None:
if self._dirty:
raise RuntimeError(f"ScriptedHttpServer is dirty: {self._dirty}")
fn_path = f"{script_fn.__module__}:{script_fn.__qualname__}"
sock_send(self._socket, wrap_as_pickle(RunScript(fn_path=fn_path, args=args)))
if not self._socket.poll(int(timeout_s * 1000)):
if not self._server_process.is_alive():
self._dirty = f"server process died before responding to {fn_path!r}"
raise RuntimeError(self._dirty)
self._dirty = f"script {fn_path!r} timed out after {timeout_s}s"
raise TimeoutError(self._dirty)
reply = sock_recv(self._socket)
match reply:
case ScriptFailed(traceback=tb):
raise AssertionError(f"scripted-runtime script failed:\n{tb}")
case ScriptSucceeded():
return
case _:
raise RuntimeError(
f"scheduler hook replied with unexpected message {reply!r}"
)
def shutdown(self) -> None:
if self._shutdown_done:
return
fatal_error: Optional[OutOfBandError] = None
try:
try:
sock_send(self._socket, wrap_as_pickle(Shutdown()))
except zmq.ZMQError:
pass
self._server_process.join(timeout=SHUTDOWN_JOIN_TIMEOUT_S)
self._terminate_process(self._server_process)
fatal_error = self._read_out_of_band_error()
finally:
self._teardown()
self._shutdown_done = True
if fatal_error:
raise AssertionError(
f"scripted-runtime server failed:\n{fatal_error.traceback}"
)
def _await_handshake(self) -> None:
if not self._socket.poll(int(LISTENER_ACCEPT_TIMEOUT_S * 1000)):
raise TimeoutError(
f"ScriptedHttpServer: HTTP server did not connect within "
f"{LISTENER_ACCEPT_TIMEOUT_S}s"
)
ready = sock_recv(self._socket)
if not isinstance(ready, HookReady):
raise RuntimeError(
f"ScriptedHttpServer: expected HookReady handshake, got {ready!r}"
)
def _await_http_ready(self) -> None:
# HookReady only means the scheduler dispatch loop started; the uvicorn
# entrypoint may not be bound yet. The first script runs
# _reset_engine_state, which POSTs to this server's own HTTP port, so
# block until the port is bound and routing before any script can run.
#
# Poll /model_info rather than /health: once the server reports Up,
# /health runs the generation-based health check (a real probe request
# through the scheduler), which cannot complete while the scheduler
# waits for scripts between RunScript commands. Any /model_info
# response proves the socket is bound and routes are registered, which
# is all the control POSTs need.
url = f"{self._base_url}/model_info"
deadline = time.monotonic() + HTTP_READY_TIMEOUT_S
while time.monotonic() < deadline:
if not self._server_process.is_alive():
raise RuntimeError(
"ScriptedHttpServer: server process died during HTTP startup"
)
try:
requests.get(url, timeout=2.0)
return
except requests.RequestException:
pass
time.sleep(HTTP_READY_POLL_INTERVAL_S)
raise TimeoutError(
f"ScriptedHttpServer: HTTP endpoint {url} not bound within "
f"{HTTP_READY_TIMEOUT_S}s"
)
def _teardown(self) -> None:
try:
close_zmq_socket(self._socket, self._ctx)
except Exception: # noqa: BLE001 — best-effort cleanup
pass
self._terminate_process(self._server_process)
self._cleanup_files()
def _read_out_of_band_error(self) -> Optional[OutOfBandError]:
try:
text = self._out_of_band_error_path.read_text()
except OSError:
return None
text = text.strip()
return OutOfBandError.from_json(text) if text else None
def _cleanup_files(self) -> None:
try:
self._out_of_band_error_path.unlink()
except OSError:
pass
@staticmethod
def _terminate_process(process: mp.process.BaseProcess) -> None:
if not process.is_alive():
return
process.terminate()
process.join(timeout=10.0)
if process.is_alive():
process.kill()
process.join(timeout=10.0)
def _create_oob_error_file() -> Path:
err_fd, err_path = tempfile.mkstemp(
prefix="scripted_runtime_oob_error_", suffix=".json"
)
os.close(err_fd)
return Path(err_path)
def _spawn_server_process(
*,
endpoint: str,
out_of_band_error_path: Path,
engine_kwargs: Dict[str, Any],
) -> Tuple[mp.process.BaseProcess, int]:
mp_ctx = mp.get_context("spawn")
launch_kwargs: Dict[str, Any] = dict(
host=SERVER_HOST,
port=get_free_port(),
kv_canary="raise",
kv_canary_real_data="partial",
kv_canary_sweep_interval=100,
disable_prefill_cuda_graph=True,
)
launch_kwargs.update(engine_kwargs)
http_port = launch_kwargs["port"]
server_process = mp_ctx.Process(
target=_launch_scripted_http_server,
kwargs=launch_kwargs,
name="scripted-runtime-http-server",
daemon=False,
)
sys_path_entry = str(Path(__file__).resolve().parent)
with (
envs.SGLANG_TEST_SCRIPTED_RUNTIME.override(True),
envs.SGLANG_TEST_SCRIPTED_RUNTIME_IPC_ADDR.override(endpoint),
envs.SGLANG_TEST_SCRIPTED_RUNTIME_OUT_OF_BAND_ERROR_PATH.override(
str(out_of_band_error_path)
),
envs.SGLANG_TEST_SCRIPTED_RUNTIME_SYS_PATH_ENTRY.override(sys_path_entry),
):
server_process.start()
return server_process, http_port
def _launch_scripted_http_server(**engine_kwargs: Any) -> None:
launch_server(ServerArgs(**engine_kwargs))