# PR #24539 完整报告

- 仓库：`sgl-project/sglang`
- 标题：[PD] Prevent update_status to Failed from cleared entries
- 合并时间：2026-05-06 23:32
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/24539

---

# 执行摘要

- 一句话：修复已清除条目恢复为 Failed 的状态污染
- 推荐动作：值得精读。虽然改动极小，但体现了处理异步竞态的典型模式：在状态机中，当一个条目已被清除后，禁止用终点状态（Failed）重新创建。这种模式在分布式系统中具有普遍参考价值。同时建议检查 `update_status` 的其他调用点是否也需要类似的防护。

# 功能与动机

PR body 明确指出两个问题：1）内存泄漏：`request_status` 因迟来的 abort 或异步回调持续增长，包含过时的 `KVFailed` 条目；2）跨请求状态污染：当未来的请求重用了相同的 `bootstrap_room`（用户场景中常见），新请求可能从第一次轮询就被错误标记为 `Failed`。该修复是 PR #24522 的后续，后者为 `abort()` 增加了 `update_status(Failed)` 调用，但未考虑 `clear()` 后的竞态。

# 实现拆解

1. **修改目标函数**：在 `python/sglang/srt/disaggregation/common/conn.py` 的 `update_status` 方法中，当 `bootstrap_room not in self.request_status` 时，增加 `KVPoll.Failed` 特判：
 - 如果状态为 `Failed`，直接 `return`，不做任何插入或更新。
 - 其他状态（如 `Success`）仍按原逻辑写入。
2. **逻辑解释**：`clear()` 从字典中弹出条目后，后续任何 `Failed` 更新都应被忽略，因为 `Failed` 是终点状态，不应“复活”一个已清理的房间。而 `Success` 等其他状态可能来自正常流程，允许首次写入。
3. **保留原有逻辑**：当 `bootstrap_room` 仍在字典中时，原有的状态更新逻辑（Failed 覆盖、Success 取 max 等）保持不变。
4. **测试**：无新增测试，仅依赖 CI 中的现有测试；PR 作者已手动触发相关测试并确认通过。

关键文件：
- `python/sglang/srt/disaggregation/common/conn.py`（模块 PD 连接；类别 source；类型 core-logic；符号 update_status）: 包含修复核心逻辑：在 `update_status` 方法中新增对已清除条目的 `Failed` 跳过处理，避免状态污染。

关键符号：update_status

## 关键源码片段

### `python/sglang/srt/disaggregation/common/conn.py`

包含修复核心逻辑：在 `update_status` 方法中新增对已清除条目的 `Failed` 跳过处理，避免状态污染。

```python
def update_status(self, bootstrap_room: int, status: KVPoll):
    # 如果是 Failed 且是在 Prefill 模式下，清理 decode prefix 长度记录
    if (
        status == KVPoll.Failed
        and self.disaggregation_mode == DisaggregationMode.PREFILL
        and hasattr(self, "req_to_decode_prefix_len")
    ):
        self.req_to_decode_prefix_len.pop(bootstrap_room, None)

    if bootstrap_room not in self.request_status:
        # 关键修复：如果条目已被 clear() 弹出，且尝试更新为 Failed，
        # 则直接忽略。否则可能复活一个已清理的房间，
        # 导致内存泄漏和后续请求的状态污染。
        if status == KVPoll.Failed:
            return
        # 其他状态（如 Success）首次写入是被允许的
        self.request_status[bootstrap_room] = status
    else:
        # 条目存在时，保持原有语义：Failed 覆盖，其他状态取最大值
        if status == KVPoll.Failed:
            self.request_status[bootstrap_room] = KVPoll.Failed
        else:
            self.request_status[bootstrap_room] = max(
                self.request_status[bootstrap_room], status
            )

```

# 评论区精华

该 PR 无 review 评论，只有 author 触发的 CI 重跑和最终确认通过。讨论主要集中在 PR body 中的动机说明，以及 Issue 评论中 author 的 CI 重跑指令。

- 暂无高价值评论线程

# 风险与影响

- 风险：
 1. **回归风险低**：改动仅 6 行，逻辑简单，在 `bootstrap_room` 已清除的情况下忽略 `Failed` 状态更新。不会影响 `request_status` 中已有条目的正常更新路径。
 2. **竞态条件仍存在**：目前仅处理了 `update_status` 一个入口，如果其他函数（如 `clear` 与 `update_status` 并发）也存在类似竞态，可能仍需加固。但当前改动已覆盖主要路径。
 3. **无测试覆盖**：本次未添加针对性测试，未来重构时可能被意外破坏。
- 影响：
 - **影响范围**：仅限于 PD 模式下的 `CommonKVManager`（`conn.py`），是 `update_status` 方法的核心逻辑。
 - **影响程度**：中等。修复了两种实际场景下的 Bug：内存泄漏（`request_status` 无限制增长）和跨请求状态污染（用户自指定 `bootstrap_room` 场景）。
 - **用户可见性**：用户不会直接看到改动，但通过降低异常失败率和内存增长，提升系统稳定性。
 - 风险标记：缺少测试覆盖 , 竞态条件

# 关联脉络

- PR #24522 [PD] Fix missing update_status call in abort() across all KV backends: 本 PR 是 #24522 的后续修复，该 PR 在 `abort()` 中引入了 `update_status(Failed)` 但未考虑 `clear()` 后的竞态。