# PR #2050 完整报告

- 仓库：`radixark/miles`
- 标题：Stop checking actor liveness while waiting for a tcp port
- 合并时间：2026-09-04 13:50
- 原文链接：http://prhub.com.cn/radixark/miles/pull/2050

---

# 执行摘要

- 一句话：移除 `wait_tcp_ready` 的 actor 存活检查参数，简化端口就绪探测接口。
- 推荐动作：该 PR 值得精读。它展示了一个典型的“关注点分离”重构：通过移除一个交叉职责（存活检查）来简化核心工具函数。其变更清晰、影响局部，且是更大架构演进（#1837, #2051）的关键一环。理解此次重构的动机和上下文，有助于把握仓库在服务启动和管理方面的设计演进方向。

# 功能与动机

本 PR 是追踪 Issue #1837 “重构和增强” 的一部分。其动机在于简化 `wait_tcp_ready` 这一基础设施函数的职责边界，使其不再耦合具体的进程生命周期管理（Ray actor 存活检查）。这种解耦是更大架构重构（如 PR#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 工具；类别 source；类型 core-logic；符号 wait_tcp_ready）: 核心变更点，`wait_tcp_ready` 函数被简化，移除了 `is_alive` 参数和进程检查逻辑，是本次重构的主要实现。
- `miles/ray/rollout/router_manager.py`（模块 路由器管理；类别 source；类型 entrypoint；符号 _actor_is_alive）: 主要调用方，展示了 `wait_tcp_ready` 简化后的集成方式变化，以及配套的 `_actor_is_alive` 函数的删除。
- `tests/fast/utils/test_http_utils.py`（模块 HTTP 工具测试；类别 test；类型 test-coverage；符号 test_waits_for_a_live_subprocess_to_open_its_port, test_raises_when_subprocess_exits_while_waiting_for_port, TestWaitTcpReady）: 测试配套更新，为新旧行为提供测试覆盖，是验证本次重构正确性的关键。

关键符号：wait_tcp_ready, _actor_is_alive

## 关键源码片段

### `miles/utils/http_utils.py`

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

```python
# 变更前的 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`

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

```python
# 变更前（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`

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

```python
# 新增的 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 转换铺平了道路。” 这点明了本次重构是后续更大设计变更的必要前置步骤。讨论中未出现争议点。

- 重构目的与上下文 (design): 本次重构是更大架构演进（#1837）的一部分，旨在简化接口以支持后续的 worker-provider 模式迁移。

# 风险与影响

- 风险：主要风险在于行为变更：移除了对启动进程（actor）的快速失败检测。在 actor 异常退出但端口未被释放的情况下，`wait_tcp_ready` 将不再快速抛出 `RuntimeError`，而是会持续轮询直至超时。这可能导致故障检测变慢（延迟从近乎即时变为最多等待 `_SERVER_READY_TIMEOUT_SECS`，此处为 120 秒）。然而，根据代码上下文和后续的重构计划（#2051），这种“延迟”可能是有意为之的设计权衡，用以换取更清晰、更通用的接口。
- 影响：影响范围局限在路由管理器（router_manager）的启动路径。对于用户和系统而言，路由器和会话服务器的启动流程逻辑保持不变，但内部实现简化。对于团队，这减少了代码耦合和认知负担，为后续将服务启动抽象为更通用的 worker-provider 模型奠定了基础。
- 风险标记：接口简化 , 超时路径变化

# 关联脉络

- PR #1837 Tracking issue for refactoring and enhancements: 本 PR 明确声明是 #1837 跟踪 Issue 的一部分，属于其规划中的重构任务。
- PR #2051 Enable the worker-provider transition: Reviewer 在审核评论中明确指出，本 PR（#2050）为 PR#2051 的 worker-provider 转换铺平了道路，是其前置依赖。
- PR #2176 safe-merge checkpoint: 本 PR 被包含在 PR#2176 这个安全合并检查点中，表明其被评估为低风险、可安全合入的变更。