执行摘要
- 一句话:添加原生 gRPC sidecar 模块启动器
- 推荐动作:值得精读,尤其是
sidecar.py 中进程生命周期管理的设计与配置校验方式。对于有类似“陪伴进程”需求的系统有参考价值。建议在后续 PR 中增加集成测试覆盖真实进程场景。
功能与动机
移除 Dynamo 特定的服务器契约,使提供商包保持可选,同时保留 SGLang 拥有的子进程监督和生命周期管理。参考 PR body:"Removes the Dynamo-specific server contract while preserving SGLang-owned child supervision and lets provider packages remain optional."
实现拆解
- 新增 sidecar.py 模块 (
python/sglang/srt/entrypoints/sidecar.py):定义 Sidecar 类封装子进程生命周期,提供 start/stop 方法;实现 start_sidecar 入口函数解析参数、构建端点、启动进程并注册看门狗。
- 扩展 ServerArgs 配置 (
python/sglang/srt/server_args.py):添加 sidecar(模块名)和 sidecar_args(JSON 数组参数)字段;在 _handle_deprecated_args 中增加校验:要求原生 gRPC 端口(--grpc-port)、拒绝与旧 SMG gRPC 模式共存、拒绝空模块名,并校验 sidecar_args 格式。
- 集成到 HTTP 服务器生命周期 (
python/sglang/srt/entrypoints/http_server.py):在 lifespan 的 try 块中、原生 gRPC 服务器启动后,检查 server_args.sidecar 并调用 start_sidecar;在 finally 块中优先停止 sidecar 再关闭 gRPC 服务器,若停止失败仅记录日志。
- 配套单元测试 (
test/registered/unit/server_args/test_server_args.py):添加 7 个测试覆盖端点构建、参数解析、进程启动参数传递、配置验证(需要原生 gRPC、拒绝旧模式、拒绝空值)以及环境变量注入。
关键文件:
python/sglang/srt/entrypoints/sidecar.py(模块 侧车管理;类别 source;类型 core-logic;符号 _loopback_host, build_sidecar_endpoint, _parse_sidecar_args, _run_sidecar): 核心新增模块,实现 sidecar 生命周期管理:进程生成、端点和参数解析、看门狗、停止清理。
test/registered/unit/server_args/test_server_args.py(模块 参数测试;类别 test;类型 test-coverage;符号 _sidecar_parser, test_sidecar_builds_loopback_grpc_endpoints, test_sidecar_args_parse_as_exact_json_argv, test_start_sidecar_passes_endpoint_and_provider_argv_separately): 为 sidecar 添加了 7 个单元测试,覆盖端点构建、参数解析、进程启动、验证逻辑(需要原生 gRPC、拒绝旧模式、拒绝空值)和环境变量注入。
python/sglang/srt/server_args.py(模块 配置参数;类别 source;类型 core-logic;符号 sidecar, sidecar_args, _handle_deprecated_args): 添加了 sidecar 和 sidecar_args 字段定义,以及在 _handle_deprecated_args 中添加验证逻辑。
python/sglang/srt/entrypoints/http_server.py(模块 HTTP服务器;类别 source;类型 dependency-wiring): 在 lifespan 中集成 sidecar 启动与停止,确保 sidecar 在原生 gRPC 启动后启动、在服务关闭时停止。
关键符号:_loopback_host, build_sidecar_endpoint, _parse_sidecar_args, _run_sidecar, Sidecar.init, Sidecar.start, Sidecar.stop, start_sidecar, ServerArgs._handle_deprecated_args
关键源码片段
python/sglang/srt/entrypoints/sidecar.py
核心新增模块,实现 sidecar 生命周期管理:进程生成、端点和参数解析、看门狗、停止清理。
import os
import multiprocessing as mp
from sglang.srt.utils.common import kill_itself_when_parent_died, kill_process_tree
from sglang.srt.utils.network import NetworkAddress
from sglang.srt.utils.watchdog import SubprocessWatchdog
SGLANG_GRPC_ENDPOINT_ENV = "SGLANG_GRPC_ENDPOINT"
def start_sidecar(server_args) -> Sidecar:
# 获取 sidecar 模块名(由 --sidecar 指定)
module_name = server_args.sidecar
assert module_name is not None
# 解析 sidecar 专属参数(provider 参数列表、shutdown 超时)
sidecar_args, shutdown_timeout = _parse_sidecar_args(server_args.sidecar_args)
# 构建本地 gRPC 端点(自动将 0.0.0.0 / :: 转为 loopback)
endpoint = build_sidecar_endpoint(server_args)
# 创建子进程,target 为 _run_sidecar(在子进程中执行)
proc = mp.get_context("spawn").Process(
name=f"sglang_sidecar_{module_name}",
target=_run_sidecar,
args=(module_name, sidecar_args, endpoint),
)
sidecar = Sidecar(proc, module_name, shutdown_timeout=shutdown_timeout)
sidecar.start()
return sidecar
class Sidecar:
def __init__(self, proc, module_name: str, shutdown_timeout: float):
self.proc = proc
self.module_name = module_name
self.shutdown_timeout = shutdown_timeout
# 启动子进程状态看门狗,当进程意外终止时记录日志
self._watchdog = SubprocessWatchdog(
processes=[proc], process_names=[module_name]
)
def start(self) -> None:
self.proc.start()
self._watchdog.start()
logger.info("Sidecar module %s started pid=%s", self.module_name, self.proc.pid)
def stop(self) -> None:
self._watchdog.stop()
if self.proc.is_alive():
self.proc.terminate()
self.proc.join(timeout=self.shutdown_timeout)
else:
self.proc.join(timeout=0)
# 若进程未在超时内终止,则强制杀死进程树
if self.proc.is_alive():
logger.warning("Sidecar module did not terminate; killing process tree")
kill_process_tree(self.proc.pid, wait_timeout=self.shutdown_timeout)
test/registered/unit/server_args/test_server_args.py
为 sidecar 添加了 7 个单元测试,覆盖端点构建、参数解析、进程启动、验证逻辑(需要原生 gRPC、拒绝旧模式、拒绝空值)和环境变量注入。
from sglang.srt.entrypoints.sidecar import (
SGLANG_GRPC_ENDPOINT_ENV,
Sidecar,
_run_sidecar,
build_sidecar_endpoint,
start_sidecar,
)
class TestGrpcServerArgs(CustomTestCase):
@staticmethod
def _sidecar_parser():
# 使用与真实 ServerArgs 相同的 CLI 解析器
parser = server_args_module.argparse.ArgumentParser()
ServerArgs.add_cli_args(parser)
return parser
def test_sidecar_builds_loopback_grpc_endpoints(self):
# 验证 0.0.0.0 和 IPv6 通配符被转换为 loopback 地址
self.assertEqual(
build_sidecar_endpoint(SimpleNamespace(host="0.0.0.0", grpc_port=50051)),
"http://127.0.0.1:50051",
)
self.assertEqual(
build_sidecar_endpoint(SimpleNamespace(host="::", grpc_port=50051)),
"http://[::1]:50051",
)
def test_sidecar_requires_native_grpc(self):
# 当未设置 --grpc-port 或 SGLANG_GRPC_PORT 时,sidecar 必须报错
sa = self._args(sidecar="example.sidecar")
with self.assertRaisesRegex(ValueError, "requires --grpc-port"):
sa._handle_deprecated_args()
def test_sidecar_rejects_empty_value(self):
# 空模块名必须被拒绝
sa = self._args(sidecar="", grpc_port=50051)
with self.assertRaisesRegex(ValueError, "must not be empty"):
sa._handle_deprecated_args()
评论区精华
Review 中主要讨论了测试量问题。@ishandhanani 多次指出测试过于冗余("Can greatly simplify this. AI slop test","way too many ai slop tests. only keep the important/impactful ones"),@connorcarpenter15 也认为许多测试不必要。最终在提交历史中看到 "test: trim sidecar coverage" 进行裁剪。另外 @ishandhanani 还提出了代码格式细节(年份范围、等号注释、参数格式等),均已修正。
- 测试覆盖范围与精简 (testing): 测试被修剪,提交历史中出现 'test: trim sidecar coverage'。最终保留 7 个关键测试。
风险与影响
关联脉络
参与讨论