Skip to content

UCT/IB/MLX5: support in progress error handler - #11829

Open
jeynmann wants to merge 20 commits into
openucx:masterfrom
jeynmann:failover_uct_rcx_no_comp_impl
Open

UCT/IB/MLX5: support in progress error handler#11829
jeynmann wants to merge 20 commits into
openucx:masterfrom
jeynmann:failover_uct_rcx_no_comp_impl

Conversation

@jeynmann

@jeynmann jeynmann commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What?

Allow an RC mlx5 error handler to return UCS_INPROGRESS so the transport does not immediately purge outstanding TX WQEs.

Why?

On endpoint failure, RC mlx5 currently always purges outstanding ops and returns both CQ and QP resources in one step. Failover needs those WQEs to stay posted so the UCP can inspect or recover them.

How?

Allows error handler return UCS_INPROGRESS to indicate that completions should not be purged imediately.
Split ep invalidate from #11684

@svc-ucx

svc-ucx commented Aug 26, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Tests BlueField on worker 3) · commit 2123627c

TL;DR: Two RC/mlx5 error-path gtests hung (3 min and 15 min of zero output, ending in the gtest watchdog Connection timed out - abort testing → SIGABRT/core), because the new "no completions" error-handling path in uct_rc_mlx5_iface_handle_failure() stops purging/completing outstanding TX ops, so flush(CANCEL) completions and UCP requests never complete. Fix: complete (or purge) the outstanding send/flush ops when entering the UCS_INPROGRESS/NO_COMPLETIONS state instead of leaving them pending forever.

Full analysis

Summary: shm_ib/test_ucp_peer_failure.force_close/0 failed after 540 s and rc_mlx5/uct_cancel_test.am_zcopy/0 hung 15 min in uct_cancel_test::flush_and_reconnect() until the test watchdog aborted the gtest binary (make: *** [Makefile:4713: test] Aborted (core dumped)).

Root cause: Evidence of a hang, not slowness: the log jumps 07:27:15 → 07:42:15 with no output while in test_flush.cc:767 (while (done.count != 1) progress();), and 07:19:53 → 07:22:53 → 07:25:54 (3-min "request did not complete on time" ticks) in force_close, followed by rc_ep.c:468 destroying txqp ... with uncompleted operation ... uct_rc_ep_send_op_completion_handler, an ucp_requests mpool leak and a leftover callbackq entry. All of these mean outstanding RC TX ops are never completed after a QP error. In the PR's new code, uct_rc_mlx5_iface_handle_failure() skips uct_rc_txqp_purge_outstanding() whenever the EP error handler returns UCS_INPROGRESS (sets UCT_RC_MLX5_EP_FLAG_NO_COMPLETIONS, src/uct/ib/mlx5/rc/rc_mlx5_iface.c:227-234) and also skips returning TX QP resources (:236-239), while the pre-existing early exit for ERR_HANDLER_INVOKED | FLUSH_CANCEL (:210-213) jumps past the purge too. Since every error CQE is funneled into handle_failure (uct_ib_mlx5_check_completion_with_err, src/uct/ib/mlx5/ib_mlx5.c:515-523), nothing ever invokes the flush-cancel completion added by uct_rc_txqp_add_flush_comp() (src/uct/ib/mlx5/rc/rc_mlx5_ep.c:773), so done.count never reaches 1 and UCP requests never complete.

Implicated commit: 2123627 "UCT/IB/MLX5: add support for no completions" (Zihao Zhao), building on 106bf44 "UCT/IB/MLX5: support err handle return inprogess"

File: src/uct/ib/mlx5/rc/rc_mlx5_iface.c:210-239 (with src/uct/ib/mlx5/rc/rc_mlx5_ep.c:726-775)

Suggested fix: In the UCS_INPROGRESS / UCT_RC_MLX5_EP_FLAG_NO_COMPLETIONS branch, do not leave the txqp outstanding queue untouched: at minimum complete the ops that can never receive a CQE — call uct_rc_txqp_purge_outstanding() (or a variant that only completes uct_rc_ep_flush_op_completion_handler / keepalive comps) for WQEs up to pi, and return the corresponding TX QP credits. Additionally, do not enter NO_COMPLETIONS mode when UCT_RC_EP_FLAG_FLUSH_CANCEL is set (the canceling flow must still complete), and make uct_rc_mlx5_base_ep_flush(UCT_FLUSH_FLAG_CANCEL) on an EP already in NO_COMPLETIONS state purge outstanding ops and complete the flush comp immediately rather than queueing a completion that no CQE will ever trigger. Also verify uct_rc_mlx5_ep_outstanding_purge() invokes the user completions of the purged ops so UCP requests are released.

Related: PR #11829 ("[DNM] UCT/IB/MLX5: support in progress error handler"), prior work PR #11668 ("UCT: outstanding purge implementation")

@jeynmann jeynmann changed the title [DNM] UCT/IB/MLX5: support in progress error handler [WIP][DNM] UCT/IB/MLX5: support in progress error handler Aug 26, 2026
@jeynmann
jeynmann force-pushed the failover_uct_rcx_no_comp_impl branch from 2123627 to 0a8d5c3 Compare August 26, 2026 12:05
@jeynmann jeynmann changed the title [WIP][DNM] UCT/IB/MLX5: support in progress error handler UCT/IB/MLX5: support in progress error handler Aug 27, 2026
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated
*/
ucs_assertv(bb_num > 0, "hw_ci=%d prev_sw_pi=%d available=%d bb_num=%d",
hw_ci, txwq->prev_sw_pi, txqp->available, bb_num);
uct_ib_mlx5_txwq_t *txwq = &ep->tx.wq;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Switch from the update_bb formula to incremental hw_ci - prev_hw_ci assumes strict monotonicity.

uint16_t bb_num = hw_ci - prev_hw_ci;
ucs_assertv(bb_num > 0, ...);
txwq->hw_ci = hw_ci;

The previous update_tx_res derived bb_num from prev_sw_pi/available, which was robust to repeated completions reporting the same counter. The new code requires every reported hw_ci/pi to strictly advance past the stored hw_ci. In handle_failure the flushed error CQEs feed pi into this function via out_update_tx_res, and this now also runs on the repeated-failure path (ERR_HANDLER_INVOKED/FLUSH_CANCEL -> goto out_update_tx_res). If a subsequent error CQE reports the same wqe_counter, bb_num == 0 trips the assert. Worth confirming that flushed error CQEs always carry a strictly increasing counter, otherwise this is a regression from the old formula.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's strict monotonicity. HW should not generates 2 cqes for 1 wqe.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_iface.c Outdated
Comment thread src/uct/ib/mlx5/rc/rc_mlx5_iface.c
@svc-nvidia-pr-review

Copy link
Copy Markdown

Notes / no action: Making hw_ci and the new ft_ci always-compiled (dropping the #if UCS_ENABLE_ASSERT) is consistent since hw_ci is now functional, not debug-only; uct_ib_mlx5_txwq_update_bb unconditionally writing hw_ci is harmless for the UD/DC paths that still use it. VFS now exposing ft_ci/hw_ci unconditionally is fine. ep->flags = 0 is correctly initialized in UCS_CLASS_INIT_FUNC(uct_rc_mlx5_base_ep_t) before any consumer.

Test coverage: This is a behavioral change to the RC error/purge path. I did not find an added regression test in the diff. Since the new ep_outstanding_purge path depends on an external plugin, please state which CI job or existing error-injection test exercises the UCS_INPROGRESS failure -> plugin purge -> update_tx_qp_res flow, or add a focused test for the non-plugin ep_outstanding_purge call (finding 1) which is reachable without any plugin.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated
@svc-nvidia-pr-review

Copy link
Copy Markdown

Test coverage: no regression test in the diff for the deferred UCS_INPROGRESS → plugin purge → update_tx_qp_res flow, and the non-plugin path (public API with unset ft_ci) is reachable without any plugin. Please add a focused test for the plugin-less ep_outstanding_purge call, or point to the CI/error-injection job that exercises it.

@svc-ucx

svc-ucx commented Aug 27, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Tests roce on worker 1) · commit 4107de39

TL;DR: The roce on worker 1 job failed in run_ucx_perftest_fault_tolerance because the helper ucx_drop_tool could not create an RDMA_TX steering flow table on mlx5_0 (FW syndrome 0x31ed04, "Remote I/O error") and exited immediately — so no traffic was ever blocked, no failover/reconfiguration happened, and the test's verify_active_devices for cfg#1 failed. This is an environment/test-harness problem (RoCE host swx-rain04 doesn't allow/support the tool's flow table), not a defect in the PR's failover code; the script must check the drop tool started successfully and skip/report instead of failing the PR.

Full analysis

Summary: contrib/test_jenkins.shrun_ucx_perftest_fault_tolerance returned 1 with "Error: Failed to find protocol configuration count 1 in standard out" after /hpc/local/oss/ucx/fault_tolerance/ucx_drop_tool failed to install its flow table.

Root cause: At 11:49:40 the log shows:

  • ucx_drop_tool.c:409 UCX ERROR failed to create TX RDMA flow table, syndrome 0x31ed04: Remote I/O error
  • ucx_drop_tool.c:661 UCX ERROR failed to setup flow table for device 0: Input/output error

The drop tool therefore died right after being backgrounded (confirmed later by kill: (2675014) - No such process). Because traffic on mlx5_0 was never dropped, ucx_perftest kept running happily on both rails (the trailing [thread 0] lines show steady ~82 µs / 12 GB/s progress with no reconfiguration), so no second protocol-config block (| perftest self cfg#1 |) was ever printed. parse_active_devices then returned found=0 and the test failed.

The script has no check on the drop tool's success: line 549 launches ${drop_tool} ... & and immediately proceeds to sleep 10 + verify_active_devices, so any failure of the tool (unsupported FW/steering domain, missing privileges, RoCE-specific SL/TC matching not available on this host) is reported as a UCX failover regression. Note also that on a RoCE worker, matching on IB service level (-s 11) is not meaningful — only the traffic class/DSCP path is, which makes this job the most likely place for the tool's RDMA_TX flow-table setup to be rejected.

Implicated commit: [REDACTED:Hex High Entropy String] — "CI/FT: Adds fault tolerance CI test (#11570)", william-gallagher-nv (added run_ucx_perftest_fault_tolerance; PR head commit [REDACTED:Hex High Entropy String] on failover_uct_rcx_no_comp_impl is not implicated by the log)

File: contrib/test_jenkins.sh:549 (drop tool launch, unchecked) — failure surfaces at contrib/test_jenkins.sh:557 via parse_active_devices at contrib/test_jenkins.sh:430

Suggested fix:

  1. Make the drop tool failure explicit and non-fatal in CI. Redirect its output to a file, verify the process is still alive and reported success before verifying failover, e.g.:
${drop_tool} -d ${dev} -s ${sl} -t ${tc} > drop_tool.${dev}.log 2>&1 &
drop_pid=$!
sleep 2
if ! kill -0 ${drop_pid} 2>/dev/null; then
    cat drop_tool.${dev}.log
    echo "Warning: drop tool failed on ${dev} (flow table not supported/permitted) - skipping FT test"
    kill_background_processes "${background_pids[@]}"
    return 0   # or 'continue' if other devices should still be tried
fi
background_pids+=(${drop_pid})
  1. Gate the test on the transport/link layer: skip run_ucx_perftest_fault_tolerance on RoCE hosts (or when the device's link_layer is Ethernet) unless the drop tool is known to support DSCP/TC-only matching there, so the IB workers keep the coverage.
  2. Separately, ask the CI/infra owner to check why mlx5_0 on swx-rain04 rejects the RDMA_TX flow table (FW syndrome 0x31ed04 — likely missing FW steering capability for the RDMA_TX domain or insufficient privileges in the agent container).
  3. Re-run this PR job once the harness is fixed; the perftest itself showed no errors, so there is no evidence of a failover regression from commit [REDACTED:Hex High Entropy String].

Related: PR #11570 (added the fault tolerance CI test); PR #11829 (this build) — no existing issue found for syndrome 0x31ed04, worth opening one against the CI/FT test.

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 3c9f03e0-83cc-4d07-8d6c-d0be882e8235 in the triage console for the audit trail.

Comment thread src/uct/ib/mlx5/ib_mlx5.c
Comment on lines +696 to +702
txwq->curr = txwq->qstart;
txwq->sw_pi = 0;
txwq->prev_sw_pi = UINT16_MAX;
txwq->ft_ci = UINT16_MAX;
txwq->hw_ci = UINT16_MAX;
#if UCS_ENABLE_ASSERT
txwq->hw_ci = 0xFFFF;
txwq->flags = 0;
txwq->flags = 0;

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.

Too many spaces (about 4 too many)

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated
uct_rc_mlx5_base_ep_t *ep = ucs_derived_of(tl_ep, uct_rc_mlx5_base_ep_t);
ucs_status_t status = uct_ib_mlx5_ext_ep_outstanding_purge(tl_ep, params);

uct_rc_mlx5_ep_update_tx_qp_res(ep, ep->tx.wq.ft_ci);

@roiedanino roiedanino Aug 27, 2026

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.

Should we gate here with NO_COMPLETIONS flag in the EP? otherwise ft_ci value would be UINT16_MAX
we should also clean NO_COMPLETIONS flag after that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When err callback returns in progress, UCP decides when to purge, clean up, and destroy the endpoint. The endpoint does not return to normal completion processing. UCT don't need touch this anymore.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated
uct_ep_h tl_ep, const uct_ep_outstanding_purge_params_t *params)
{
uct_rc_mlx5_base_ep_t *ep = ucs_derived_of(tl_ep, uct_rc_mlx5_base_ep_t);
ucs_status_t status = uct_ib_mlx5_ext_ep_outstanding_purge(tl_ep, params);

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.

So that API stays in EXT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It can be removed in the PR for the purge implementation. In this PR it keeps the behavior the same as master. IMO, cleanup plugin call is not this PR’s responsibility.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

UCT_RC_MLX5_BASE_EP_DECL(tl_ep, iface, ep);

return uct_ib_mlx5_modify_qp_state(&iface->super.super, &ep->tx.wq.super,
IBV_QPS_ERR);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

uct_rc_mlx5_ep_outstanding_purge asserts ep->no_comp on a public API path. This function is installed as the transport's public ep_outstanding_purge op (uct_ep_outstanding_purge). The API contract does not require the ep to have first entered the deferred-completion (no_comp) state. If a caller invokes uct_ep_outstanding_purge on an ep that never failed, ep->no_comp == 0: in debug build the ucs_assert(ep->no_comp) fires; in release build ep->tx.wq.ft_ci is still UINT16_MAX (its reset value) so uct_rc_mlx5_ep_update_tx_qp_res(ep, ft_ci) computes wrong tx-qp resources. Please guard this path (return an error, or early-out) when no_comp is not set, instead of asserting it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

uct_rc_mlx5_ep_outstanding_purge should only be called when error callback return in progress.

}

ucs_status_t uct_rc_mlx5_base_ep_fc_ctrl(uct_ep_t *tl_ep, unsigned op,
uct_rc_pending_req_t *req)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ft_ci passed to update_tx_qp_res on purge leaves the outstanding range's tx-qp credits unreclaimed. ft_ci is set in handle_failure to the hw_ci of the first outstanding WQE: ft_ci = prev_sw_pi - (bb_max - available). Feeding that same value back into available = bb_max - (prev_sw_pi - ft_ci) recomputes exactly the pre-failure available, so the purge returns zero tx-qp resources for the just-purged range. If the intent is to reclaim the purged WQEs, this looks like it should pass sw_pi (end of the range), not ft_ci. Can you confirm the intended value here, and whether the ep is expected to be destroyed right after so the unreclaimed credits don't matter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Purge will be implemented later PR. ft_ci will be updated in Purge.

@svc-nvidia-pr-review

Copy link
Copy Markdown

src/uct/ib/mlx5/rc/rc_mlx5.h

Minor: bool no_comp; is added to uct_rc_mlx5_base_ep_t outside the tx sub-struct. UCX generally uses uint8_t for such flags (see uct_rc_ep_t::flags); consider uint8_t no_comp; for consistency. Not blocking.

@svc-ucx

svc-ucx commented Aug 28, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Basic_compile Basic compile) · commit e428402f

