UCP/RNDV: Fix rkey use-after-destroy on FAILOVER restart of rndv/put - #3
UCP/RNDV: Fix rkey use-after-destroy on FAILOVER restart of rndv/put#3jyizheng wants to merge 1 commit into
Conversation
ucp_proto_rndv_put_common_complete() unconditionally destroys req->send.rndv.rkey (setting it to NULL) before calling ucp_proto_request_zcopy_complete(). In UCP_ERR_HANDLING_MODE_FAILOVER, an error completion does not complete the request - it restarts it via ucp_proto_request_restart(), and the restarted send dereferences the rkey in ucp_proto_rndv_put_common_send() -> ucp_rkey_get_tl_rkey(), crashing on the NULL pointer (assertions are disabled in release builds). Hit in production during a RoCE fabric congestion event: a rail send failed with a hard error, the PUT completed with error status, and the FAILOVER restart path segfaulted the worker inside the UCXX progress thread. Keep the rkey alive when the completion status is an error and the EP uses FAILOVER mode, i.e. exactly when zcopy_complete will restart the request; the restarted protocol instance destroys it on its own completion.
|
Independently traced the same chain and confirm the diagnosis — Two things I'd raise before this lands. 1. The guard is wider than the restart condition, so it leaks the rkey. The hook restarts only when: status != UCS_OK && FAILOVER && !(req->send.ep->flags & UCP_EP_FLAG_FAILED)This PR skips the destroy whenever That case isn't hypothetical — it's precisely the endpoint-death path ( Suggestion: have the release site ask the same predicate the hook uses, rather than restating a subset of it. I have this locally as a small helper in static UCS_F_ALWAYS_INLINE int
ucp_proto_request_is_failover_restart(const ucp_request_t *req,
ucs_status_t status)
{
return ucs_unlikely(status != UCS_OK) &&
ucp_ep_err_mode_eq(req->send.ep, UCP_ERR_HANDLING_MODE_FAILOVER) &&
!(req->send.ep->flags & UCP_EP_FLAG_FAILED);
}used by the hook itself and by both schemes. The get path had this right already but open-coded the condition, which is exactly how the put path came to be written without it — one predicate keeps them from drifting again. Happy to hand that over or open it separately, whichever you prefer. 2. Base branch: is
If production hit this, production is running something with the put admission, and a fix landing only on |
|
Correction to my earlier comment — my first point was wrong, and I should have checked your base before raising it. I read your guard against status != UCS_OK && FAILOVER && !(req->send.ep->flags & UCP_EP_FLAG_FAILED)On My second point still stands as a question rather than a finding: |
|
Answering my own question about the lineage, since I found it: TRT-LLM builds UCX from this fork's That pin is now moved past the fix on our side. Flagging it here in case |
What
ucp_proto_rndv_put_common_complete()destroysreq->send.rndv.rkey(sets it to NULL) beforeucp_proto_request_zcopy_complete()decides whether to complete or restart the request. InUCP_ERR_HANDLING_MODE_FAILOVER, an error completion restarts the request, and the restarted send dereferences the NULL rkey inucp_proto_rndv_put_common_send()→ucp_rkey_get_tl_rkey()→ segfault (release builds compile out theucs_assertguards).Fix: destroy the rkey only when the request is really completing (success, or non-FAILOVER EP). On the restart path the re-selected protocol instance destroys it on its own completion.
Context
Hit in production (boostrun/bst-use1) during a RoCE fabric congestion event: rail send failed with a hard error → PUT completed with error status → FAILOVER restart segfaulted the worker inside the UCXX progress thread. Backtrace:
Same bug exists on upstream master; will report/port upstream separately.
Testing
Full release build (
--with-verbs --with-cuda) passes with the patch on v1.21.x.