Prhub

#31543 [PD] pool decode bootstrap HTTP sessions

原始 PR 作者 inkcherry 合并时间 2026-07-27 21:07 文件变更 1 提交数 5 评论 5 代码增减 +26 / -2

执行摘要

复用 bootstrap HTTP 会话避免端口耗尽

PD 解耦部署下 decode 调度器每轮事件循环都会为每个未解析 dp_rank 的请求发起 GET /routePOST /query_dp_ranks 查询,每次使用全新的短连接。高并发时大量 TIME-WAIT 套接字耗尽本地临时端口([Errno 99] Cannot assign requested address),导致后续查询失败、请求永久无法解析。

建议快速合入。本 PR 以极小的代码改动(仅一个文件)解决了 PD 部署中严重的生产问题,A/B 验证数据充分。值得关注的设计决策:使用 threading.local() 保证 Session 线程安全、小连接池(pool=1)避免资源浪费。

讨论亮点
  1. Session 线程安全性:kkHuang-amd 指出 requests.Session 不是线程安全的,初始实现使用共享字典,第二个提交将存储改为 threading.local(),修复了潜在的并发 bug。
  2. 连接池配置:讨论中提及 Connection: keep-alive 头在连接池模式下冗余,后续提交删除。
  3. Session 清理策略:明确不主动 evict session,因为 bootstrap_addr 数量有限(一个 prefill 服务器对应一个地址),且仅挂载 http 协议。

实现拆解

  1. 引入线程本地 Session 池:在 python/sglang/srt/disaggregation/common/conn.py 模块顶部新增 _bootstrap_sessions = threading.local()_get_bootstrap_session(bootstrap_addr) 函数。函数内部以 threading.local() 对象存储每个 bootstrap_addr 对应的 requests.Session,确保每个线程持有自己的持久连接(requests.Session 非线程安全)。

  2. 配置 HTTPAdaptersession.mount("http://", HTTPAdapter(pool_connections=1, pool_maxsize=1)),限制每个地址的连接池大小为 1,避免不必要的连接膨胀;仅挂载 http:// 协议(bootstrap 只走 http)。

  3. 替换两处调用点

    • _get_bootstrap_info_from_server 中原 requests.get(url, timeout=5) 改为 _get_bootstrap_session(self.bootstrap_addr).get(url, timeout=5)
    • query_prefill_dp_ranks 中原 requests.post(url, ...) 改为 _get_bootstrap_session(bootstrap_addr).post(url, ...)
    • 两组调用的 URL、payload、超时、错误处理均未变更,仅连接方式从短连接变为连接池复用。
  4. 删除冗余请求头:回顾讨论后删除了 Connection: keep-alive 请求头(连接池已自动保活)。测试与配置无需改动,原 A/B 验证已覆盖回归。

文件 模块 状态 重要度
python/sglang/srt/disaggregation/common/conn.py 解耦模块 modified 7.22

关键符号

_get_bootstrap_session

关键源码片段

python/sglang/srt/disaggregation/common/conn.py core-logic

唯一变更文件,新增 `_bootstrap_sessions` 线程局部变量和 `_get_bootstrap_session` 函数,改造两处请求调用点。

# python/sglang/srt/disaggregation/common/conn.py# ... ( 其他 import 省略 )
import threading# Thread-local storage for per-address bootstrap sessions.
# `requests.Session` is not safe for concurrent cross-thread use,
# so each thread keeps its own pool.
_bootstrap_sessions = threading.local()
​
​
def _get_bootstrap_session(bootstrap_addr: str) -> requests.Session:
    """Return a keep-alive session for the given bootstrap address.    Sessions are cached per thread in a thread-local dict. The number of
    distinct bootstrap_addr values is bounded (one per prefill server),
    so eviction is not needed. Only http:// is mounted because bootstrap
    is HTTP-only.
    """
    # Access the per-thread dict; lazy-init if not present
    sessions = getattr(_bootstrap_sessions, "by_addr", None)
    if sessions is None:
        sessions = {}
        _bootstrap_sessions.by_addr = sessions
​
    session = sessions.get(bootstrap_addr)
    if session is None:
        # Create a new Session with a small connection pool (1 connection per addr)
        session = requests.Session()
        session.mount(
            "http://",
            requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1)
        )
        sessions[bootstrap_addr] = session
    return session

同时,在 _get_bootstrap_info_from_serverquery_prefill_dp_ranks 两处方法中,将原有的 requests.get(url, ...)requests.post(url, ...) 替换为 _get_bootstrap_session(addr).get(...) / _get_bootstrap_session(addr).post(...),其余逻辑(URL、超时、异常处理)保持不变。

评论区精华

Session 线程安全性 正确性

kkHuang-amd 指出初始版本使用模块级共享字典可能导致多线程竞争,因为 `requests.Session` 不是线程安全的。

结论:第二个提交将 Session 存储改为 `threading.local()`,每个线程持有独立 Session,修复了潜在并发 bug。 · 已解决

连接池配置与冗余头 设计

讨论中建议删除显式的 `Connection: keep-alive` 头,因为 Session 已自动管理连接保活;同时确认连接池大小(1)合适。

结论:删除了冗余头,提交者添加注释说明设计选择。 · 已解决

风险与影响

  • 回归风险低:改动仅将 requests.get/post 替换为 _get_bootstrap_session().get/post,URL、参数、超时、异常处理保持不变,行为等价。
  • 线程安全:使用 threading.local() 确保每线程独立 Session,避免共享状态竞争。
  • 连接泄漏风险:Session 数量与 bootstrap_addr 数量一致(通常 1~N 个 prefill server),不会无限增长。
  • 测试覆盖缺失:当前无单元测试覆盖新函数,依赖 CI 集成测试(A/B 验证通过)。
  • 用户:高并发 PD 部署不再出现 Cannot assign requested address 错误,请求可正常完成;TTFT 和吞吐量有小幅改善(-9% TTFT p99, +4% 吞吐)。
  • 系统:decode 节点到 bootstrap 的 TCP 连接从每请求新建变为长期复用,TIME-WAIT 套接字从约 10,100 降到 0,降低系统负担。
  • 团队:改动量极小(+26/-2),风险低,可快速合入。
核心路径变更 缺少测试覆盖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论