TL;DR: The Basic compile job failed with -Werror=implicit-function-declaration in src/uct/ib/mlx5/rc/rc_mlx5_ep.c:825 — the new uct_rc_mlx5_ep_outstanding_purge() calls uct_ib_mlx5_ext_ep_outstanding_purge() without the prototype from uct/ib/mlx5/ib_mlx5_ext.h being visible; add that #include to rc_mlx5_ep.c (or rebase, since the current branch tip already has it at line 17).

Full analysis

Summary: Build of src/uct/ib/mlx5/rc/libuct_ib_mlx5_la-rc_mlx5_ep.lo aborted; make returned Error 1/2 and the job exited with code 2.

Root cause: Compiler output (log 02:12:31.83):

rc_mlx5_ep.c: In function 'uct_rc_mlx5_ep_outstanding_purge':
rc_mlx5_ep.c:825:14: error: implicit declaration of function
  'uct_ib_mlx5_ext_ep_outstanding_purge'; did you mean 'uct_rc_mlx5_ep_outstanding_purge'?
  [-Werror=implicit-function-declaration]
rc_mlx5_ep.c:825:14: error: nested extern declaration of
  'uct_ib_mlx5_ext_ep_outstanding_purge' [-Werror=nested-externs]
cc1: all warnings being treated as errors

The build uses -Wall -Werror -Werror-implicit-function-declaration -Wnested-externs, so a missing prototype is fatal. uct_ib_mlx5_ext_ep_outstanding_purge() is defined in src/uct/ib/mlx5/ib_mlx5_ext.c:265 and declared only in src/uct/ib/mlx5/ib_mlx5_ext.h:203-204 (both added by [REDACTED:Hex High Entropy String], "UCT: outstanding purge implementation (#11668)"). In the compiled snapshot of the PR/merge, rc_mlx5_ep.c invokes it (in the new uct_rc_mlx5_ep_outstanding_purge(), repo line 794) while ib_mlx5_ext.h was not included in that translation unit — the "nested extern declaration" diagnostic confirms no prototype was in scope. Note the compiled file is ~31 lines longer before the call site than the file I read, i.e. the built tree is the refs/pull/11829/merge result, so this is very likely a semantic merge/rebase artifact where the #include line was lost while the new call survived. Everything else in the log (configure, ucm/ucs/uct) is clean — this is a pure compile break, not an environment or timing issue.

Implicated commit: e428402 "UCT/IB/MLX5: address comments" (Zihao Zhao), on top of b27def8 "UCT/IB/MLX5: add support for no completions" (Zihao Zhao) — PR #11829

File: src/uct/ib/mlx5/rc/rc_mlx5_ep.c:825 (call site; repo copy line 794) — prototype at src/uct/ib/mlx5/ib_mlx5_ext.h:203

Suggested fix: Ensure rc_mlx5_ep.c includes the ext header before using the helper:

#include <uct/ib/mlx5/ib_mlx5_log.h>
#include <uct/ib/mlx5/ib_mlx5_ext.h>   /* <- required for
                                          uct_ib_mlx5_ext_ep_outstanding_purge() */

The branch tip already carries this include at rc_mlx5_ep.c:17, so rebase the PR onto current master (refs/pull/11829/merge regenerated) and re-run CI; verify the merge result still contains the include. To catch this class of failure earlier, build locally with the same flags (--enable-debug-style -Werror -Werror-implicit-function-declaration) after a merge with master.

Related: PR #11829 (this PR), PR #11668 (added uct_ib_mlx5_ext_ep_outstanding_purge), PR #11684 (related "no completion invalidate" work)

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id c02df64c-c033-4c12-ad37-6adfab070336 in the triage console for the audit trail.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated

status = uct_ib_mlx5_ext_ep_outstanding_purge(tl_ep, params);

