houlin2016 opened a new issue, #3416:
URL: https://github.com/apache/brpc/issues/3416
## 问题描述
当前 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 路径优先保证“可降级”,而不是过早失败整个连接。
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]