Skip to content

drain CQ after moving QP into RESET state - #3261

Merged
chenBright merged 1 commit into
apache:masterfrom
live4thee:fix-3252
May 18, 2026
Merged

chenBright merged 1 commit into
apache:masterfrom
live4thee:fix-3252

Conversation

@live4thee

Copy link
Copy Markdown
Contributor

Otherwise there might segfault due to the race below:

Socket::OnInputEvent()          |
  `-- ProcessEvent (bthread)    |
                                |
  [ bthread queueed ]           |  QP error -> SetFailed -> HC -> WaitAndReset()
                                |  Reset() -> _sbuf.clear()
                                |  CheckHealth() -> Revive()
                                |
                                |  Socket is now Addressable!
  RdmaEndpoint:PollCq()         |
  Socket::Address() OK!         |
  RdmaEndpoint:HandleCompletion()
  _sbuf[_sq_sent++].clear()    <= BOOM!  CQ is not drained but _sbuf is cleared.

Another possible fix is to add a generation field in RdmaEndpoint, such that:

  • each RdmaEndpoint::Reset() will advance the generation by 1;
  • the RdmaEndpoint::PollCq(m, orig_gen) will need to compare the generation.

But it will contaminate existing interface, and we need to drain CQ anyway.

What problem does this PR solve?

Issue Number: 3252

Problem Summary: see above.

What is changed and the side effects?

Changed:

  1. drain CQ after moving QP into RESET state;
  2. do not re-use QP if drain failed.

Side effects:

  • Performance effects: n/a

  • Breaking backward compatibility: none


Check List:

Otherwise there might segfault due to the race below:

```txt
Socket::OnInputEvent()          |
  `-- ProcessEvent (bthread)    |
                                |
  [ bthread queueed ]           |  QP error -> SetFailed -> HC -> WaitAndReset()
                                |  Reset() -> _sbuf.clear()
                                |  CheckHealth() -> Revive()
                                |
                                |  Socket is now Addressable!
  RdmaEndpoint:PollCq()         |
  Socket::Address() OK!         |
  RdmaEndpoint:HandleCompletion()
  _sbuf[_sq_sent++].clear()    <= BOOM!  CQ is not drained but _sbuf is cleared.
```

Another possible fix is to add a _generation_ field in RdmaEndpoint, such that:
 - each RdmaEndpoint::Reset() will advance the _generation_ by 1;
 - the RdmaEndpoint::PollCq(m, orig_gen) will need to compare the _generation_.

But it will contaminate existing interface, and we need to drain CQ anyway.

Signed-off-by: David Lee <live4thee@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an RDMA reset/revive race where stale CQEs can be consumed after a QP is moved to RESET and internal send buffers are cleared, leading to a crash (issue #3252). It ensures completion queues are drained before reusing a prepared QP, and falls back to reclaiming resources if draining fails.

Changes:

  • Add a DrainCq() helper to poll and clear CQEs from a CQ.
  • Drain polling/send/recv CQs after moving the QP to RESET and before returning the resource to the prepared-QP list.
  • If draining fails, do not reuse the QP; instead reclaim/destroy resources via the existing cleanup path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ret = ibv_poll_cq(cq, 1, &wc);
} while (ret > 0);

LOG_IF(ERROR, ret < 0) << "drain CQ failed: " << ret;
Comment on lines +1413 to +1419
int ret = DrainCq(_resource->polling_cq);
ret += DrainCq(_resource->send_cq);
ret += DrainCq(_resource->recv_cq);
if (ret < 0) {
move_to_rdma_resource_list = false;
goto _reclaim;
}
Comment on lines +1403 to +1418
// When a QP is moved to the RESET state, all associated send and
// receive queues are flushed, meaning any outstanding WRs are effectively
// abandoned by the hardware.
//
// However, the CQ associated with that QP is *not* cleared automatically,
// meaning that it will still contain entries for WRs that completed before
// the reset.
//
// The application should finish polling the CQ to remove these obsolete
// entries before reusing the QP.
int ret = DrainCq(_resource->polling_cq);
ret += DrainCq(_resource->send_cq);
ret += DrainCq(_resource->recv_cq);
if (ret < 0) {
move_to_rdma_resource_list = false;
goto _reclaim;

@chenBright chenBright left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@chenBright
chenBright merged commit 58fec6f into apache:master May 18, 2026
33 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants