Prhub

#31076 feat: add native gRPC sidecar module launcher

原始 PR 作者 ishandhanani 合并时间 2026-07-22 21:39 文件变更 4 提交数 7 评论 19 代码增减 +309 / -0

执行摘要

添加原生 gRPC sidecar 模块启动器

移除 Dynamo 特定的服务器契约,使提供商包保持可选,同时保留 SGLang 拥有的子进程监督和生命周期管理。参考 PR body:"Removes the Dynamo-specific server contract while preserving SGLang-owned child supervision and lets provider packages remain optional."

值得精读,尤其是 sidecar.py 中进程生命周期管理的设计与配置校验方式。对于有类似“陪伴进程”需求的系统有参考价值。建议在后续 PR 中增加集成测试覆盖真实进程场景。

讨论亮点

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 还提出了代码格式细节(年份范围、等号注释、参数格式等),均已修正。

实现拆解

  1. 新增 sidecar.py 模块 (python/sglang/srt/entrypoints/sidecar.py):定义 Sidecar 类封装子进程生命周期,提供 start/stop 方法;实现 start_sidecar 入口函数解析参数、构建端点、启动进程并注册看门狗。
  2. 扩展 ServerArgs 配置 (python/sglang/srt/server_args.py):添加 sidecar(模块名)和 sidecar_args(JSON 数组参数)字段;在 _handle_deprecated_args 中增加校验:要求原生 gRPC 端口(--grpc-port)、拒绝与旧 SMG gRPC 模式共存、拒绝空模块名,并校验 sidecar_args 格式。
  3. 集成到 HTTP 服务器生命周期 (python/sglang/srt/entrypoints/http_server.py):在 lifespantry 块中、原生 gRPC 服务器启动后,检查 server_args.sidecar 并调用 start_sidecar;在 finally 块中优先停止 sidecar 再关闭 gRPC 服务器,若停止失败仅记录日志。
  4. 配套单元测试 (test/registered/unit/server_args/test_server_args.py):添加 7 个测试覆盖端点构建、参数解析、进程启动参数传递、配置验证(需要原生 gRPC、拒绝旧模式、拒绝空值)以及环境变量注入。
文件 模块 状态 重要度
python/sglang/srt/entrypoints/sidecar.py 侧车管理 added 8.77
test/registered/unit/server_args/test_server_args.py 参数测试 modified 7.31
python/sglang/srt/server_args.py 配置参数 modified 6.49
python/sglang/srt/entrypoints/http_server.py HTTP 服务器 modified 5.68

关键符号

_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 core-logic

核心新增模块,实现 sidecar 生命周期管理:进程生成、端点和参数解析、看门狗、停止清理。

import os
import multiprocessing as mpfrom 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 SubprocessWatchdogSGLANG_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 sidecarclass 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 test-coverage

为 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()

评论区精华

测试覆盖范围与精简 测试

@ishandhanani 多次评论测试过于冗余('Can greatly simplify this. AI slop test', 'way too many ai slop tests. only keep the important/impactful ones'),@connorcarpenter15 也认为许多测试不必要('I think a lot of the added tests aren\'t strictly necessary')。

结论:测试被修剪,提交历史中出现 'test: trim sidecar coverage'。最终保留 7 个关键测试。 · 已解决

风险与影响

  1. 进程管理依赖:sidecar 依赖 multiprocessing 启动子进程,若操作系统不支持 fork/spawn 可能异常,但 SGLang 已使用 spawn 上下文并依赖 kill_itself_when_parent_died,理论上跨平台风险可控。
  2. 模块导入与调用:sidecar 模块在子进程中导入,要求模块包含可调用的 main(argv),若模块不存在或接口不匹配会抛出 RuntimeError 并终止子进程(被 watchdog 捕获),不影响主进程。
  3. 与原生 gRPC 强绑定:sidecar 依赖 --grpc-port 开启的原生 gRPC 服务器,若用户未提供则启动失败,配置校验已涵盖。
  4. 测试覆盖局限:测试主要使用 mock 而非真正启动子进程,可能遗漏进程间通信或环境变量隔离的真实问题。

用户影响:引入可选 --sidecar <module> 参数,不影响现有启动方式;提供商可通过新接口注册陪伴进程。
影响范围:仅涉及 SGLang 运行时启动流程,不改变核心推理或数据传输路径。
团队影响:降低了 Dynamo 集成耦合度,便于未来支持更多提供商。

新增进程管理依赖 sidecar 模块必须导出 main 与原生 gRPC 强绑定 测试主要依赖 mock 而非真实进程

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论