Prhub

#1867 Move engine teardown into ServerCell.stop

原始 PR 作者 fzyzcjy 合并时间 2026-09-04 13:02 文件变更 4 提交数 1 评论 1 代码增减 +43 / -28

执行摘要

将引擎关闭逻辑从 ServerGroup 下沉至 ServerCell,提升模块封装性与职责清晰度。

此变更属于 Issue #1837 所追踪的重构与增强计划。其直接动机是优化引擎关闭逻辑的模块组织。原有实现在 ServerGroup.stop_engines 中通过扁平化的引擎索引操作,既处理组级别的协调(如从路由器注销),又处理单元级别的资源清理(shutdown/kill actor),导致职责混合。将资源清理逻辑封装到 ServerCell 中,使得每个 Cell 能够自主管理其内部引擎的关闭,符合单一职责原则,并为未来可能的更细粒度控制(如单个 Cell 的独立重启)铺平道路。

这是一个清晰、有价值且风险可控的重构 PR。它很好地遵循了单一职责原则,将特定层次的逻辑封装到对应的模块中。PR 改动集中,有配套测试更新,且获得了审核者的积极反馈。对于维护 Rollout 模块代码质量的工程师而言,此 PR 值得精读,以理解模块化重构的具体手法。其将协调与执行分离、利用数据结构封装行为的设计模式具有参考价值。

讨论亮点

唯一的审核评论由 guapisolo 提供,该评论关联到了安全合并检查点 #2176。评论指出本 PR (#1870,即当前分析的 #1867) 将关闭选择移至 cell 索引,消除了验证前的部分引擎注销状态,并且此修复在检查链中得以保持,因此不再阻塞 PR。这表明审核者从集成和系统稳定性的角度认可了此次重构的价值,认为它解决了一个潜在的不一致状态风险。

实现拆解

  1. 在 ServerCell 中新增停止能力:在 miles/ray/rollout/server_cell.py 的 ServerCell 数据类中,新增了 stop() 方法。该方法遍历其内部的所有 ServerEngine,尝试优雅关闭 (ray.get(engine.actor_handle.shutdown.remote(), timeout=SHUTDOWN_TIMEOUT)),若失败则强制杀死 (ray.kill),并最终将每个引擎标记为已停止 (mark_stopped)。同时,将关闭超时常量 SHUTDOWN_TIMEOUT 从 server_group.py 移动到 server_cell.py,作为两个模块共享的配置。
  2. 重构 ServerGroup.stop_engines 委托逻辑:在 miles/ray/rollout/server_group.py 的 stop_engines 方法中,删除了原有的直接操作引擎的循环和 ray 调用。该方法现在首先完成路由器注销等组级协调,然后将请求的全局引擎索引 (engine_indices) 按 nodes_per_engine 转换为对应的 Cell 索引集合,对每个涉及的 Cell 调用其新的 stop() 方法。同时添加了断言,确保请求的引擎索引完整覆盖了这些 Cell,防止部分关闭导致的不一致状态。
  3. 同步更新测试用例以指向新模块:修改了两个测试文件 tests/fast/ray/rollout/test_server_group_router_registration.py 和 tests/fast/ray/rollout/real_ray/test_server_group_teardown.py。将测试中 mock 或 patch 的目标从原来的 server_group 模块(例如 miles.ray.rollout.server_group.ray 和 _SHUTDOWN_TIMEOUT)更新为新的位置 server_cell 模块(例如 miles.ray.rollout.server_cell.ray 和 SHUTDOWN_TIMEOUT),确保单元测试和集成测试准确验证新的代码路径。
文件 模块 状态 重要度
miles/ray/rollout/server_cell.py 服务器单元 modified 7.19
miles/ray/rollout/server_group.py 服务器组 modified 6.73
tests/fast/ray/rollout/test_server_group_router_registration.py 路由注册测试 modified 4.91
tests/fast/ray/rollout/real_ray/test_server_group_teardown.py 关闭逻辑测试 modified 3.99

关键符号

ServerCell.stop ServerGroup.stop_engines

关键源码片段

miles/ray/rollout/server_cell.py core-logic

本次重构的核心文件,新增了 `stop()` 方法,承接了从 `ServerGroup` 下沉的引擎关闭与清理逻辑,并引入了共享的超时常量。

import logging
import ray
​
SHUTDOWN_TIMEOUT = 30 # 从 server_group.py 移入,作为共享常量
​
# ... ( 省略其他导入和类定义 )
​
@dataclass
class ServerCell:
    engines: list[ServerEngine]
​
    # ... ( 省略其他方法 )
​
    def stop(self):
        """关闭并杀死此单元内的所有已分配引擎。
​
        对于每个引擎,尝试优雅关闭,若超时或失败则强制杀死。
        无论成功与否,最终都将引擎标记为已停止。
        """
        for local_index, engine in enumerate(self.engines):
            if engine.is_allocated:
                logger.info(f"Shutting down and killing engine at cell-local index {local_index}")
                try:
                    # 尝试优雅关闭,等待超时。
                    ray.get(engine.actor_handle.shutdown.remote(), timeout=SHUTDOWN_TIMEOUT)
                except Exception as e:
                    # 优雅关闭失败(如超时、异常),记录警告并继续强制杀死。
                    logger.warning(
                        f"Graceful shutdown of engine at cell-local index {local_index} failed, killing anyway (e: {e})"
                    )
                try:
                    # 强制杀死 Ray actor,释放资源。
                    ray.kill(engine.actor_handle)
                    logger.info(f"Successfully killed engine at cell-local index {local_index}")
                except Exception as e:
                    # 强制杀死也失败,记录警告。资源可能泄漏,但流程继续。
                    logger.warning(f"Fail to kill engine at cell-local index {local_index} (e: {e})")
            else:
                # 引擎未分配(actor_handle 为 None),记录信息并跳过。
                logger.info(f"Engine at cell-local index {local_index} is already None")
            # 无论如何,标记引擎状态为已停止。
            self.engines[local_index].mark_stopped()

评论区精华

PR 合并审查与安全检查点 设计

审核者 guapisolo 在评论中提到了 #2176 安全合并检查点,并确认本 PR (#1870) 通过将关闭选择移至 cell 索引,消除了验证前的部分引擎注销不一致状态,因此该问题不再阻塞 PR 在合并链中的前进。

结论:审核从系统集成和稳定性角度认可了重构,认为它修复了一个潜在的状态不一致问题,从而支持合并。 · 已解决

风险与影响

  1. 行为一致性风险:本次重构旨在保持行为不变。主要的风险在于 ServerGroup.stop_engines 现在依赖 cell.stop() 的实现。如果 cell.stop() 的内部逻辑(如超时、异常处理)与原先在 ServerGroup 中的实现存在细微差异,可能改变关闭引擎的时序或最终状态。目前代码显示逻辑被忠实移动,但需要集成测试覆盖。
  2. 索引转换逻辑风险:ServerGroup.stop_engines 中新增了将全局引擎索引转换为 Cell 索引并验证完整性的逻辑(for cell_index in sorted({i // self.nodes_per_engine for i in engine_indices}))。若 nodes_per_engine 的配置错误或 engine_indices 不是按 Cell 完整给出,断言将失败。这是一个防御性改进,但改变了失败模式,从可能的不一致关闭变为明确的断言错误。
  3. 测试覆盖风险:测试用例已更新以 mock 正确的模块,这降低了回归风险。但真实 Ray 环境下的行为依赖于对 ray.get/ray.kill 的正确调用,这些调用现在集中在 ServerCell.stop 中。集成测试 test_server_group_teardown.py 覆盖了挂起关闭的场景,这提供了关键的保障。
  1. 对代码架构的影响:提升了 ServerCell 的封装性和自主性,使其成为更独立的生命周期管理单元。这为后续将 ServerCell 可能发展为更通用的资源组抽象提供了基础。
  2. 对开发者的影响:代码更易理解。当需要修改引擎关闭逻辑(如调整超时、增加清理步骤)时,开发者只需关注 ServerCell.stop 一个位置,而非可能分散在 ServerGroup 的复杂逻辑中。
  3. 对系统稳定性的影响:消除了原先在 ServerGroup 层面混合协调与执行的潜在复杂状态。明确的 Cell 级别关闭和断言有助于更可靠地回收资源,尤其是在故障恢复场景下。
逻辑迁移完整性 测试 mock 路径更新

关联 Issue

#1837 Tracking issue for refactoring and enhancements

完整报告

参与讨论