问题描述
当前 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() 失败,就会触发该问题。
根因分析
当前逻辑
- 握手线程进入
AllocateResources()
AllocateResources() 调用 ReqNotifyCq(true/false)
ReqNotifyCq() 内部如果 ibv_req_notify_cq() 失败,直接 _socket->SetFailed(...)
AllocateResources() 返回 < 0
- 上层握手代码把它当作 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 侧
- 建立一条启用 RDMA 的连接
- 在 client handshake 的
AllocateResources() 调用期间,让第一次或第二次 ibv_req_notify_cq() 返回错误
- 观察:
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 路径优先保证“可降级”,而不是过早失败整个连接。
问题描述
当前 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()失败,就会触发该问题。根因分析
当前逻辑
AllocateResources()AllocateResources()调用ReqNotifyCq(true/false)ReqNotifyCq()内部如果ibv_req_notify_cq()失败,直接_socket->SetFailed(...)AllocateResources()返回< 0rdma_state = RDMA_OFFendpoint state = FALLBACK_TCP问题点
握手层认为这是“可降级错误”,但
ReqNotifyCq()已经把 socket 提前标记为 failed。因此该连接并不能真正继续以 TCP 方式工作。
期望行为
在 握手阶段 / 初始化阶段,
ReqNotifyCq()失败应仅向上返回错误,由握手层决定:FALLBACK_TCP只有在 连接已经建立并进入 RDMA 正常工作阶段 后,
PollCq()中的 re-arm 失败才应该被视为 fatal,并调用SetFailed()。实际行为
当前实现中,初始化阶段的
ReqNotifyCq()失败也会直接SetFailed(),导致:FALLBACK_TCP复现建议
可以通过 mock / hook
ibv_req_notify_cq()在 handshake 阶段返回失败来验证:client 侧
AllocateResources()调用期间,让第一次或第二次ibv_req_notify_cq()返回错误AllocateResources()返回< 0RDMA_OFF/FALLBACK_TCPSetFailed()server 侧
同样在 server handshake 的
AllocateResources()阶段注入ibv_req_notify_cq()失败,观察结果一致。断言建议
测试中应断言:
FALLBACK_TCP当前实现下,最后两项预期会失败。
建议修复
建议将“初始化阶段 arm CQ”与“运行阶段 re-arm CQ”的错误处理区分开:
方案一:拆分 helper
ReqNotifyCq()给运行阶段使用(失败时SetFailed())SetFailed()的 helper,供AllocateResources()在 handshake 阶段使用方案二:增加参数
给
ReqNotifyCq()增加类似fatal_on_error的参数:AllocateResources()调用时传falsePollCq()调用时传true备注
这个问题和“TCP fallback 语义”直接相关,和一般运行态 CQ re-arm 失败的 fatal 处理不是一类场景。
建议 handshake 路径优先保证“可降级”,而不是过早失败整个连接。