Prhub

#2050 Stop checking actor liveness while waiting for a tcp port

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

执行摘要

移除 `wait_tcp_ready` 的 actor 存活检查参数,简化端口就绪探测接口。

本 PR 是追踪 Issue #1837 “重构和增强” 的一部分。其动机在于简化 wait_tcp_ready 这一基础设施函数的职责边界,使其不再耦合具体的进程生命周期管理(Ray actor 存活检查)。这种解耦是更大架构重构(如 PR#2051 提到的 worker-provider 转换)的前置步骤,旨在使服务启动和就绪检查的逻辑更加通用和可组合。

该 PR 值得精读。它展示了一个典型的“关注点分离”重构:通过移除一个交叉职责(存活检查)来简化核心工具函数。其变更清晰、影响局部,且是更大架构演进(#1837, #2051)的关键一环。理解此次重构的动机和上下文,有助于把握仓库在服务启动和管理方面的设计演进方向。

讨论亮点

Reviewer guapisolo 在审核中指出:“本 PR 清晰地将就绪检查范围缩小到 TCP 端点本身,并为 #2051 中的 worker-provider 转换铺平了道路。” 这点明了本次重构是后续更大设计变更的必要前置步骤。讨论中未出现争议点。

实现拆解

  1. 核心函数简化:修改 miles/utils/http_utils.py 中的 wait_tcp_ready 函数,移除 is_alive 关键字参数及其内部对进程存活状态的检查逻辑。函数签名和文档注释相应更新。
  2. 调用方适配:在 miles/ray/rollout/router_manager.py 中,更新 start_router 和 start_session_server 两个函数对 wait_tcp_ready 的调用,移除传入的 is_alive=functools.partial(_actor_is_alive, actor_handle) 参数。
  3. 废弃代码清理:从 router_manager.py 中删除已无调用方的 _actor_is_alive 辅助函数及其相关的 functools 导入。同时,将 start_router 中不再使用的 actor_handle 变量重命名为 _actor_handle 以表示其仅作保留引用。
  4. 测试覆盖强化:在 tests/fast/utils/test_http_utils.py 中,为 wait_for_server_ready 函数新增两个测试用例(test_waits_for_a_live_subprocess_to_open_its_port, test_raises_when_subprocess_exits_while_waiting_for_port),以验证其原有的子进程存活检查行为。同时,新增 TestWaitTcpReady 测试类,专门验证简化后的 wait_tcp_ready 函数仅依赖端口状态和超时,与任何外部存活状态无关。
文件 模块 状态 重要度
miles/utils/http_utils.py HTTP 工具 modified 6.49
miles/ray/rollout/router_manager.py 路由器管理 modified 7.09
tests/fast/utils/test_http_utils.py HTTP 工具测试 modified 6.85

关键符号

wait_tcp_ready _actor_is_alive

关键源码片段

miles/utils/http_utils.py core-logic

核心变更点,`wait_tcp_ready` 函数被简化,移除了 `is_alive` 参数和进程检查逻辑,是本次重构的主要实现。

# 变更前的 wait_tcp_ready 函数签名与核心逻辑
def wait_tcp_ready(host: str, port: int, *, is_alive: Callable[[], bool], timeout: float = 30) -> None:
    """Poll until a TCP port accepts connections, treating worker death as failure."""
    deadline = time.time() + timeout
    while time.time() < deadline:
        if not is_alive(): # 此处检查外部 actor 存活状态
            raise RuntimeError(f"Server process died before port {port} became ready")
        try:
            with socket.create_connection((host.strip("[]"), port), timeout=1):
                return
        except OSError:
            time.sleep(0.5)
    raise RuntimeError(f"Server at {host}:{port} not ready after {timeout}s")
​
# 变更后的 wait_tcp_ready 函数签名与核心逻辑
def wait_tcp_ready(host: str, port: int, *, timeout: float = 30) -> None:
    """Poll until a TCP port accepts connections."""
    deadline = time.time() + timeout
    while time.time() < deadline:
        # 不再检查任何外部存活状态,仅关注端口本身
        try:
            with socket.create_connection((host.strip("[]"), port), timeout=1):
                return
        except OSError:
            time.sleep(0.5)
    raise RuntimeError(f"Server at {host}:{port} not ready after {timeout}s")
miles/ray/rollout/router_manager.py entrypoint

主要调用方,展示了 `wait_tcp_ready` 简化后的集成方式变化,以及配套的 `_actor_is_alive` 函数的删除。

# 变更前(start_router 示例):
actor_handle = _launch_command_on_head(launch_command)
wait_tcp_ready(
    router_ip,
    router_port,
    is_alive=functools.partial(_actor_is_alive, actor_handle), # 传入存活检查回调
    timeout=_SERVER_READY_TIMEOUT_SECS,
)
​
# 变更后(start_router 示例):
_actor_handle = _launch_command_on_head(launch_command) # 下划线前缀表示变量不再被后续使用
wait_tcp_ready(router_ip, router_port, timeout=_SERVER_READY_TIMEOUT_SECS) # 不再传入 is_alive 参数
tests/fast/utils/test_http_utils.py test-coverage

测试配套更新,为新旧行为提供测试覆盖,是验证本次重构正确性的关键。

# 新增的 TestWaitTcpReady 测试类,验证简化后的 wait_tcp_ready 行为
class TestWaitTcpReady:
    def test_keeps_retrying_until_the_port_accepts(self):
        """Readiness depends on the endpoint alone, retrying while it refuses connections."""
        # ... (mock 设置 )
        wait_tcp_ready("[2001:db8::7]", 23456, timeout=30) # 调用时不传 is_alive
        # 验证:只进行了端口连接尝试,没有进行任何存活检查
        assert attempts == [(('2001:db8::7', 23456), 1)] * 3
​
    def test_gives_up_when_the_deadline_passes(self):
        """A port that never opens fails with a timeout instead of blocking forever."""
        # ... (mock 设置 )
        with pytest.raises(RuntimeError, match="Server at 127.0.0.1:23456 not ready after 1s"):
            wait_tcp_ready("127.0.0.1", 23456, timeout=1) # 超时行为不变

评论区精华

重构目的与上下文 设计

Reviewer guapisolo 指出:" 本 PR 清晰地将就绪检查范围缩小到 TCP 端点本身,并为 #2051 中的 worker-provider 转换铺平了道路。"

结论:本次重构是更大架构演进(#1837)的一部分,旨在简化接口以支持后续的 worker-provider 模式迁移。 · 已解决

风险与影响

主要风险在于行为变更:移除了对启动进程(actor)的快速失败检测。在 actor 异常退出但端口未被释放的情况下,wait_tcp_ready 将不再快速抛出 RuntimeError,而是会持续轮询直至超时。这可能导致故障检测变慢(延迟从近乎即时变为最多等待 _SERVER_READY_TIMEOUT_SECS,此处为 120 秒)。然而,根据代码上下文和后续的重构计划(#2051),这种“延迟”可能是有意为之的设计权衡,用以换取更清晰、更通用的接口。

影响范围局限在路由管理器(router_manager)的启动路径。对于用户和系统而言,路由器和会话服务器的启动流程逻辑保持不变,但内部实现简化。对于团队,这减少了代码耦合和认知负担,为后续将服务启动抽象为更通用的 worker-provider 模型奠定了基础。

接口简化 超时路径变化

关联 Issue

#1837 Tracking issue for refactoring and enhancements

完整报告

参与讨论