Re: [PATCH v2] xprtrdma: serialize unmap_sync with xprt_disconnect

"Chuck Lever" <[email protected]>
Newsgroups org.kernel.vger.linux-nfs,org.kernel.vger.stable
Message-ID <[email protected]>
Hey Tim -

I didn't find any correctness issues in this one.

Nits: A few comments were made stale, and a layering change.


On Thu, Aug 20, 2026, at 5:17 PM, Tim Menninger wrote:
> diff --git a/net/sunrpc/xprtrdma/frwr_ops.c b/net/sunrpc/xprtrdma/frwr_ops.c
> index e5c71cf705a3..77aff9c0533a 100644
> --- a/net/sunrpc/xprtrdma/frwr_ops.c
> +++ b/net/sunrpc/xprtrdma/frwr_ops.c

[ ... ]

> @@ -538,40 +541,62 @@ static void frwr_wc_localinv(struct ib_cq *cq, struct ib_wc *wc)

[ ... ]

>  /**
> - * frwr_unmap_sync - invalidate memory regions that were registered for @req
> + * frwr_unmap_sync - synchronously invalidate MRs registered for @req
>   * @r_xprt: controlling transport instance
> - * @req: rpcrdma_req with a non-empty list of MRs to process
> + * @req: request whose registered MRs are to be invalidated
>   *
> - * Sleeps until it is safe for the host CPU to access the previously mapped
> - * memory regions. This guarantees that registered MRs are properly fenced
> - * from the server before the RPC consumer accesses the data in them. It
> - * also ensures proper Send flow control: waking the next RPC waits until
> - * this RPC has relinquished all its Send Queue entries.
> + * If @req still owns registered MRs after synchronizing with transport
> + * disconnect, post a chain of LOCAL_INV Work Requests and wait for the
> + * final completion. This fences the mapped regions from remote access
> + * and preserves Send Queue flow control.
> + *
> + * A concurrent disconnect can reset @req while this function waits for
> + * the read side. In that case the MRs have already been released and no
> + * LOCAL_INV Work Requests are posted.
>   */

The rewritten block drops the sleeping behavior the old text carried:

  * Sleeps until it is safe for the host CPU to access the previously mapped
  * memory regions.

frwr_unmap_sync() now also takes and releases a transport-wide rwsem.
Both facts belong in a Context: section.

	 * Context: Process context. Takes and releases
	 *	    @r_xprt->rx_unmap_rwsem for read. May sleep.

[ ... ]

> @@ -598,37 +623,44 @@ 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;
> +		wait_for_completion(&req->rl_linv_done);
>
> -	/* On error, the MRs get destroyed once the QP has drained. */
> -	trace_xprtrdma_post_linv_err(req, rc);
> +	if (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.
> +		 */
> +		rpcrdma_force_disconnect(ep);
> +	}
> +
> +	if (rpcrdma_ep_put(ep))
> +		rdma_destroy_id(id);

This is not a defect, but it makes frwr_ops.c the only file outside
verbs.c that destroys a CM ID.  The other three teardowns are all in
verbs.c: rpcrdma_create_id() on its error path, rpcrdma_ep_create() on
its error path, and rpcrdma_xprt_disconnect().

The rule that a nonzero return obliges the caller to destroy the CM ID
lives in a comment above rpcrdma_ep_put():

net/sunrpc/xprtrdma/verbs.c {
	/*
	 *     %0 if @ep still has a positive kref count, or
	 *     %1 if @ep was destroyed successfully.
	 */
	noinline int rpcrdma_ep_put(struct rpcrdma_ep *ep)
	{
		return kref_put(&ep->re_kref, rpcrdma_ep_destroy);
	}
}

A small helper in verbs.c would carry that rule instead of open coding
the idiom in a second file.

	void rpcrdma_ep_release(struct rpcrdma_ep *ep)
	{
		struct rdma_cm_id *id = ep->re_id;

		if (rpcrdma_ep_put(ep))
			rdma_destroy_id(id);
	}

That exports rpcrdma_ep_get() and rpcrdma_ep_release() rather than the
get/put pair.  The local id then goes away, because ib_post_send() can
read ep->re_id->qp directly while the read side is still held.

[ ... ]

> diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c
> index 04b286223b24..193a90c97f7a 100644
> --- a/net/sunrpc/xprtrdma/verbs.c
> +++ b/net/sunrpc/xprtrdma/verbs.c

[ ... ]

> @@ -580,18 +578,22 @@ int rpcrdma_xprt_connect(struct rpcrdma_xprt *r_xprt)
>   */
>  void rpcrdma_xprt_disconnect(struct rpcrdma_xprt *r_xprt)
>  {
> -	struct rpcrdma_ep *ep = r_xprt->rx_ep;
> +	struct rpcrdma_ep *ep;
>  	struct rdma_cm_id *id;
>  	int rc;
>
> +	down_write(&r_xprt->rx_unmap_rwsem);
> +
> +	ep = r_xprt->rx_ep;

rpcrdma_xprt_disconnect() now holds rx_unmap_rwsem for write across the
QP drain and all of the resource teardown.  Its kernel-doc still records
only the caller-side rule:

  * Caller serializes. Either the transport send lock is held,
  * or we're being called to destroy the transport.

The rwsem belongs there as well.

The same block goes on to say:

  * On return, @r_xprt is completely divested of all hardware
  * resources and prepared for the next ->connect operation.

The first half of that no longer holds.  A concurrent frwr_unmap_sync()
holds an endpoint reference across its completion wait, so
rpcrdma_ep_put() below returns 0.  The QP, both completion queues, the
PD and the CM ID then outlive this return.

[ ... ]

> diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h
> index 4cbc941e4a3e..e50b0af5adf8 100644
> --- a/net/sunrpc/xprtrdma/xprt_rdma.h
> +++ b/net/sunrpc/xprtrdma/xprt_rdma.h

[ ... ]

> @@ -449,6 +450,12 @@ struct rpcrdma_xprt {
>  	struct delayed_work	rx_connect_worker;
>  	struct rpc_timeout	rx_timeout;
>  	struct rpcrdma_stats	rx_stats;
> +
> +	/*
> +	 * Read side protects sampling rx_ep and posting synchronous LOCAL_INV
> +	 * WRs; disconnect holds the write side across QP drain and teardown.
> +	 */
> +	struct rw_semaphore	rx_unmap_rwsem;
>  };

This is not a defect, but the comment claims more than the lock
delivers.  rx_ep is sampled without this rwsem in frwr_mr_init(),
frwr_map(), frwr_send(), frwr_unmap_async(), frwr_wp_create(),
rpcrdma_xprt_drain(), rpcrdma_mrs_create(), rpcrdma_mrs_refresh(),
rpcrdma_rep_create(), rpcrdma_rep_resize() and rpcrdma_post_recvs().

What the read side establishes is narrower: it orders
frwr_unmap_sync()'s LOCAL_INV submission ahead of the QP drain in
rpcrdma_xprt_disconnect().  Something closer to:

	/*
	 * Orders frwr_unmap_sync()'s LOCAL_INV submission ahead of the
	 * QP drain.  Disconnect holds the write side across the drain
	 * and resource teardown.
	 */

> @@ -479,6 +486,8 @@ extern unsigned int xprt_rdma_memreg_strategy;
>   * Endpoint calls - xprtrdma/verbs.c
>   */
>  void rpcrdma_force_disconnect(struct rpcrdma_ep *ep);
> +void rpcrdma_ep_get(struct rpcrdma_ep *ep);
> +int rpcrdma_ep_put(struct rpcrdma_ep *ep);

Nothing here says that a return of 1 leaves the CM ID for the caller to
destroy, which is the rule the frwr_unmap_sync() hunk above open codes.

-- 
Chuck Lever
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.