执行摘要
- 一句话:为 diffusion 服务添加优雅关闭 worker 进程的机制
- 推荐动作:值得精读,尤其是
launch_server.py 中的层级式进程关闭模式(join → terminate → kill)设计和 kill_itself_when_parent_died 的强化实现。该 PR 为 diffusion 服务稳定性做出了重要改进。
功能与动机
在 SGLang Office Hour 7/2 讨论中,用户反馈 Ctrl-C 后后台 scheduler 进程仍然存活,需要手动 kill,有时会残留 GPU 内存占用导致 OOM(PR body 原文:"Ctrl-C often leaves the background scheduler process alive, requiring manual kill and sometimes leaving enough GPU memory occupied to OOM a large model")。本 PR 旨在通过优雅关闭机制解决该问题。
实现拆解
- 在
launch_server.py 中添加关闭辅助函数:新增 _process_names、_join_processes_with_deadline、_terminate_alive_processes、_kill_alive_processes、_request_monolithic_scheduler_shutdown 和 shutdown_scheduler_processes。这些函数实现了带超时的层级式进程关闭:先尝试 join,超时后 terminate,再超时后 kill。
- 在服务入口集成关闭逻辑:在
launch_server 函数的 finally 块以及 pool_disagg_launch_server 和 disagg_role_launch_server 中调用 shutdown_scheduler_processes,确保无论正常退出还是异常中断都能清理进程。
- 重写
utils.py 中的 kill_itself_when_parent_died:移除平台分支,直接使用 Linux prctl(PR_SET_PDEATHSIG, SIGKILL) 并处理错误;如果父进程已死亡(pid 变为 1),则立即自杀,避免 worker 成为孤儿。
- 在
gpu_worker.py 的 run_scheduler_process 入口处调用 kill_itself_when_parent_died:使得每个 worker 进程在启动时绑定父进程死亡信号。
- 添加单元测试文件
test_launch_server_shutdown.py:使用 _FakeProcess 模拟进程行为,测试 monolithic 关闭流程(验证发送了 ShutdownReq,调用了进程的 terminate 和 kill),测试 scheduler 请求失败时仍强制执行关闭,以及非 monolithic 角色不发送关闭请求。
关键文件:
python/sglang/multimodal_gen/runtime/launch_server.py(模块 启动器;类别 source;类型 core-logic;符号 _process_names, _join_processes_with_deadline, _terminate_alive_processes, _kill_alive_processes): 核心实现文件,添加了 shutdown_scheduler_processes 及辅助函数,并在多个服务入口集成关闭逻辑。
python/sglang/multimodal_gen/test/unit/test_launch_server_shutdown.py(模块 测试;类别 test;类型 test-coverage;符号 _FakeProcess, init, join, is_alive): 新增单元测试,覆盖 monolithic 关闭流程、错误路径和 disagg role 跳过场景,使用 FakeProcess 模拟进程行为。
python/sglang/multimodal_gen/utils.py(模块 工具;类别 source;类型 core-logic;符号 kill_itself_when_parent_died): 重写 kill_itself_when_parent_died 函数,使其更健壮并处理 parent 已死亡的情况。
python/sglang/multimodal_gen/runtime/managers/gpu_worker.py(模块 GPU 工作器;类别 source;类型 core-logic;符号 run_scheduler_process): 在 worker 进程执行入口调用 kill_itself_when_parent_died,确保 worker 在父进程死亡时自动退出。
关键符号:_process_names, _join_processes_with_deadline, _terminate_alive_processes, _kill_alive_processes, _run_http_server_process, _request_monolithic_scheduler_shutdown, shutdown_scheduler_processes, kill_itself_when_parent_died, run_scheduler_process
关键源码片段
python/sglang/multimodal_gen/runtime/launch_server.py
核心实现文件,添加了 shutdown_scheduler_processes 及辅助函数,并在多个服务入口集成关闭逻辑。
# 带超时的进程连接,确保不会永久阻塞
def _join_processes_with_deadline(processes, timeout_s: float) -> None:
deadline = time.monotonic() + timeout_s
for process in processes:
remaining_s = max(0.0, deadline - time.monotonic())
process.join(timeout=remaining_s)
# 终止仍然存活的进程,返回依然存活的进程列表
def _terminate_alive_processes(processes, timeout_s: float) -> list:
alive = [p for p in processes if p.is_alive()]
if not alive:
return []
logger.warning("Worker process(es) did not exit in time; terminating: %s", _process_names(alive))
for process in alive:
process.terminate()
_join_processes_with_deadline(alive, timeout_s)
return [p for p in alive if p.is_alive()]
# 强制杀死仍然存活的进程
def _kill_alive_processes(processes, timeout_s: float) -> None:
alive = [p for p in processes if p.is_alive()]
if not alive:
return
logger.warning("Worker process(es) did not terminate in time; killing: %s", _process_names(alive))
for process in alive:
process.kill()
_join_processes_with_deadline(alive, timeout_s)
# 向 monolithic scheduler 发送关闭请求
def _request_monolithic_scheduler_shutdown(server_args: ServerArgs) -> None:
if server_args.disagg_role != RoleType.MONOLITHIC:
return
client = SchedulerClient()
try:
client.initialize(server_args)
client.forward(ShutdownReq(), timeout_ms=_SCHEDULER_SHUTDOWN_TIMEOUT_MS)
except Exception as e:
logger.warning("Failed to request graceful scheduler shutdown: %s", e)
finally:
client.close()
# 主关闭入口:先尝试优雅关闭 scheduler,再逐步强制退出 workers
def shutdown_scheduler_processes(
server_args: ServerArgs | None,
processes: list,
*,
request_shutdown: bool = True,
) -> None:
if not processes:
return
# 1. 尝试优雅关闭 monolithic scheduler
if request_shutdown and server_args is not None:
_request_monolithic_scheduler_shutdown(server_args)
# 2. 等待 worker 进程正常退出(最长 _WORKER_JOIN_TIMEOUT_S 秒)
_join_processes_with_deadline(processes, _WORKER_JOIN_TIMEOUT_S)
# 3. 对未退出的 worker 发送 SIGTERM
still_alive = _terminate_alive_processes(processes, _WORKER_TERMINATE_TIMEOUT_S)
# 4. 对仍然未退出的 worker 发送 SIGKILL
if still_alive:
_kill_alive_processes(processes, _WORKER_KILL_TIMEOUT_S)
评论区精华
Review 建议:在请求 scheduler 关闭前检查 master 进程是否存活
gemini-code-assist[bot] 提出:如果 master 进程已经死亡(例如被直接 SIGKILL),则 scheduler 肯定未运行,此时 _request_monolithic_scheduler_shutdown 会超时等待 5 秒,造成不必要的延迟。建议增加 processes[0].is_alive() 检查。该建议未被采纳,作者未回复,PR 已合并。可能作者认为在正常关闭路径中 master 进程不会先于 scheduler 退出,或者该优化影响较小。
- 检查 master 进程存活以避免不必要的 scheduler shutdown 超时 (performance): 作者未回应,PR 已合并,未采纳该建议。
风险与影响
关联脉络
参与讨论