Skip to content

UCP/CORE: Purge held outstanding work with the stored RX token - #12008

Merged
evgeny-leksikov merged 8 commits into
openucx:masterfrom
evgeny-leksikov:ucp_tf_dummy_purge_after_tokens_exchange
Oct 6, 2026
Merged

evgeny-leksikov merged 8 commits into
openucx:masterfrom
evgeny-leksikov:ucp_tf_dummy_purge_after_tokens_exchange

Conversation

@evgeny-leksikov

Copy link
Copy Markdown
Contributor

What?

Purge held outstanding work with the stored RX token

Why?

Preparation to replaying the undelivered operations. Moved from #12005 to be under 500 lines limit.

How?

A lanes-address reply or ACK carries the peer receive position of a held
lane. Purge that lane with the stored token and destroy the endpoint.

@svc-nvidia-pr-review

Copy link
Copy Markdown

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

Comment thread src/ucp/core/ucp_ep.c Outdated
}

uct_ep_destroy(tf->uct_ep);
tf->uct_ep = NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

after the purge the lane stays UCP_EP_TF_LANE_HELD with its tokens, so the next REQUEST republishes a tx_token of a destroyed ep (ucp_ep_tf_set_request_id + ucp_ep_tf_tx_len only check HELD), and a later ucp_ep_tf_hold() on the same lane trips ucs_assert(tf->state == UCP_EP_TF_LANE_EMPTY) in ucp_ep_tf_claim() and overwrites both token pointers. pls release the lane here like ucp_ep_tf_cleanup() does: free tokens, reset state/request_id/peer_id/rsc_index and handle deactivate_cfg_index.

Comment thread src/ucp/wireup/wireup.c Outdated
* the trailer would not know the message type either */
* the trailer would not know the message type either. The reply may
* still have stored an RX token for our held lanes. */
ucp_ep_tf_lanes_purge_outstanding(ep,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

purge call in the !ucp_wireup_ep_supports_tokens(ep) branch: request_id is always 0 here, because ucp_wireup_parse_lanes_addr() returns before setting it when the peer adds no trailer, and ucp_ep_tf_lanes_purge_outstanding() drops request_id == 0 - so this call can never purge anything. pls drop it (and the comment above it).

Comment thread src/ucp/core/ucp_ep.c
tf->uct_ep = NULL;
}

void ucp_ep_tf_lanes_purge_outstanding(ucp_ep_h ep, ucp_lane_map_t lanes,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
void ucp_ep_tf_lanes_purge_outstanding(ucp_ep_h ep, ucp_lane_map_t lanes,
void ucp_ep_tf_lanes_purge_outstanding(ucp_ep_h ep, ucp_lane_map_t lanes,
uint32_t request_id, int from_ack)

minor: function arguments are indented on column (docs/CodeStyle.md); same for the prototype in ucp_ep.h.

Comment thread src/ucp/core/ucp_ep.c Outdated
static void
ucp_ep_tf_purge_replay_cb(const uct_ep_op_info_t *op_info, void *arg)
{
ucs_error("ep %p: replaying purged operation %p is not implemented", arg,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

minor: pls use ucs_diag here (ucs_error in ucp_ep_tf_purge_replay_cb) - this fires once per undelivered operation on every token failover, and UCT already completes those ops with UCS_ERR_CANCELED (uct_rc_mlx5_ep_outstanding_complete_send_ops), so it is not an error the program cannot continue from.

Comment thread src/ucp/wireup/wireup.c
static ucs_status_t
ucp_wireup_read_token_section(ucp_lane_map_t lane_map, const void *section,
size_t avail, size_t *consumed_p,
ucp_wireup_lane_token_t *slots)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ucp_wireup_read_token_section: can we extend test_ucp_wireup_token_section to cover the new slot-filling path (lane indexing and a token cut in the middle)? DISABLED_test_ucp_fault_tolerance_tf is still disabled, so today nothing exercises the parsed tokens.

@svc-nvidia-pr-review

Copy link
Copy Markdown

Process caveat: this environment has no gh/network access, so the paginated inline comments, issue comments, and reviews for PR #12008 could not be fetched. Duplicate detection against prior discussion could not be completed, and the review cannot be submitted with gh api. The draft is therefore for you to dedupe and post. Suggested review event: COMMENT (no blockers).

Things checked and not flagged: Trailer layout/versioning is still gated by UCP_WIREUP_ADDR_TOKEN_MIN_DST_VERSION, lengths precede tokens, addresses stay last — wire compatibility with older peers is preserved. slots[lane]/rx_tokens[lane] indexing from a wire-supplied lane map is in bounds (UCP_MAX_LANES == 64, ucp_lane_map_t is 64-bit), and ucp_ep_tf_get()/ucp_worker_iface() both return NULL for out-of-range lane/UCP_NULL_RESOURCE. request_id vs peer_id matching in save_rx/purge_outstanding is consistent with the REQUEST/REPLY/ACK direction; the || in ucp_ep_tf_tx_published is harmless because the TX token is per-lane, not per-exchange. ucp_ep_tf_lane_wiface() preferring tf->rsc_index is needed and correct: uct_rc_mlx5_iface_query_rx_token resolves the QP on the old lane's iface, which can differ from the rebuilt lane's device. No leak on the rx_tokens[] buffers: both process_lanes_addr_request and process_lanes_addr_reply have no early return between derive and ucp_wireup_free_lane_tokens. Size is within the 500 added-line limit and the change is scoped to the token exchange.

Residual coverage gap: the end-to-end flow (hold → derive → publish → purge) only runs with RC mlx5 DEVX hardware and the FAILOVER_MODE=token suite is still DISABLED_, so nothing in CI executes the new purge path.

Comment thread src/ucp/core/ucp_ep.c Outdated
/* Undelivered operations are reported here. Keeping them for replay is left
* to a later change. */
static void
ucp_ep_tf_purge_replay_cb(const uct_ep_op_info_t *op_info, void *arg)

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.

For an undelivered PUT_ZCOPY, UCT places the user completion in op_info, then releases the send operation without invoking that completion on the canceled path. The request can remain outstanding indefinitely.

Comment thread src/ucp/core/ucp_ep.c
continue;
}

params.field_mask = UCT_EP_OUTSTANDING_FIELD_RX_TOKEN |

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.

After purging and destroying its UCT EP, the lane retains its tokens and state. A later request can publish the old TX token, and holding that lane again hits the EMPTY assertion.

@evgeny-leksikov
evgeny-leksikov force-pushed the ucp_tf_dummy_purge_after_tokens_exchange branch from 946c784 to 1e98dc2 Compare October 2, 2026 09:51
@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

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

@svc-ucx

svc-ucx commented Oct 2, 2026

Copy link
Copy Markdown

🤖 CI Triage Agent — UCX PR (AddressSanitizer roce on worker 3) · commit 6708ee27

TL;DR: The ASAN job did not run out of time — shm_rc_dc/test_ucp_rma_order.put_ordering/0 <self,shm,rc,dc> hung for exactly 15 minutes inside a blocking strong fence (ucp_rma_wait busy-looping on ucp_worker_progress) until the gtest watchdog fired SIGABRT; the remote-completion flush (UCT_FLUSH_FLAG_REMOTE) never completed on this branch's token-failover changes.

Full analysis

Summary: gtest aborted via watchdog ("Connection timed out - abort testing") during shm_rc_dc/test_ucp_rma_order.put_ordering/0, causing make: *** [Makefile:4809: test] Aborted (core dumped) and Bash exited with code '2'.

Root cause: The log shows a hang, not slowness. Timestamps:

  • 15:29:27.9313938Z [ RUN ] shm_rc_dc/test_ucp_rma_order.put_ordering/0 <self,shm,rc,dc>
  • 15:44:27.9084617Z /__w/1/s/contrib/../test/gtest/common/test_helpers.cc:57: Failure

That is a 900-second gap with zero output — by far the largest gap in the run (every other test completes in 1–1800 ms). test_helpers.cc:57 is the gtest watchdog's ADD_FAILURE() << strerror(ret) << " - abort testing" followed by pthread_kill(..., SIGABRT), which is why the process caught signal 6.

The backtrace names the hung operation precisely:

7  libucp.so.0(ucp_worker_fence+0xaa8)
6  libucp.so.0(ucp_worker_progress+0x144)
5  libuct_ib_mlx5.so.0(+0xbfbdd)

The frames at ucp_worker_fence+0xaa8 correspond to the strong-fence path in `src/

A lanes-address reply or ACK carries the peer receive position of a held
lane. Purge that lane with the stored token and destroy the endpoint.
Replaying the undelivered operations is left for a later change.
A successful purge destroyed the transport endpoint and left the lane held, so the next request republished that endpoint's TX token. Drop the reply purge that cannot match a request id, complete an undelivered zcopy, and log the missing replay at diag.
A token means that lane already failed remotely. Leave the transport endpoint on the lane until the error handler holds it. Lanes that cannot carry a token still take the software discard path.
@evgeny-leksikov
evgeny-leksikov force-pushed the ucp_tf_dummy_purge_after_tokens_exchange branch from 6708ee2 to 98a57b3 Compare October 2, 2026 21:29
@svc-nvidia-pr-review

Copy link
Copy Markdown

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

Comment thread src/ucp/core/ucp_ep.c Outdated
ucs_assert(tf != NULL);
tf->peer_id = peer_id;
tf->state = UCP_EP_TF_LANE_INVALIDATED;
tf->rsc_index = ucp_ep_get_rsc_index(ep, lane);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tf->rsc_index is recorded here, but ucp_ep_tf_lane_wiface() only prefers it when the state is HELD, so ucp_ep_tf_derive_rx()/ucp_ep_tf_save_rx() on an invalidated lane still resolve the iface from the current lane config; after a reconfig that can be a different device and the token is dropped. can we accept UCP_EP_TF_LANE_INVALIDATED there as well?

Comment thread src/ucp/wireup/wireup.c
&remote_address);
} else if (msg->type == UCP_WIREUP_MSG_LANES_ADDR_REPLY) {
ucs_assert(lanes_info != NULL);
ucp_wireup_invalidate_unaware_failed_lanes(ep, lanes_info,

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 invalidates lanes we still consider alive, and it runs before ucp_wireup_process_lanes_addr_reply() sends the ACK, so if the current am_lane is in that set the ACK goes out on a QP we just invalidated and the peer never purges. the REQUEST path deliberately does local failover first so "the reply rides the new am_lane" — can we send the ACK before invalidating?

Comment thread src/ucp/core/ucp_ep.c
/* A lane can enter the token path only when a later hold would accept it.
* Anything else stays on the software discard path. */
static int
ucp_ep_tf_lane_supported(ucp_ep_h ep, ucp_lane_index_t lane, uct_ep_h uct_ep)

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 repeats the gating of ucp_ep_tf_claim() almost line by line (mode, FAILED, err_mode, cm_lane, dst_version, token caps, tx_len range), so the two can silently drift. can we extract one helper and call it from both?

Comment thread src/ucp/core/ucp_ep.c Outdated
* puts a zcopy completion in op_info and does not invoke it when the
* operation is canceled, so complete it here. */
static void
ucp_ep_tf_purge_replay_cb(const uct_ep_op_info_t *op_info, void *arg)

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 is nearly identical to ucp_ep_tf_outstanding_purge_cb(); can we reuse that one with params.arg = UCS_STATUS_PTR(UCS_ERR_CANCELED) and drop the ucs_diag, which prints one line per purged operation?

Comment thread src/ucp/wireup/wireup.c
return;
}

lanes = ucp_wireup_lanes_with_tokens(lanes_info->requested_lane_map,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why are the reply's RX-token lanes part of the invalidate set? they name our own held lanes, and a held lane is always in ucp_ep_get_failed_lanes() (hold → detach → ucp_ep_reconfig_internal), so this term is masked out two lines below.

@svc-nvidia-pr-review

Copy link
Copy Markdown

Residual coverage gap: no test accompanies the change. The invalidate → derive → store → purge path needs RC mlx5 DEVX hardware and the FAILOVER_MODE=token gtest suite is still DISABLED_, so nothing in CI executes ucp_ep_tf_invalidate_lanes() or ucp_ep_tf_lanes_purge_outstanding().

@svc-nvidia-pr-review

Copy link
Copy Markdown

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

@svc-ucx

svc-ucx commented Oct 5, 2026

Copy link
Copy Markdown

🤖 CI Triage Agent — UCX PR (AddressSanitizer BlueField on worker 1) · commit 0d101789

TL;DR: The ASAN BlueField build died because gcc's cc1 was killed by a SIGSEGV ("internal compiler error") while compiling src/uct/ib/ud/verbs/ud_verbs.c — a toolchain/resource failure on the arm64 agent, not a code defect in PR #12008; retry the job and, if it recurs, lower build parallelism (or drop -O3 for ASAN) on the BlueField agent.

Full analysis

Summary: make failed at ud/verbs/libuct_ib_la-ud_verbs.lo with gcc: internal compiler error: Segmentation fault signal terminated program cc1, aborting the whole AddressSanitizer BlueField build.

Root cause: Evidence from the log (08:12:59 → 08:13:01):

  • gcc -O3 -g -fsanitize=address ... ud/verbs/ud_verbs.c was running concurrently with the ASAN compiles of ud/base/ud_ep.c and rc/verbs/rc_verbs_ep.c (the stage uses make V=1 -j${parallel_jobs}).
  • At 08:13:01 gcc reported internal compiler error: Segmentation fault signal terminated program cc1, i.e. the compiler front-end process itself was terminated by a fatal signal — not a compile error in UCX source. No warning/error was emitted for any UCX file, and the sibling compilations of the same translation units completed fine seconds later.
  • There is no hang: the build ran continuously from 08:11:57 to 08:13:04 with sub-second gaps, so this is not a timeout/wall-clock kill either.

This signature on an aarch64/BlueField agent with gcc-11 plus -fsanitize=address -O3 -g is characteristic of the compiler being OOM-killed or hitting a gcc-11 ICE under the ASAN instrumentation pass — an agent/toolchain issue. The PR under test (ucp_tf_dummy_purge_after_tokens_exchange, PR #12008) touches UCP tag/token logic and has nothing to do with src/uct/ib/ud/verbs/ud_verbs.c, which the recent history shows was last changed by unrelated AH-cache work (f53da7f, c849510).

Implicated commit: unknown — not attributable to commit [REDACTED:Hex High Entropy String]; the failing file is untouched by this PR. Environment-related; buildlib/tools/common.sh parallelism logic (cd36bd3 / f491f6b, Alexey Rivkin) is the relevant knob.

File: src/uct/ib/ud/verbs/ud_verbs.c (compilation unit that crashed cc1); build config at buildlib/tools/common.sh:47-55

Suggested fix:

  1. Re-run the "AddressSanitizer BlueField on worker 1" job first — ICE/OOM of cc1 is typically transient.
  2. If it reproduces, constrain the ASAN build on the low-memory BlueField agents: the parallel_jobs computation in buildlib/tools/common.sh:47-52 only caps by CPU count and NPROC, not by available RAM for non-container agents (nproc=$(nproc --all) at line 38). For ASAN builds set parallel_jobs=1/2, or extend the memory-based cap at lines 21-41 to apply to bare-metal agents too.
  3. Secondarily, reduce the optimization level for sanitizer builds (-O1/-O0 instead of -O3 together with -fsanitize=address) to cut compiler memory use, and/or move the BlueField ASAN job to a newer gcc (the GCC_MODULE knob in common.sh:13) since the crash is in gcc-11's cc1.
  4. Capture dmesg/OOM-killer output on the agent on failure to confirm whether cc1 was OOM-killed versus a genuine gcc bug.

Related: PR #12008 (the PR under test); #11915 (only loose textual match, not the same failure) — no existing issue tracking this ICE.

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 5f302140-777a-41aa-8dee-e1c29487c472 in the triage console for the audit trail.

Failover reconfig keeps each lane on its original transport resource, so token derive and save use the live endpoint config.
Hold copied the checks that decide whether a lane can carry a token, so the two paths could drift.
The token path had its own callback that completed a zcopy and logged every operation. The shared callback already completes with the status it is given.
@evgeny-leksikov
evgeny-leksikov marked this pull request as ready for review October 5, 2026 13:44
@svc-nvidia-pr-review

Copy link
Copy Markdown

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

Comment thread src/ucp/wireup/wireup.c
invalidated = 0;
if (request_id != 0) {
ucp_ep_tf_hold_lanes(ep, lanes_info->provided_lane_map, request_id);
invalidated = ucp_ep_tf_invalidate_lanes(ep,

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.

A lane is invalidated before the reply is sent. Same in reply path. If that's am_lane, its QP is put into error but ucp_wireup_msg_progress() still selects 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.

in this case it will be handled as a new lane failure or operation for retransmit on purge, both are not fatal and recoverable

@gleon99

gleon99 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

A 0-length peer token (e.g SW failover) still takes the token path

@evgeny-leksikov

Copy link
Copy Markdown
Contributor Author

A 0-length peer token (e.g SW failover) still takes the token path

ucp_ep_tf_lane_supported checks dst_version and tx_len
ucp_wireup_lanes_with_tokens checks buf and len of rx token

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.

4 participants