uct_rc_mlx5_ep_update_tx_qp_res(ep, ep->tx.wq.ft_ci);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ft_ci was set in handle_failure to prev_sw_pi - (bb_max - available), i.e. the CI of the first outstanding WQE. Passing it here yields available = bb_max - (prev_sw_pi - ft_ci) = bb_max - outstanding, which equals the current available, so update_tx_qp_res adds 0 — the outstanding TX resources are never restored after the purge. Is the plugin expected to advance ep->tx.wq.ft_ci (to prev_sw_pi) before returning? If so, can we add a short comment stating that contract; if not, should this pass the post-purge CI instead of the range start?

@svc-nvidia-pr-review

Copy link
Copy Markdown

No regression test in the diff exercises the new deferred UCS_INPROGRESSerr_handler_inprogressuct_rc_mlx5_ep_outstanding_purge flow, and the existing test_ib_mlx5_ext.cc gtest is left unmodified even though this PR changes the op it depends on. Please update/point to the test that covers the new behavior; the existing one appears to break.

Note (no action): Making hw_ci/ft_ci always-compiled (removing #if UCS_ENABLE_ASSERT) and exposing them in VFS is consistent now that hw_ci is functional.

Note (no action): err_handler_inprogress initialized to 0 in UCS_CLASS_INIT_FUNC(uct_rc_mlx5_base_ep_t) before any consumer — OK. Minor: it is declared bool but assigned 0; consider uint8_t/false for consistency with other UCX flag fields (already raised in a prior review for a similar field).

Note (no action): The signed/unsigned math in uct_rc_mlx5_ep_update_tx_qp_res (available - prev_available) is within range and fine.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_iface.c Outdated
.ep_get_device_ep = (uct_ep_get_device_ep_func_t)ucs_empty_function_return_unsupported,
.ep_put_sgl_zcopy = uct_rc_mlx5_ep_put_sgl_zcopy,
.ep_outstanding_purge = uct_ib_mlx5_ext_ep_outstanding_purge
.ep_outstanding_purge = uct_rc_mlx5_ep_outstanding_purge

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rewiring .ep_outstanding_purge to uct_rc_mlx5_ep_outstanding_purge, which begins with ucs_assert(ep->err_handler_inprogress) and then reads ep->tx.wq.ft_ci, makes the public uct_ep_outstanding_purge() API path unsafe whenever the ep is not mid error-handling: it aborts in debug builds and, in release, feeds a stale/uninitialized ft_ci into uct_rc_mlx5_ep_update_tx_qp_res, corrupting QP resource accounting. The purge dispatch should tolerate err_handler_inprogress == 0 (early-return / delegate to the external purge without touching ft_ci) rather than requiring callers to route around it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Purge should be called only after error callback return UCS_INPROGRESS.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated
IBV_QPS_ERR);
}

ucs_status_t uct_rc_mlx5_ep_outstanding_purge(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

uct_rc_mlx5_ep_outstanding_purge begins with ucs_assert(ep->err_handler_inprogress) then reads ep->tx.wq.ft_ci; when the ep is not mid error-handling this aborts in debug builds and feeds a stale/uninitialized ft_ci into uct_rc_mlx5_ep_update_tx_qp_res in release builds. It should tolerate err_handler_inprogress == 0 instead of requiring callers to bypass the public API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Purge should be called only after error callback return UCS_INPROGRESS.

Comment thread test/gtest/uct/ib/test_ib_mlx5_ext.cc Outdated
scoped_log_handler wrap_err(wrap_errors_logger);
EXPECT_EQ(UCS_ERR_INVALID_PARAM,
uct_ep_outstanding_purge(m_e1->ep(0), &params));
uct_ib_mlx5_ext_ep_outstanding_purge(m_e1->ep(0), &params));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This test now calls uct_ib_mlx5_ext_ep_outstanding_purge() directly instead of the public uct_ep_outstanding_purge(). That change is a workaround for the newly-broken public entry point — it hides the regression and removes public-path coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will be removed in later PRs.

Comment thread test/gtest/uct/ib/test_ib_mlx5_ext.cc Outdated
params.arg = &callback_invoked;

ASSERT_UCS_OK(uct_ep_outstanding_purge(m_e1->ep(0), &params));
ASSERT_UCS_OK(uct_ib_mlx5_ext_ep_outstanding_purge(m_e1->ep(0), &params));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This test now calls uct_ib_mlx5_ext_ep_outstanding_purge() directly instead of the public uct_ep_outstanding_purge(), bypassing the newly-broken public entry point and removing public-path coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will be removed in later PRs.

@svc-nvidia-pr-review

Copy link
Copy Markdown

Test coverage gap: no regression test in the diff exercises the deferred UCS_INPROGRESS -> err_handler_inprogress -> uct_rc_mlx5_ep_outstanding_purge -> uct_rc_mlx5_ep_update_tx_qp_res flow; it depends on an external plugin not present in-tree. Additionally, the plugin-less/public-API path is reachable without any plugin and is exactly the case the modified gtest now avoids. Please add a focused test for uct_ep_outstanding_purge() on an RC mlx5 ep outside error handling, or point to the CI/error-injection job that covers the supported-INPROGRESS branch.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_ep.c Outdated
@svc-nvidia-pr-review

Copy link
Copy Markdown

Test coverage gap (still open): No regression test in the diff exercises the deferred UCS_INPROGRESS -> err_handler_inprogress -> uct_rc_mlx5_ep_outstanding_purge -> uct_rc_mlx5_ep_update_tx_qp_res flow; it depends on an out-of-tree plugin. Additionally, the existing test_ib_mlx5_ext.cc was changed to call uct_ib_mlx5_ext_ep_outstanding_purge directly instead of the public uct_ep_outstanding_purge, which sidesteps the newly-inserted wrapper and its ucs_assert(err_handler_inprogress) - masking the plugin-less public-API path rather than covering it. Please point to the CI/error-injection job that covers the supported-INPROGRESS branch, or add a focused test.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

UCT_RC_MLX5_BASE_EP_DECL(tl_ep, iface, ep);

return uct_ib_mlx5_modify_qp_state(&iface->super.super, &ep->tx.wq.super,
IBV_QPS_ERR);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

uct_rc_mlx5_ep_outstanding_purge asserts ep->err_handler_inprogress, but the public uct_ep_outstanding_purge API routes through it and is reachable outside error handling (fresh ep → err_handler_inprogress == 0 → debug abort).

bool callback_invoked = false;
uct_ep_outstanding_purge_params_t params = {};

{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The gtest was changed from uct_ep_outstanding_purge to uct_ib_mlx5_ext_ep_outstanding_purge, sidestepping the new wrapper and its assert rather than covering the public-API path — which is exactly the reachable case in Finding 1.

@svc-nvidia-pr-review

Copy link
Copy Markdown

No regression test in the diff exercises the deferred UCS_INPROGRESS → err_handler_inprogress → uct_rc_mlx5_ep_outstanding_purge → uct_rc_mlx5_ep_update_tx_qp_res flow; it depends on an out-of-tree plugin.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5_iface.c
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Review complete — no issues found.

@svc-ucx

svc-ucx commented Aug 29, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (AddressSanitizer roce on worker 0) · commit 7ae7ad87

TL;DR: The gtest process hung for exactly 900 s inside ucp_worker_progress during shm_gga/test_ucp_loopback.envelope/20 <shm,gga/tag_bw_b> and was killed by the gtest watchdog — the GGA/rc_mlx5 TX CQ never produced the completion the worker was waiting for, which points at this PR's new "no completions" (unsignaled WQE) send path in rc_mlx5.inl. Fix: guarantee at least one signaled WQE (MLX5_WQE_CTRL_CQ_UPDATE) per outstanding batch instead of dropping CQ_UPDATE whenever comp == NULL.

Full analysis

Summary: roce on worker 0 (AddressSanitizer) failed with make: *** [Makefile:4713: test] Aborted (core dumped) after the gtest watchdog aborted a hung test: test_helpers.cc:57: Failure / Connection timed out - abort testing.

Root cause: Log timeline proves a hang, not slowness:

  • 04:49:21.191 [ RUN ] shm_gga/test_ucp_loopback.envelope/20 <shm,gga/tag_bw_b> is the last application line.
  • The very next line is 05:04:21.194 ... Connection timed out - abort testing — a 15-minute silent gap, matching watchdog_timeout = 900. (test/gtest/common/test_helpers.cc:31), i.e. the watchdog killed the watched thread; SIGABRT/backtrace is the symptom.
  • The backtrace shows where the thread was spinning: ucp_worker_progresslibuct_ib.so(+0xa9a44) (the mlx5 iface progress/CQ-poll path) → ucs_debug_asan_validate_address. So the worker was polling an IB (GGA) CQ that never delivered a completion, so the UCP request/flush never finished.
  • The test configuration is shm,gga, so the only IB transport in play is gga_mlx5, which is built on the rc_mlx5 base TX path that this PR rewrites. Commit b27def8e "UCT/IB/MLX5: add support for no completions" changed uct_rc_mlx5_base_ep_zcopy_post to request a CQE only when a user completion is supplied (fm_ce_se = (comp == NULL) ? wqe_flags : (wqe_flags | MLX5_WQE_CTRL_CQ_UPDATE), rc_mlx5.inl:2018), and added signaled-PI/credit bookkeeping (txwq->sig_pi at rc_mlx5.inl:525-527, uct_rc_mlx5_iface_update_tx_cq_res / uct_rc_mlx5_ep_update_tx_qp_res at rc_mlx5.inl:1933-1962). With unsignaled WQEs, txwq->hw_ci only advances when some later WQE is signaled; if the tail of the send queue is unsignaled and no further send is posted, no TX CQE is ever generated, QP/CQ credits are never returned and the outstanding operation is never completed — the worker then progresses forever, exactly as observed. Nothing else in the log (no error CQE, no peer failure, no ASAN report) indicates any other fault, and the preceding ~4000 tests passed.

Implicated commit: b27def8e "UCT/IB/MLX5: add support for no completions" (Zihao Zhao), on branch failover_uct_rcx_no_comp_impl; tested head 7ae7ad87 (Zihao Zhao)

File: src/uct/ib/mlx5/rc/rc_mlx5.inl:2018 (and the signaled-PI/credit accounting at src/uct/ib/mlx5/rc/rc_mlx5.inl:525-527, 1933-1962)

Suggested fix:

  1. Never allow a send queue to end with only unsignaled WQEs. In uct_rc_mlx5_base_ep_zcopy_post (and any other new comp == NULL path), keep forcing MLX5_WQE_CTRL_CQ_UPDATE when the number of unsignaled BBs since txwq->sig_pi crosses a threshold (e.g. sw_pi - sig_pi >= bb_max/2) in addition to uct_rc_iface_tx_moderation(), so hw_ci/CQ credits are always eventually reclaimed.
  2. Force a signaled WQE (or the existing signaled NOP) whenever an ep has unsignaled outstanding WQEs at flush/fence time, and assert in uct_rc_mlx5_ep_update_tx_qp_res that sw_pi - sig_pi cannot exceed the moderation window — that assert would have caught this in the ASAN build instead of hanging for 15 minutes.
  3. To confirm before changing code: rerun gtest --gtest_filter='shm_gga/test_ucp_loopback.envelope*' on this branch vs. master with UCX_RC_TX_MODERATION=1 and UCX_LOG_LEVEL=trace_poll; if the hang disappears with aggressive moderation, the missing-CQ_UPDATE diagnosis is confirmed.

Related: PR #11829 (this PR, "UCT/IB/MLX5: support in progress error handler"); base-path context in #11821 (RC send PSN tracking) which the branch merged just before this run.

@roiedanino

Copy link
Copy Markdown
Contributor

@jeynmann it seems like the CI failures are related to this PR

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Review complete — no issues found.

@jeynmann

jeynmann commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

@jeynmann it seems like the CI failures are related to this PR

I didn't change anything on rc_v, it fails with timed out, so it seems more like a CI env issue.

Comment thread src/uct/ib/mlx5/rc/rc_mlx5.h Outdated
Comment on lines +212 to +213
ucs_status_t uct_rc_mlx5_ep_outstanding_purge(
uct_ep_h tl_ep, const uct_ep_outstanding_purge_params_t *params);

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.

should probably remove this, will be added in am_short purge PR
#11857

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Review complete — no issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants