Skip to content

Fixes a race in the UCX backend between synchronous rkey unpack and endpoint teardown. - #1987

Merged
iyastreb merged 9 commits into
ai-dynamo:mainfrom
chaunceyjiang:fix/ucx-rkey-unpack-close-race
Jul 29, 2026
Merged

iyastreb merged 9 commits into
ai-dynamo:mainfrom
chaunceyjiang:fix/ucx-rkey-unpack-close-race

Conversation

@chaunceyjiang

@chaunceyjiang chaunceyjiang commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Fix #1986

What?

Fixes a race in the UCX backend between synchronous rkey unpack and endpoint teardown.

After a remote peer disconnects or restarts, the UCX error callback may force-close an endpoint while, or immediately
before, loadRemoteMD() calls ucp_ep_rkey_unpack() on the same endpoint. This can cause a native segfault instead of
returning a recoverable NIXL error.

This PR:

  • Serializes rkey unpack with both error-driven and normal endpoint close.
  • Checks endpoint state before calling ucp_ep_rkey_unpack().
  • Returns NIXL_ERR_REMOTE_DISCONNECT when unpack is attempted on a failed endpoint.
  • Preserves the status through the rkey constructor and internalMDHelper().
  • Adds focused tests for close-during-unpack and unpack-after-close.

Why?

In vLLM P/D disaggregation, restarting a Prefill Pod can leave the surviving Decode process handling asynchronous UCX
endpoint errors while loading metadata for the replacement Prefill.

The reported crash occurs in:

ucp_ep_rkey_unpack
→ nixl::ucx::rkey::unpackUcpRkey
→ nixlUcxEngine::internalMDHelper
→ nixlAgent::loadRemoteMD

One Decode TP worker segfaults while the other workers return NIXL_ERR_REMOTE_DISCONNECT. The crashed worker brings down
the entire Decode instance.

NIXL previously called ucp_ep_rkey_unpack() without synchronizing against endpoint close or checking whether the endpoint
had already failed.

How?

An endpoint-level mutex now protects:

  • Endpoint state validation followed by rkey unpack.
  • Force-close from the UCX error callback.
  • Normal endpoint close during teardown.

The endpoint state remains atomic so existing lock-free transfer-path state checks are preserved.

Since the rkey constructor cannot return a NIXL status directly, a typed rkey_error carries the failure status to
internalMDHelper(). This allows a remote disconnect to remain NIXL_ERR_REMOTE_DISCONNECT instead of being converted to the
generic NIXL_ERR_BACKEND.

The tests cover both relevant orderings:

rkey unpack starts → endpoint close waits
endpoint closes first → unpack returns remote disconnect without calling UCX

Summary by CodeRabbit

  • Bug Fixes

    • Improved UCX connection error and disconnect handling for more reliable endpoint state transitions.
    • Prevented metadata creation when a remote endpoint is unavailable or in a failed transmit state.
    • Ensured failed connections are closed consistently and reported with the appropriate remote-disconnect status.
  • Tests

    • Added automated coverage for UCX endpoint connection-reset and failure scenarios.
    • Updated optional UCX test integration for supported builds.

@chaunceyjiang
chaunceyjiang requested review from a team, brminich, gleon99 and yosefe as code owners July 23, 2026 11:31
@copy-pr-bot

copy-pr-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

Copy link
Copy Markdown

👋 Hi chaunceyjiang! Thank you for contributing to ai-dynamo/nixl.

Your PR reviewers will review your contribution then trigger the CI to test your changes.

🚀

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

UCX failure handling

Layer / File(s) Summary
Atomic endpoint state and failure lifecycle
src/plugins/ucx/ucx_utils.h, src/plugins/ucx/ucx_utils.cpp
Endpoint state reads and transitions use atomics; error callbacks mark connected endpoints failed, and close paths force-close failed endpoints while clearing handles.
Metadata endpoint preflight
src/plugins/ucx/ucx_backend.cpp
Metadata loading checks each worker endpoint’s transmit state and returns the first failure before constructing public metadata.
UCX failure test coverage
test/gtest/unit/ucx/*, test/gtest/unit/meson.build
Meson wiring builds a UCX endpoint test that triggers a connection-reset callback and verifies the endpoint failure state and native handle.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: brminich

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the core UCX race fix between rkey unpack and endpoint teardown.
Description check ✅ Passed The PR description follows the template with What, Why, and How sections and includes the linked issue.
Linked Issues check ✅ Passed The changes check endpoint state before UCX rkey unpack, preserve remote-disconnect status, and add teardown race tests for [#1986].
Out of Scope Changes check ✅ Passed All changes support the UCX race fix and associated tests; no unrelated functionality stands out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/ucx/ucx_utils.cpp (1)

157-170: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Transition state before releasing a closed endpoint.

Lines 160 and 170 clear eph but leave state as CONNECTED. A subsequently unblocked unpackRkey() therefore passes its state check and calls UCX with a null endpoint. Set a terminal state (DISCONNECTED on successful close; an appropriate failure state on close failure) before clearing eph.

Proposed fix
         if (request == nullptr) {
+            setState(nixl::ucx::ep_state_t::DISCONNECTED);
             eph = nullptr;
             return NIXL_SUCCESS;
         }

         if (UCS_PTR_IS_ERR(request)) {
+            setState(nixl::ucx::ep_state_t::FAILED);
             eph = nullptr;
             return nixl::ucx::ucsToNixlStatus(UCS_PTR_STATUS(request));
         }

         ucp_request_free(request);
+        setState(nixl::ucx::ep_state_t::DISCONNECTED);
         eph = nullptr;
         return NIXL_SUCCESS;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/ucx/ucx_utils.cpp` around lines 157 - 170, Update the CONNECTED
branch of the endpoint close logic to transition state before clearing eph: set
state to DISCONNECTED on successful or null-request close, and set the
appropriate failure state when UCS_PTR_IS_ERR(request) is true, then clear eph
and return. Ensure subsequent unpackRkey() state checks cannot use a null
endpoint.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plugins/ucx/ucx_utils.h`:
- Line 64: Rename the private member state to state_ in the relevant class, and
update every reference to this member accordingly while preserving its atomic
type and initialization.
- Around line 46-59: Add Doxygen-style block comments for the public
nixlUcxEpOps type and unpackRkey() function, documenting the callback and
context lifetime requirements and the status behavior returned by the
operations. Cover both the endpoint-operation callbacks and the associated
unpackRkey API without changing their implementation.

In `@test/gtest/unit/ucx/meson.build`:
- Line 1: The copyright headers in test/gtest/unit/ucx/meson.build lines 1-1 and
test/gtest/unit/ucx/ucx_ep_rkey_test.cpp lines 1-4 are missing the required “All
rights reserved.” wording. Update both SPDX header blocks so the NVIDIA
copyright line ends with “NVIDIA CORPORATION & AFFILIATES. All rights reserved.”

In `@test/gtest/unit/ucx/ucx_ep_rkey_test.cpp`:
- Around line 21-147: Run clang-format on the affected test code, including
CoordinatedEpOps::ops, the callback lambdas, the initializer, and asynchronous
test expressions. Preserve the test logic while applying the repository’s
standard formatting throughout the commented range.

---

Outside diff comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 157-170: Update the CONNECTED branch of the endpoint close logic
to transition state before clearing eph: set state to DISCONNECTED on successful
or null-request close, and set the appropriate failure state when
UCS_PTR_IS_ERR(request) is true, then clear eph and return. Ensure subsequent
unpackRkey() state checks cannot use a null endpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 3c6cfdd2-3e98-4e0b-af90-a9a928d538b2

📥 Commits

Reviewing files that changed from the base of the PR and between b941430 and 6277182.

📒 Files selected for processing (8)
  • src/plugins/ucx/rkey.cpp
  • src/plugins/ucx/rkey.h
  • src/plugins/ucx/ucx_backend.cpp
  • src/plugins/ucx/ucx_utils.cpp
  • src/plugins/ucx/ucx_utils.h
  • test/gtest/unit/meson.build
  • test/gtest/unit/ucx/meson.build
  • test/gtest/unit/ucx/ucx_ep_rkey_test.cpp

Comment thread src/plugins/ucx/ucx_utils.h Outdated
Comment thread src/plugins/ucx/ucx_utils.h Outdated
Comment thread test/gtest/unit/ucx/meson.build Outdated
Comment thread test/gtest/unit/ucx/ucx_ep_rkey_test.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.h Outdated
Comment thread src/plugins/ucx/ucx_utils.h Outdated
Comment thread src/plugins/ucx/ucx_backend.cpp Outdated
Comment thread src/plugins/ucx/rkey.cpp
@pull-request-size pull-request-size Bot added size/M and removed size/L labels Jul 24, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/ucx/ucx_utils.cpp (1)

89-117: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Serialize UCX endpoint lifetime across close, error callbacks, and rkey unpack.
checkTxState() is only an atomic read, and ucp_ep_rkey_unpack() still dereferences the raw endpoint after a separate preflight. Without a shared synchronization boundary around endpoint close/clearing and metadata construction, the endpoint can disappear or fail between the check and the UCX call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/ucx/ucx_utils.cpp` around lines 89 - 117, Serialize UCX endpoint
lifetime operations across the error callback and endpoint users: introduce or
reuse a shared synchronization boundary covering close/endpoint clearing,
metadata construction around ucp_ep_rkey_unpack(), and the state/error-callback
paths in nixlUcxEp::checkTxState, nixlUcxEp::setState, and the callback shown
here. In src/plugins/ucx/ucx_utils.cpp lines 89-117, update the callback/state
handling to participate in that synchronization; in
src/plugins/ucx/ucx_backend.cpp lines 969-974, hold the same boundary through
endpoint validation and ucp_ep_rkey_unpack() rather than relying on a separate
preflight check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 135-138: Replace the deprecated ucp_ep_close_nb() call in the
force-close path with ucp_ep_close_nbx(), using UCP_EP_CLOSE_FLAG_FORCE and the
existing request handling. Confirm the project’s minimum UCX version supports
the newer API, and keep the connected path’s existing newer-API usage
consistent.
- Around line 134-152: Make closeImpl’s FAILED handling idempotent: after
force-closing and releasing a valid endpoint in the FAILED case, transition the
state to DISCONNECTED, or return immediately when FAILED already has no endpoint
handle. Preserve the existing remote-disconnect result for the initial failed
closure while preventing subsequent calls from invoking ucp_ep_close_nb with
null.

In `@test/gtest/unit/ucx/ucx_ep_rkey_test.cpp`:
- Line 26: Add an assertion immediately after capturing the native endpoint in
the test’s relevant flow to verify that getEp() returns a non-null valid handle.
Ensure the assertion specifically checks the captured nativeEndpoint, so the
test cannot pass when both endpoint retrievals return nullptr.

---

Outside diff comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 89-117: Serialize UCX endpoint lifetime operations across the
error callback and endpoint users: introduce or reuse a shared synchronization
boundary covering close/endpoint clearing, metadata construction around
ucp_ep_rkey_unpack(), and the state/error-callback paths in
nixlUcxEp::checkTxState, nixlUcxEp::setState, and the callback shown here. In
src/plugins/ucx/ucx_utils.cpp lines 89-117, update the callback/state handling
to participate in that synchronization; in src/plugins/ucx/ucx_backend.cpp lines
969-974, hold the same boundary through endpoint validation and
ucp_ep_rkey_unpack() rather than relying on a separate preflight check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: ee2a4e92-e78b-401e-90b2-6cc8036db892

📥 Commits

Reviewing files that changed from the base of the PR and between 6277182 and c0547ed.

📒 Files selected for processing (5)
  • src/plugins/ucx/ucx_backend.cpp
  • src/plugins/ucx/ucx_utils.cpp
  • src/plugins/ucx/ucx_utils.h
  • test/gtest/unit/ucx/meson.build
  • test/gtest/unit/ucx/ucx_ep_rkey_test.cpp

Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
Comment thread test/gtest/unit/ucx/ucx_ep_test.cpp Outdated

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/ucx/ucx_utils.cpp (1)

89-106: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Serialize endpoint state transitions with endpoint-handle access.

err_cb() and closeImpl() can both observe CONNECTED, while setState() performs a separate load/assert/store. One thread can close or null eph while the callback reads it, and the callback can then overwrite the close transition or trigger the new_state != old_state assertion. Protect the state and eph lifetime with a shared synchronization protocol; atomic state alone is insufficient.

Also applies to: 113-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/ucx/ucx_utils.cpp` around lines 89 - 106, Synchronize the
endpoint state and eph lifetime across err_cb() and closeImpl() using the same
shared mutex or equivalent protocol, rather than relying on the atomic state
alone. Hold that synchronization while validating/accessing eph and while
performing the state transition, so closeImpl() cannot null or close the handle
concurrently and err_cb() cannot overwrite the close transition or violate
setState()’s transition assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 89-106: Synchronize the endpoint state and eph lifetime across
err_cb() and closeImpl() using the same shared mutex or equivalent protocol,
rather than relying on the atomic state alone. Hold that synchronization while
validating/accessing eph and while performing the state transition, so
closeImpl() cannot null or close the handle concurrently and err_cb() cannot
overwrite the close transition or violate setState()’s transition assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: ebe75fb7-19f6-4f1e-a6be-b1f5befb63c1

📥 Commits

Reviewing files that changed from the base of the PR and between c0547ed and 816a9a1.

📒 Files selected for processing (2)
  • src/plugins/ucx/ucx_utils.cpp
  • src/plugins/ucx/ucx_utils.h

@pull-request-size pull-request-size Bot added size/L and removed size/M labels Jul 24, 2026

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/ucx/ucx_utils.cpp (1)

121-148: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Serialize eph use with endpoint teardown.

state_ is atomic, but eph is not protected by a lifetime lock. A rkey path can pass its state check and call UCX while this path force-closes and nulls the same native handle. Hold a shared lock across the state check plus ucp_ep_rkey_unpack(), and an exclusive lock throughout closeImpl(), to prevent the reported close-during-unpack crash.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/ucx/ucx_utils.cpp` around lines 121 - 148, Protect endpoint
handle lifetime with the existing synchronization mechanism: acquire an
exclusive lock for the entire closeImpl() operation, and hold a shared lock
across the rkey path’s state validation and ucp_ep_rkey_unpack() call. Ensure
eph cannot be force-closed or nulled while the rkey operation is using it, while
preserving the current state and error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 121-148: Protect endpoint handle lifetime with the existing
synchronization mechanism: acquire an exclusive lock for the entire closeImpl()
operation, and hold a shared lock across the rkey path’s state validation and
ucp_ep_rkey_unpack() call. Ensure eph cannot be force-closed or nulled while the
rkey operation is using it, while preserving the current state and error
handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 0d9a3fa1-a0de-4c33-9e27-bb0a989755b0

📥 Commits

Reviewing files that changed from the base of the PR and between 816a9a1 and 02a9753.

📒 Files selected for processing (1)
  • src/plugins/ucx/ucx_utils.cpp

Comment thread test/gtest/unit/ucx/ucx_ep_test.cpp Outdated
iyastreb
iyastreb previously approved these changes Jul 24, 2026
@iyastreb

Copy link
Copy Markdown
Contributor

/build

1 similar comment
@iyastreb

Copy link
Copy Markdown
Contributor

/build

@chaunceyjiang

Copy link
Copy Markdown
Contributor Author

blossom-ci

Hi @iyastreb, it looks like this CI failure is unrelated to my code.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-build-wheel · commit fb6f84ac

TL;DR: The vLLM and SGLang sanity stages failed because the model prefetch step hit HuggingFace's anonymous rate limit (HTTP 429) — an infra/environment issue, not the PR's code. Fix by authenticating the download with an HF_TOKEN and/or caching model weights so CI doesn't re-download on every run.

Full analysis

Summary: Stages "Run vLLM sanity" (#715) and "Run SGLang sanity" (#674) failed; the wheel builds themselves succeeded (the aarch64 build_helper failures #488/#489 are the pre-sanity pipeline-stop steps, superseded by the successful rebuilds #536/#567/#598).

Root cause: In .gitlab/test_vllm_sglang_sanity.sh line 99, snapshot_download('Qwen/Qwen3-8B') is called with no HF token. HuggingFace returned 429 Too Many Requests: you have reached your 'api' rate limit ... We had to rate limit your IP (216.228.127.129 / 216.228.125.131). ... make sure you pass a HF_TOKEN, raising LocalEntryNotFoundError and exiting with code 1. Both jobs run from the shared NVIDIA egress IPs against the anonymous per-IP quota (0/500 remaining), so concurrent CI jobs exhaust it. This is unrelated to commit fb6f84a / PR #1987.

Implicated commit: unknown (environmental — HuggingFace anonymous rate limit; not caused by the PR under test)

File: .gitlab/test_vllm_sglang_sanity.sh:99

Suggested fix:

  • Provide an authenticated token to the prefetch: inject a CI secret HF_TOKEN into the sanity containers and pass it through (e.g. snapshot_download('${MODEL}', token=os.environ.get('HF_TOKEN'))), which moves the job off the shared anonymous IP quota. Store it as a Jenkins credential — do not hard-code it.
  • Better long-term: bake the sanity model into a persistent shared cache (mount HF_HOME/HF_HUB_CACHE from a warm CI volume, as is already done for the gsm8k dataset) so the prefetch is a cache hit and never contacts the API.
  • As a stopgap, add bounded retry/backoff around the snapshot_download call (respect the Retry after N seconds hint) and set HF_HUB_ENABLE_HF_TRANSFER/offline reuse where possible.
  • Re-running the build will likely pass once the 300s rate-limit window resets, but that only masks the recurring flakiness.

Related: none found.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit fb6f84ac

TL;DR: The "Allocate DL EP Environment" stage failed because the Slurm salloc on partition gb200nvl72_cx8 waited the full --immediate=3600 (60 min) for a node, never got one, and exited with "Unable to allocate resources: Connection timed out" — a cluster capacity/scheduling issue, not a NIXL code bug.

Full analysis

Summary: Stage 252 (Allocate DL EP Environment) exited code 1 after ~60 min because Slurm could not allocate a GPU node.

Root cause: salloc -N 1 -p gb200nvl72_cx8 ... --immediate=3600 ... --account=blackwell queued job 1648629, sat in "queued and waiting for resources" for the entire 3600s immediate window, then failed with Unable to allocate resources: Connection timed out. The GB200 partition had no free nodes within the wait window. This is confirmed to be environmental, not code: the Docker/UCX/NIXL build (stages 214, 176's build portion) completed successfully, a sibling allocation (stage 235) succeeded, and the EP tests (stage 272) passed on the node that was allocated. The ~60-min silent gap in the log is the salloc queue wait (expected behavior of --immediate), not an application hang.

Implicated commit: unknown — not code-related; commit fb6f84a only rides along with the CI infra failure.

File: CI pipeline Slurm allocation step (slurm.allocation in the swx-jenkins-lib pipeline; invoked with partition:gb200nvl72_cx8, immediateTimeout:3600).

Suggested fix: Re-run the build — this is a transient resource-contention timeout on the gb200nvl72_cx8 partition. To reduce recurrence, either (a) increase immediateTimeout / add automatic retry-with-backoff around the salloc call so a busy queue doesn't fail the whole pipeline, or (b) fall back to a --no-shell blocking salloc with a bounded --time and pipeline-level retry. Also check cluster/partition capacity and any competing reservations on dlcluster.nvidia.com at the failure time (07:17–08:18 UTC). No source-code change in nixl is warranted.

Related: none found.

Comment thread src/plugins/ucx/ucx_utils.h Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp
@rakhmets

Copy link
Copy Markdown
Contributor

/ok to test a8030d1

@chaunceyjiang

Copy link
Copy Markdown
Contributor Author

pls check if test is in CI

@iyastreb This test is now included in CI.

@iyastreb

Copy link
Copy Markdown
Contributor

/build

@iyastreb

Copy link
Copy Markdown
Contributor

/ok to test 926fc09

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-gpu · commit 6a8856a9

TL;DR: The "Run CPP tests" stage failed at ./bin/test_plugin with exit code 127 ("No such file or directory") because test_cpp.sh unconditionally invokes a binary that the build never produces; guard it with an [ -x ] check or remove the line.

Full analysis

Summary: The Run CPP tests pipeline stage (nodes 220 & 242) aborted when the harness tried to run ./bin/test_plugin, which does not exist, returning exit code 127.

Root cause: .gitlab/test_cpp.sh line 109 runs ./bin/test_plugin unconditionally. No meson target named test_plugin exists — test/unit/plugins/meson.build only descends into per-plugin subdirs that each build differently-named binaries (nixl_posix_test, nixl_gusli_test, ucx_backend_multi, gtest, etc.). With bash -e set, the missing binary yields "No such file or directory" / exit 127, failing the stage (and the log confirms all preceding tests — transfer, telemetry, POSIX, GUSLI, gtest — passed, and the container-export step after the failure is a non-fatal debug artifact). This is unrelated to PR #1987's UCX rkey/endpoint change; it is a test-script/build mismatch.

Implicated commit: unknown — the ./bin/test_plugin line predates the recent .gitlab/test_cpp.sh history shown; no commit in the last 10 introduces or guards it. The immediate trigger is the missing/renamed test_plugin build target.

File: .gitlab/test_cpp.sh:109 (./bin/test_plugin); build side: test/unit/plugins/meson.build (no test_plugin executable defined).

Suggested fix: Either (a) guard the invocation like the adjacent DOCA tests — if [ -x ./bin/test_plugin ]; then ./bin/test_plugin; fi — or (b) remove the stale line if the plugin test was renamed/removed, or (c) restore the missing meson target so bin/test_plugin is actually built. Option (a) is the minimal, immediate unblock; then confirm the intended binary name against the plugins test meson files.

Related: PR #1987 (the triggering PR, but not the cause); none of the searched issues track this test_plugin failure.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 6a8856a9

TL;DR: The build and compile succeeded; the job failed in the "Allocate DL EP Environment" stage because a Slurm salloc on the gb200nvl72_cx8 partition waited the full --immediate=3600 window (~1h) and was refused ("Unable to allocate resources"). This is a cluster-capacity/infra issue, not a code regression — re-run when GPU nodes are available.

Full analysis

Summary: nixl-ci-dl-gpu-ep #516 failed during GPU environment allocation, not build or tests.

Root cause: ssh ... salloc -N 1 -p gb200nvl72_cx8 --immediate=3600 --time=01:30:00 --no-shell --account=blackwell sat in job queued and waiting for resources from 15:24:35 to 16:24:44 (~3600s), then returned salloc: error: Unable to allocate resources: Connection timed out and exited 1. The exactly-one-hour gap equals the --immediate=3600 budget, so the partition simply had no free node within the immediate window. All prior stages (ucx v1.22.x build in node 125, NIXL meson/ninja compile, image build, podman push) completed successfully — so this is a scheduler capacity/availability problem, unrelated to the PR's code. (The FAILURE flag on stage 125 is a parallel-branch rollup of the same allocation timeout; its own log shows a clean, successful build.)

Implicated commit: none — not caused by [REDACTED:Hex High Entropy String] / PR #1987.

File: Jenkins pipeline allocation step invoking slurm.allocation (salloc -p gb200nvl72_cx8 --immediate=3600 --account=blackwell), stage "Allocate DL EP Environment" (node 224).

Suggested fix: Retry the build once GB200 (gb200nvl72_cx8) capacity is free — this is a transient scheduler shortage. To make CI more robust: (1) add automatic retry/backoff around the salloc step instead of failing on the first --immediate timeout, (2) consider raising --immediate or queueing without --immediate with a bounded overall timeout, and (3) surface the pending-job reason (squeue -j 1682590 -o "%r") in the log to distinguish capacity waits from real errors. Do not treat this as a code/test failure or change application timeouts.

Related: none found.

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 516326e9-e07b-4ec7-a883-beeec26da6b8 in the triage console for the audit trail.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 6a8856a9

TL;DR: The nixl-ci-dl-gpu-ep build failed in the "Allocate DL EP Environment" stage because the SLURM salloc on the gb200nvl72_cx8 partition timed out after its 3600s --immediate window with the job still queued ("Unable to allocate resources: Connection timed out") — a cluster capacity/infrastructure issue, not a code defect. Re-run the job once GB200 nodes free up (or raise immediateTimeout); no source fix is warranted.

Full analysis

Summary: Post-build GPU environment allocation failed; salloc could not obtain a GB200 node within its immediate-allocation timeout.

Root cause: In stage 200 the pipeline ran salloc -N 1 -p gb200nvl72_cx8 --immediate=3600 --time=01:30:00 ... at 17:31:58. The job was accepted and queued (Pending job allocation 1683448, queued and waiting for resources), then after a continuous 60-minute gap (17:31:58 → 18:32:06, exactly the 3600s immediate window) SLURM returned error: Unable to allocate resources: Connection timed out. This is the SLURM scheduler waiting for a busy partition to free a node — not a hang in nixl. The Docker image compile stages (159/162) and the retried UCX build_helper stages (142/149) all succeeded; the earlier build_helper FAILUREs (124/125) were superseded by those retries and are not the real failure.

Implicated commit: none — not a code regression (unrelated to PR #1987's UCX rkey/endpoint race fix at commit 6a8856a).

File: n/a — CI infrastructure (SLURM allocation in the pipeline slurm.allocation/salloc step, partition gb200nvl72_cx8).

Suggested fix: Retry the build once GB200 (gb200nvl72_cx8) capacity is available; this is a transient scheduler/capacity timeout. If these allocation timeouts recur, raise the immediateTimeout/--immediate value or add automatic retry/backoff around the salloc step, and check partition health with the cluster admins. No change to the PR is required to clear this failure.

Related: PR #1987 (the triggering change, unrelated to the failure).

@chaunceyjiang
chaunceyjiang requested a review from iyastreb July 28, 2026 22:47
@chaunceyjiang

Copy link
Copy Markdown
Contributor Author

nixl-ci-dl-gpu-ep

Hi @iyastreb, it looks like this CI failure is unrelated to my code.

@iyastreb
iyastreb merged commit 3e3e083 into ai-dynamo:main Jul 29, 2026
19 checks passed
@chaunceyjiang
chaunceyjiang deleted the fix/ucx-rkey-unpack-close-race branch July 29, 2026 06:07
vedularaghu added a commit to vedularaghu/nixl that referenced this pull request Aug 5, 2026
## What?

Drain the worker in `nixlUcxBackendH::release()` until every in-flight request
has completed, instead of relying on `ucp_request_cancel()` alone.

## Why?

`release()` currently cancels outstanding requests and frees them immediately,
with a TODO noting "it may not be enough to cancel UCX request". It isn't:
`ucp_request_cancel()` is a no-op for RMA/zcopy operations, and
`ucp_request_free()` does not synchronously complete internal requests.

So a request can still be in flight after `release()` returns. When the caller
subsequently deregisters its memory, `ucp_mem_unmap()` frees the memory handle
while a zcopy completion is still pending. That completion later runs on the
progress thread and dereferences the freed handle, giving a use-after-free that
surfaces as a SIGSEGV inside `ucp_memh_put()`.

Draining until the request reaches a terminal state guarantees the zcopy
completion callback runs while the memh is still valid.

This is distinct from the rkey-unpack / endpoint-teardown race fixed in ai-dynamo#1987 —
that one races unpack against teardown, this one races zcopy completion against
`ucp_mem_unmap()`.

## How was it tested?

Carried in production at Fireworks AI on a NIXL 0.10.0-based build for
disaggregated-prefill KV cache transfer, where the use-after-free was originally
observed. Forward-ported here to `main`, which still contains the original TODO
and the same cancel-and-free path.
vedularaghu added a commit to vedularaghu/nixl that referenced this pull request Aug 5, 2026
## What?

Drain the worker in `nixlUcxBackendH::release()` until every in-flight request
has completed, instead of relying on `ucp_request_cancel()` alone.

## Why?

`release()` currently cancels outstanding requests and frees them immediately,
with a TODO noting "it may not be enough to cancel UCX request". It isn't:
`ucp_request_cancel()` is a no-op for RMA/zcopy operations, and
`ucp_request_free()` does not synchronously complete internal requests.

So a request can still be in flight after `release()` returns. When the caller
subsequently deregisters its memory, `ucp_mem_unmap()` frees the memory handle
while a zcopy completion is still pending. That completion later runs on the
progress thread and dereferences the freed handle, giving a use-after-free that
surfaces as a SIGSEGV inside `ucp_memh_put()`.

Draining until the request reaches a terminal state guarantees the zcopy
completion callback runs while the memh is still valid.

The drain is bounded by `NIXL_UCX_REQUEST_DRAIN_TIMEOUT` (default 10s) so that
`release()` cannot block forever on a transfer that is wedged on an endpoint
which is still alive but no longer making progress. If the deadline expires the
request is deliberately leaked rather than freed, since freeing a request that
is still in flight is the very use-after-free this change is avoiding, and the
error log tells the caller its memory is not safe to deregister. Progress is
reported every `NIXL_UCX_WARNING_TIMEOUT` (default 5s), matching the existing
wait loop in `mem_list.cpp`.

This is distinct from the rkey-unpack / endpoint-teardown race fixed in ai-dynamo#1987 —
that one races unpack against teardown, this one races zcopy completion against
`ucp_mem_unmap()`.

## How was it tested?

Carried in production at Fireworks AI on a NIXL 0.10.0-based build for
disaggregated-prefill KV cache transfer, where the use-after-free was originally
observed. Forward-ported here to `main`, which still contains the original TODO
and the same cancel-and-free path.

Co-authored-by: Cursor <cursoragent@cursor.com>
vedularaghu added a commit to vedularaghu/nixl that referenced this pull request Aug 5, 2026
## What?

Drain the worker in `nixlUcxBackendH::release()` until every in-flight request
has completed, instead of relying on `ucp_request_cancel()` alone.

## Why?

`release()` currently cancels outstanding requests and frees them immediately,
with a TODO noting "it may not be enough to cancel UCX request". It isn't:
`ucp_request_cancel()` is a no-op for RMA/zcopy operations, and
`ucp_request_free()` does not synchronously complete internal requests.

So a request can still be in flight after `release()` returns. When the caller
subsequently deregisters its memory, `ucp_mem_unmap()` frees the memory handle
while a zcopy completion is still pending. That completion later runs on the
progress thread and dereferences the freed handle, giving a use-after-free that
surfaces as a SIGSEGV inside `ucp_memh_put()`.

Draining until the request reaches a terminal state guarantees the zcopy
completion callback runs while the memh is still valid.

The drain is bounded by `NIXL_UCX_REQUEST_DRAIN_TIMEOUT` (default 10s) so that
`release()` cannot block forever on a transfer that is wedged on an endpoint
which is still alive but no longer making progress. If the deadline expires the
request is deliberately leaked rather than freed, since freeing a request that
is still in flight is the very use-after-free this change is avoiding, and the
error log tells the caller its memory is not safe to deregister. Progress is
reported every `NIXL_UCX_WARNING_TIMEOUT` (default 5s), matching the existing
wait loop in `mem_list.cpp`.

This is distinct from the rkey-unpack / endpoint-teardown race fixed in ai-dynamo#1987 —
that one races unpack against teardown, this one races zcopy completion against
`ucp_mem_unmap()`.

## How was it tested?

Carried in production at Fireworks AI on a NIXL 0.10.0-based build for
disaggregated-prefill KV cache transfer, where the use-after-free was originally
observed. Forward-ported here to `main`, which still contains the original TODO
and the same cancel-and-free path.

Co-authored-by: Cursor <cursoragent@cursor.com>
vedularaghu added a commit to vedularaghu/nixl that referenced this pull request Aug 5, 2026
## What?

Drain the worker in `nixlUcxBackendH::release()` until every in-flight request
has completed, instead of relying on `ucp_request_cancel()` alone.

## Why?

`release()` currently cancels outstanding requests and frees them immediately,
with a TODO noting "it may not be enough to cancel UCX request". It isn't:
`ucp_request_cancel()` is a no-op for RMA/zcopy operations, and
`ucp_request_free()` does not synchronously complete internal requests.

So a request can still be in flight after `release()` returns. When the caller
subsequently deregisters its memory, `ucp_mem_unmap()` frees the memory handle
while a zcopy completion is still pending. That completion later runs on the
progress thread and dereferences the freed handle, giving a use-after-free that
surfaces as a SIGSEGV inside `ucp_memh_put()`.

Draining until the request reaches a terminal state guarantees the zcopy
completion callback runs while the memh is still valid.

The drain is bounded by `NIXL_UCX_REQUEST_DRAIN_TIMEOUT` (default 10s) so that
`release()` cannot block forever on a transfer that is wedged on an endpoint
which is still alive but no longer making progress. If the deadline expires the
request is deliberately leaked rather than freed, since freeing a request that
is still in flight is the very use-after-free this change is avoiding, and the
error log tells the caller its memory is not safe to deregister. Progress is
reported every `NIXL_UCX_WARNING_TIMEOUT` (default 5s), matching the existing
wait loop in `mem_list.cpp`.

This is distinct from the rkey-unpack / endpoint-teardown race fixed in ai-dynamo#1987 —
that one races unpack against teardown, this one races zcopy completion against
`ucp_mem_unmap()`.

## How was it tested?

Carried in production at Fireworks AI on a NIXL 0.10.0-based build for
disaggregated-prefill KV cache transfer, where the use-after-free was originally
observed. Forward-ported here to `main`, which still contains the original TODO
and the same cancel-and-free path.
brminich pushed a commit that referenced this pull request Aug 5, 2026
…les (#2027)

Follow-up to #1811 / #1987.

`postXferReq` guards the remote section by name only, so after a
disconnect invalidates (frees) gen-N metadata and the peer is
re-registered (gen-N+1), posting a gen-N xfer handle dereferences the
freed `nixlUcxPublicMetadata` pinned in `targetDescs` — segfault in
`sendXferRangeBatch`/`ucp_put_nbx`.
Reject stale-generation handles with `NIXL_ERR_NOT_FOUND` (same guard in
`estimateXferCost`, which dereferences the same metadata). Verified with
a fault-injection reproducer: without the fix the crash reproduces
reliably; with it, none.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Prevented remote transfer requests from using outdated metadata after
the associated remote resource is re-registered.
* Added validation to reject invalid or stale handles during cost
estimation and transfer submission.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: jjthomas <jjthomas@users.noreply.github.com>
vedularaghu added a commit to vedularaghu/nixl that referenced this pull request Aug 7, 2026
## What?

Drain the worker in `nixlUcxBackendReqH::release()` until every in-flight
request has reached a terminal state, instead of returning as soon as
`ucp_request_cancel()` and `ucp_request_free()` have been called.

## Why?

`release()` currently cancels outstanding requests and frees them, with a TODO
noting "it may not be enough to cancel UCX request". It isn't, but not for the
reason the request-lifetime reading suggests.

`ucp_request_cancel()` is a no-op for RMA: it only acts on requests carrying
`UCP_REQUEST_FLAG_RECV_TAG`. So `release()` can return with the operation still
outstanding.

The hazard that creates is on the *memory handle*, not on the request. NIXL
posts RMA with `UCP_OP_ATTR_FIELD_MEMH` and a memh from `ucp_mem_map()`. UCX
stores that pointer in the request (`ucp_datatype_iter`, `type.contig.memh`)
without taking a reference - a user memh is not reference counted. When the
operation completes, `ucp_datatype_iter_cleanup()` calls
`ucp_datatype_iter_mem_dereg_single()` -> `ucp_memh_put()`, which dereferences
`memh->context` and `memh->parent`. If the caller has deregistered in the
meantime, `ucp_mem_unmap()` -> `ucp_memh_cleanup()` has already `ucs_free()`d
that memh, and the completion faults inside `ucp_memh_put()`.

Releasing the request object itself is safe either way:
`ucp_request_release_common()` returns an *uncompleted* request to the pool only
by marking it `UCP_REQUEST_FLAG_RELEASED`; UCX calls `ucp_request_put()` when
the request later completes. Draining protects the memory handle, not the
request, so `reqRelease()` is now called unconditionally.

The drain is bounded by `NIXL_UCX_REQUEST_DRAIN_TIMEOUT` (default 10s) so that
`release()` cannot block forever on a wedged transfer. If the deadline expires,
the request is still released and an error tells the caller its memory is not
safe to deregister - there is nothing better `release()` can do without a real
abort primitive. Progress is reported every `NIXL_UCX_WARNING_TIMEOUT`
(default 5s), matching the existing wait loop in `mem_list.cpp`.

This is distinct from the rkey-unpack / endpoint-teardown race fixed in ai-dynamo#1987 -
that one races unpack against teardown, this one races RMA completion against
`ucp_mem_unmap()`.

## How was it tested?

`TestTransferRelease.InFlightXferIsDrainedBeforeReleaseReturns` in
`test/gtest/test_transfer.cpp` posts a 64 MiB READ over a TCP-pinned UCX data
path, releases the handle while the read is still in flight, and checks the
destination is fully written by the time `releaseXferReq()` returns. A read
completes only once its data has landed locally, so this observes the drain
directly and needs no RDMA hardware.

Also carried in production at Fireworks AI on a NIXL 0.10.0-based build for
disaggregated-prefill KV cache transfer, where the `ucp_memh_put()` SIGSEGV was
originally observed.
vedularaghu added a commit to vedularaghu/nixl that referenced this pull request Aug 7, 2026
## What?

Drain the worker in `nixlUcxBackendReqH::release()` until every in-flight
request has reached a terminal state, instead of returning as soon as
`ucp_request_cancel()` and `ucp_request_free()` have been called.

The composite/threadpool half of the same invariant is in the following commit;
neither closes the window on its own.

## Why?

`release()` currently cancels outstanding requests and frees them, with a TODO
noting "it may not be enough to cancel UCX request". It isn't, but not for the
reason the request-lifetime reading suggests.

`ucp_request_cancel()` is a no-op for RMA: it only acts on requests carrying
`UCP_REQUEST_FLAG_RECV_TAG`. So `release()` can return with the operation still
outstanding.

The hazard that creates is on the *memory handle*, not on the request. NIXL
posts RMA with `UCP_OP_ATTR_FIELD_MEMH` and a memh from `ucp_mem_map()`. UCX
stores that pointer in the request (`ucp_datatype_iter`, `type.contig.memh`)
without taking a reference - a user memh is not reference counted. When the
operation completes, `ucp_datatype_iter_cleanup()` calls
`ucp_datatype_iter_mem_dereg_single()` -> `ucp_memh_put()`, which dereferences
`memh->context` and `memh->parent`. If the caller has deregistered in the
meantime, `ucp_mem_unmap()` -> `ucp_memh_cleanup()` has already `ucs_free()`d
that memh, and the completion faults inside `ucp_memh_put()`.

Releasing the request object itself is safe either way:
`ucp_request_release_common()` returns an *uncompleted* request to the pool only
by marking it `UCP_REQUEST_FLAG_RELEASED`; UCX calls `ucp_request_put()` when
the request later completes. Draining protects the memory handle, not the
request, so `reqRelease()` is now called unconditionally.

The drain is bounded by `NIXL_UCX_REQUEST_DRAIN_TIMEOUT` (default 10s) so that
`release()` cannot block forever on a wedged transfer. If the deadline expires,
the request is still released and an error tells the caller its memory is not
safe to deregister - there is nothing better `release()` can do without a real
abort primitive. Progress is reported every `NIXL_UCX_WARNING_TIMEOUT`
(default 5s), matching the existing wait loop in `mem_list.cpp`.

This is distinct from the rkey-unpack / endpoint-teardown race fixed in ai-dynamo#1987 -
that one races unpack against teardown, this one races RMA completion against
`ucp_mem_unmap()`.

## How was it tested?

`TestTransferRelease.InFlightXferIsDrainedBeforeReleaseReturns` in
`test/gtest/test_transfer.cpp` posts a 64 MiB READ over a TCP-pinned UCX data
path, releases the handle while the read is still in flight, and checks the
destination is fully written by the time `releaseXferReq()` returns. A read
completes only once its data has landed locally, so this observes the drain
directly and needs no RDMA hardware.

With the drain removed and nothing else changed, the `ucx` parameterisation
sees 8 128 of 67 108 864 bytes landed when `releaseXferReq()` returns, and
`ucx_no_pt` sees 0. With the drain in place both are complete.

Also carried in production at Fireworks AI on a NIXL 0.10.0-based build for
disaggregated-prefill KV cache transfer, where the `ucp_memh_put()` SIGSEGV was
originally observed.
nv-nmailhot pushed a commit that referenced this pull request Aug 18, 2026
…les (#2027) (#2104)

Cherry pick of #2027

---------

Follow-up to #1811 / #1987.

`postXferReq` guards the remote section by name only, so after a
disconnect invalidates (frees) gen-N metadata and the peer is
re-registered (gen-N+1), posting a gen-N xfer handle dereferences the
freed `nixlUcxPublicMetadata` pinned in `targetDescs` — segfault in
`sendXferRangeBatch`/`ucp_put_nbx`.
Reject stale-generation handles with `NIXL_ERR_NOT_FOUND` (same guard in
`estimateXferCost`, which dereferences the same metadata). Verified with
a fault-injection reproducer: without the fix the crash reproduces
reliably; with it, none.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Prevented remote transfer requests from using outdated metadata after
the associated remote resource is re-registered.
* Added validation to reject invalid or stale handles during cost
estimation and transfer submission.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

## What?
_Describe what this PR is doing._

## Why?
_Justification for the PR. If there is an existing issue/bug, please
reference it. For
bug fixes, the 'Why?' and 'What?' can be merged into a single item._

## How?
_It is optional, but for complex PRs, please provide information about
the design,
architecture, approach, etc._

Signed-off-by: jjthomas <jjthomas@users.noreply.github.com>
Co-authored-by: James Thomas <jamesjoethomas@gmail.com>
@ovidiusm

Copy link
Copy Markdown
Contributor

@iyastreb We need to think about how to add test coverage for this kind of issues

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UCX] Race between endpoint close and rkey unpack can crash loadRemoteMD after peer restart

6 participants