Skip to content

RDMA handshake 中 ReqNotifyCq() 失败会直接 SetFailed(),导致本应 fallback 到 TCP 的连接被提前置失败 #3416

Description

@houlin2016

问题描述

当前 RDMA 握手流程中,RdmaEndpoint::AllocateResources() 在初始化 CQ/QP 后,会立即调用 ReqNotifyCq(true)ReqNotifyCq(false) 对 send/recv CQ 进行 arm。

ReqNotifyCq() 的实现里,一旦 ibv_req_notify_cq() 返回错误,会直接调用 _socket->SetFailed(...)。这会把当前连接标记为 failed,而握手调用方仍然将 AllocateResources() < 0 当作一个可恢复路径处理:仅设置 RDMA_OFF / FALLBACK_TCP,希望后续继续走 TCP。

结果是:一次 CQ arm 失败会把“优雅降级到 TCP”变成“连接失败”
从语义上看,这和握手层设计不一致;从实现上看,SetFailed() 之后该 socket 后续 Address() 将失效,连接不能继续正常承载 TCP 收发。

影响范围

这个问题不仅存在于 backport patch,也存在于较新的 brpc 代码路径中。
只要 AllocateResources() 在 handshake 阶段调用的 ReqNotifyCq() 失败,就会触发该问题。

根因分析

当前逻辑

  1. 握手线程进入 AllocateResources()
  2. AllocateResources() 调用 ReqNotifyCq(true/false)
  3. ReqNotifyCq() 内部如果 ibv_req_notify_cq() 失败,直接 _socket->SetFailed(...)
  4. AllocateResources() 返回 < 0
  5. 上层握手代码把它当作 fallback,设置:
    • rdma_state = RDMA_OFF
    • endpoint state = FALLBACK_TCP

问题点

握手层认为这是“可降级错误”,但 ReqNotifyCq() 已经把 socket 提前标记为 failed。
因此该连接并不能真正继续以 TCP 方式工作。

期望行为

握手阶段 / 初始化阶段ReqNotifyCq() 失败应仅向上返回错误,由握手层决定:

  • 将 RDMA 置为 OFF
  • 将 endpoint 状态切到 FALLBACK_TCP
  • 保留底层 TCP socket 可继续使用

只有在 连接已经建立并进入 RDMA 正常工作阶段 后,PollCq() 中的 re-arm 失败才应该被视为 fatal,并调用 SetFailed()

实际行为

当前实现中,初始化阶段的 ReqNotifyCq() 失败也会直接 SetFailed(),导致:

  • 握手层虽然进入 FALLBACK_TCP
  • 但 socket 已是 failed 状态
  • 后续 TCP 收发无法正常继续
  • 原本应可降级的连接被直接中断

复现建议

可以通过 mock / hook ibv_req_notify_cq() 在 handshake 阶段返回失败来验证:

client 侧

  1. 建立一条启用 RDMA 的连接
  2. 在 client handshake 的 AllocateResources() 调用期间,让第一次或第二次 ibv_req_notify_cq() 返回错误
  3. 观察:
    • AllocateResources() 返回 < 0
    • 代码进入 RDMA_OFF / FALLBACK_TCP
    • 但 socket 同时已被 SetFailed()

server 侧

同样在 server handshake 的 AllocateResources() 阶段注入 ibv_req_notify_cq() 失败,观察结果一致。

断言建议

测试中应断言:

  • endpoint 最终进入 FALLBACK_TCP
  • socket 没有进入 failed 状态
  • 连接后续仍可继续通过 TCP 完成收发

当前实现下,最后两项预期会失败。

建议修复

建议将“初始化阶段 arm CQ”与“运行阶段 re-arm CQ”的错误处理区分开:

方案一:拆分 helper

  • 保留当前 ReqNotifyCq() 给运行阶段使用(失败时 SetFailed()
  • 新增一个仅返回错误、不 SetFailed() 的 helper,供 AllocateResources() 在 handshake 阶段使用

方案二:增加参数

ReqNotifyCq() 增加类似 fatal_on_error 的参数:

  • AllocateResources() 调用时传 false
  • PollCq() 调用时传 true

备注

这个问题和“TCP fallback 语义”直接相关,和一般运行态 CQ re-arm 失败的 fatal 处理不是一类场景。
建议 handshake 路径优先保证“可降级”,而不是过早失败整个连接。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions