From: Jorge Mora <jmora1300@gmail.com>
To: linux-nfs@vger.kernel.org
Cc: trondmy@kernel.org, anna@kernel.org, Rakesh.Bhargava@netapp.com,
Mohit.Kumar@netapp.com, mora@netapp.com
Subject: [PATCH] xprtrdma: fix race between frwr_unmap_sync() and transport disconnect
Date: Sat, 24 Oct 2026 10:46:33 -0600 [thread overview]
Message-ID: <20261024164633.256458-1-mora@netapp.com> (raw)
From: Rakesh Bhargava <Rakesh.Bhargava@netapp.com>
frwr_unmap_sync() is called from xprt_rdma_free() via ->buf_free, which
xprt_release() invokes without holding the transport send lock and
without XPRT_LOCKED. rpcrdma_xprt_disconnect() runs from xprt_autoclose()
under XPRT_LOCKED and, after draining the QP, resets every rpcrdma_req
(rpcrdma_reqs_reset) and destroys every MR (rpcrdma_mrs_destroy). Nothing
orders the two, so both can walk and list_del() the same req's
rl_registered list and the same MRs. Observed on RHEL 10.2
(6.12.0-211.7.3.el10_2) NFS/RDMA FlexFiles clients as:
kernel BUG at lib/list_debug.c:65!
frwr_unmap_sync+0x75 [rpcrdma]
xprt_release+... [sunrpc]
In a vmcore, the panicking task was in frwr_unmap_sync() for r_xprt X
while another kworker was in xprt_autoclose -> rpcrdma_xprt_disconnect
-> rpcrdma_reqs_reset -> frwr_mr_release for the same r_xprt X. The
window was large because 256 transports were being torn down at once,
each blocked in mlx5_core_destroy_mkey waiting on firmware.
Other consequences of the same race are a use of ep after
rpcrdma_ep_put() frees it, and rpcrdma_flush_disconnect() dereferencing
r_xprt->rx_ep after disconnect cleared it.
Fix it by making frwr_unmap_sync() and disconnect cooperate through
rb_lock:
- frwr_unmap_sync() reads rx_ep, checks rb_closing and that
rl_registered is non-empty, and claims all of the req's MRs under
rb_lock. It bumps rb_unmap_users while it still references the MRs
and the ep, and drops it (waking rb_unmap_wq) when done.
- If rb_closing is set, frwr_unmap_sync() returns and leaves the MRs on
rl_registered. rb_closing is only set after rdma_disconnect(), when
the QP is in the error state and the peer cannot reach the MRs;
rpcrdma_reqs_reset() then releases them.
- rpcrdma_xprt_disconnect() sets rb_closing after rdma_disconnect(),
waits for rb_unmap_users to reach zero after draining the QP and
before releasing reps, reqs and MRs, and clears rx_ep and rb_closing
under rb_lock before dropping the ep reference.
- rpcrdma_flush_disconnect() tolerates a NULL rx_ep.
Signed-off-by: Rakesh Bhargava <Rakesh.Bhargava@netapp.com>
Signed-off-by: Jorge Mora <mora@netapp.com>
---
net/sunrpc/xprtrdma/frwr_ops.c | 43 ++++++++++++++++++++++++++------
net/sunrpc/xprtrdma/verbs.c | 44 ++++++++++++++++++++++++++++++---
net/sunrpc/xprtrdma/xprt_rdma.h | 14 +++++++++++
3 files changed, 90 insertions(+), 11 deletions(-)
diff --git a/net/sunrpc/xprtrdma/frwr_ops.c b/net/sunrpc/xprtrdma/frwr_ops.c
index e83cef19e656..03e0e6c475b1 100644
--- a/net/sunrpc/xprtrdma/frwr_ops.c
+++ b/net/sunrpc/xprtrdma/frwr_ops.c
@@ -567,12 +567,33 @@ static void frwr_wc_localinv_wake(struct ib_cq *cq, struct ib_wc *wc)
*/
void frwr_unmap_sync(struct rpcrdma_xprt *r_xprt, struct rpcrdma_req *req)
{
+ struct rpcrdma_buffer *buf = &r_xprt->rx_buf;
struct ib_send_wr *first, **prev, *last;
- struct rpcrdma_ep *ep = r_xprt->rx_ep;
const struct ib_send_wr *bad_wr;
+ struct rpcrdma_ep *ep;
struct rpcrdma_mr *mr;
int rc;
+ /* This function is invoked via ->buf_free, which does not
+ * hold the transport send lock. It therefore races with
+ * rpcrdma_xprt_disconnect(), which resets every req and
+ * destroys every MR.
+ *
+ * Claim the req's MRs, and pin the transport's ep, under
+ * rb_lock. If the disconnect path has already put the QP
+ * into the error state, leave the MRs on rl_registered:
+ * the peer can no longer reach them, and
+ * rpcrdma_reqs_reset() releases them.
+ */
+ spin_lock(&buf->rb_lock);
+ ep = r_xprt->rx_ep;
+ if (buf->rb_closing || !ep ||
+ list_empty(&req->rl_registered)) {
+ spin_unlock(&buf->rb_lock);
+ return;
+ }
+ buf->rb_unmap_users++;
+
/* ORDER: Invalidate all of the MRs first
*
* Chain the LOCAL_INV Work Requests and post them with
@@ -598,6 +619,7 @@ void frwr_unmap_sync(struct rpcrdma_xprt *r_xprt, struct rpcrdma_req *req)
*prev = last;
prev = &last->next;
} while ((mr = rpcrdma_mr_pop(&req->rl_registered)));
+ spin_unlock(&buf->rb_lock);
mr = container_of(last, struct rpcrdma_mr, mr_invwr);
@@ -621,15 +643,22 @@ void frwr_unmap_sync(struct rpcrdma_xprt *r_xprt, struct rpcrdma_req *req)
*/
if (bad_wr != first)
wait_for_completion(&mr->mr_linv_done);
- if (!rc)
- return;
+ if (rc) {
+ /* On error, the MRs get destroyed once the QP has drained. */
+ trace_xprtrdma_post_linv_err(req, rc);
- /* On error, the MRs get destroyed once the QP has drained. */
- trace_xprtrdma_post_linv_err(req, rc);
+ /* Force a connection loss to ensure complete recovery.
+ */
+ rpcrdma_force_disconnect(ep);
+ }
- /* Force a connection loss to ensure complete recovery.
+ /* The MRs and the ep are no longer referenced. Allow
+ * rpcrdma_xprt_disconnect() to proceed.
*/
- rpcrdma_force_disconnect(ep);
+ spin_lock(&buf->rb_lock);
+ if (!--buf->rb_unmap_users)
+ wake_up(&buf->rb_unmap_wq);
+ spin_unlock(&buf->rb_lock);
}
/**
diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c
index be335eed329d..8848cebc42b2 100644
--- a/net/sunrpc/xprtrdma/verbs.c
+++ b/net/sunrpc/xprtrdma/verbs.c
@@ -133,8 +133,14 @@ void rpcrdma_force_disconnect(struct rpcrdma_ep *ep)
*/
void rpcrdma_flush_disconnect(struct rpcrdma_xprt *r_xprt, struct ib_wc *wc)
{
- if (wc->status != IB_WC_SUCCESS)
- rpcrdma_force_disconnect(r_xprt->rx_ep);
+ struct rpcrdma_ep *ep = READ_ONCE(r_xprt->rx_ep);
+
+ /* A flushed completion can be processed after the disconnect
+ * path has already divested the transport of its endpoint.
+ * In that case the connection is already gone.
+ */
+ if (wc->status != IB_WC_SUCCESS && ep)
+ rpcrdma_force_disconnect(ep);
}
/**
@@ -580,6 +586,7 @@ int rpcrdma_xprt_connect(struct rpcrdma_xprt *r_xprt)
*/
void rpcrdma_xprt_disconnect(struct rpcrdma_xprt *r_xprt)
{
+ struct rpcrdma_buffer *buf = &r_xprt->rx_buf;
struct rpcrdma_ep *ep = r_xprt->rx_ep;
struct rdma_cm_id *id;
int rc;
@@ -591,16 +598,42 @@ void rpcrdma_xprt_disconnect(struct rpcrdma_xprt *r_xprt)
rc = rdma_disconnect(id);
trace_xprtrdma_disconnect(r_xprt, rc);
+ /* The QP is in the error state now, so the peer can no
+ * longer access any registered MR. From here on, a
+ * frwr_unmap_sync() caller that has not yet claimed a req's
+ * MRs leaves them on rl_registered: rpcrdma_reqs_reset()
+ * releases them below.
+ */
+ spin_lock(&buf->rb_lock);
+ buf->rb_closing = true;
+ spin_unlock(&buf->rb_lock);
+
rpcrdma_xprt_drain(r_xprt);
+
+ /* frwr_unmap_sync() runs without the transport send lock.
+ * Callers that claimed MRs before rb_closing was set may
+ * still be posting or waiting on LOCAL_INV Work Requests.
+ * Those complete (or flush) because the QP has been
+ * drained, but the callers still reference the MRs and the
+ * ep, so wait for them before anything is released.
+ */
+ wait_event(buf->rb_unmap_wq, !READ_ONCE(buf->rb_unmap_users));
+
rpcrdma_reps_unmap(r_xprt);
rpcrdma_sendctxs_destroy(r_xprt);
rpcrdma_reqs_reset(r_xprt);
rpcrdma_mrs_destroy(r_xprt);
+ /* Unpublish the ep before dropping the last reference to it
+ * so that frwr_unmap_sync() never picks up a stale pointer.
+ */
+ spin_lock(&buf->rb_lock);
+ r_xprt->rx_ep = NULL;
+ buf->rb_closing = false;
+ spin_unlock(&buf->rb_lock);
+
if (rpcrdma_ep_put(ep))
rdma_destroy_id(id);
-
- r_xprt->rx_ep = NULL;
}
/* Fixed-size circular FIFO queue. This implementation is wait-free and
@@ -1168,6 +1201,9 @@ int rpcrdma_buffer_create(struct rpcrdma_xprt *r_xprt)
buf->rb_bc_srv_max_requests = 0;
spin_lock_init(&buf->rb_lock);
+ buf->rb_closing = false;
+ buf->rb_unmap_users = 0;
+ init_waitqueue_head(&buf->rb_unmap_wq);
INIT_LIST_HEAD(&buf->rb_mrs);
INIT_LIST_HEAD(&buf->rb_all_mrs);
INIT_WORK(&buf->rb_refresh_worker, rpcrdma_mr_refresh_worker);
diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h
index 4cbc941e4a3e..0161317e4916 100644
--- a/net/sunrpc/xprtrdma/xprt_rdma.h
+++ b/net/sunrpc/xprtrdma/xprt_rdma.h
@@ -402,6 +402,20 @@ struct rpcrdma_buffer {
u32 rb_bc_max_requests;
struct work_struct rb_refresh_worker;
+
+ /* Serializes frwr_unmap_sync(), which runs from ->buf_free
+ * without holding the transport send lock, against
+ * rpcrdma_xprt_disconnect(). Protected by rb_lock.
+ *
+ * rb_closing: the QP is in the error state and the disconnect
+ * path is about to reset all reqs and destroy all MRs.
+ * rb_unmap_users: number of frwr_unmap_sync() callers that
+ * own MRs taken from a req's rl_registered list, or have
+ * LOCAL_INV Work Requests outstanding on the QP.
+ */
+ bool rb_closing;
+ unsigned int rb_unmap_users;
+ wait_queue_head_t rb_unmap_wq;
};
/*
base-commit: 18a80121193c6d19519e218434c9acf90e8d0e5c
--
2.47.3
next reply other threads:[~2026-10-24 16:46 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 16:46 Jorge Mora [this message]
2026-10-24 17:37 ` [PATCH] xprtrdma: fix race between frwr_unmap_sync() and transport disconnect Mora, Jorge
2026-10-24 20:33 ` Chuck Lever
2026-10-26 15:46 ` Mora, Jorge
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261024164633.256458-1-mora@netapp.com \
--to=jmora1300@gmail.com \
--cc=Mohit.Kumar@netapp.com \
--cc=Rakesh.Bhargava@netapp.com \
--cc=anna@kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=mora@netapp.com \
--cc=trondmy@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.