Prhub

#26804 Pull test_utils server-launch boilerplate into reusable helpers

原始 PR 作者 fzyzcjy 合并时间 2026-05-31 09:52 文件变更 1 提交数 1 评论 1 代码增减 +48 / -34

执行摘要

抽提炼测试服务器启动子进程的公用逻辑

消除测试工具中服务器启动子进程代码的重复,降低维护成本,并让popen_launch_pd_server获得与_launch_server_process相同的输出捕获能力,便于调试和日志采集。

该PR作为测试基础设施的小幅改进值得合并,但建议在后续迭代中修复_dump函数的异常安全问题,以避免潜在的文件描述符泄漏。

讨论亮点

Review中gemini-code-assist[bot]指出_dump函数中如果写入sink时抛出异常(例如文件描述符已关闭),会导致src.close()被跳过,造成文件描述符泄漏。建议在_dump中使用try...finally确保src.close()一定执行,并对单个sink写入添加try...except防止一个sink失败影响其他sink。该评论未得到回复且PR已合并,说明此问题未被解决,存在潜在风险。

实现拆解

  1. 新增_subprocess_popen_with_outputs函数:位于python/sglang/test/test_utils.py,接受command、env、return_stdout_stderr参数。当return_stdout_stderr为None时,直接以继承stdout/stderr方式启动子进程;否则通过subprocess.PIPE捕获输出,并启动两个daemon线程分别将stdout和stderr tee到return_stdout_stderr元组指定的文件与sys.stdout/sys.stderr。
  2. 重构_launch_server_process:将其原有的子进程创建与输出重定向逻辑替换为调用_subprocess_popen_with_outputs,并传入清理后的环境变量(child_env),保持原有打印CI_OFFLINE日志的行为。
  3. 扩展popen_launch_pd_server:新增return_stdout_stderr可选参数,其内部原本直接调用subprocess.Popen的逻辑改为调用_subprocess_popen_with_outputs,使PD服务器启动也能捕获输出。
  4. 删除重复代码:移除原_launch_server_process中的内联子进程启动与_dump函数定义,统一使用新抽取的公共函数。
文件 模块 状态 重要度
python/sglang/test/test_utils.py 测试工具 modified 6.4

关键符号

_subprocess_popen_with_outputs _dump _launch_server_process popen_launch_pd_server

关键源码片段

python/sglang/test/test_utils.py test-coverage

唯一变更文件,抽取出公共辅助函数并重构了两个服务器启动函数

# python/sglang/test/test_utils.pydef _subprocess_popen_with_outputs(
    command: list,
    env: Optional[dict],
    return_stdout_stderr: Optional[tuple],
) -> subprocess.Popen:
    """启动子进程,可选地通过背景线程 tee stdout/stderr 到 sinks。    当 return_stdout_stderr 为 None 时,子进程直接继承父进程的 stdout/stderr;
    否则捕获输出并将每一行写入 return_stdout_stderr 元组中的文件以及 sys.stdout/sys.stderr。
    """
    if not return_stdout_stderr:
        # 直接继承 stdout/stderr,不捕获
        return subprocess.Popen(command, stdout=None, stderr=None, env=env)
​
    # 捕获输出
    process = subprocess.Popen(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=env,
        text=True,
        bufsize=1,
    )
​
    # 后台线程:从 src 读取每一行,写入所有 sinks
    def _dump(src, sinks):
        for line in iter(src.readline, ""):
            for sink in sinks:
                sink.write(line)
                sink.flush()
        src.close()
​
    # 分别启动 stdout 和 stderr 的 tee 线程
    threading.Thread(
        target=_dump,
        args=(process.stdout, [return_stdout_stderr[0], sys.stdout]),
        daemon=True,
    ).start()
    threading.Thread(
        target=_dump,
        args=(process.stderr, [return_stdout_stderr[1], sys.stderr]),
        daemon=True,
    ).start()
    return process

评论区精华

_dump 函数可能遗漏 src.close 导致文件描述符泄漏 正确性

gemini-code-assist[bot] 指出:如果写入 sink 时抛出异常(例如文件句柄在 teardown 中关闭),_dump 循环提前终止,src.close() 不会执行,导致文件描述符泄漏。建议使用 try...finally 确保关闭,并对单个 sink 写操作添加异常处理。

结论:未在 PR 中得到解决或回复,PR 已合并,风险依然存在。 · unresolved

风险与影响

_dump函数缺少异常保护,若某sink在写日志时关闭(如测试teardown中文件句柄被关闭),将导致文件描述符泄漏,可能影响CI稳定性。此外,改为daemon线程后,子进程退出后线程可能仍短暂运行,但风险较低。

影响范围限于test_utils.py中两个启动服务器的辅助函数。对调用者透明,但popen_launch_pd_server新增了return_stdout_stderr参数,原无该参数的调用不受影响。未来所有测试服务器启动均可复用同一子进程+输出捕获逻辑,减少重复代码。

潜在文件描述符泄漏 异常保护缺失

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论