Netdev List
 help / color / mirror / Atom feed
From: Chuck Lever <cel@kernel.org>
To: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>,
	 Olga Kornievskaia <okorniev@redhat.com>,
	Dai Ngo <Dai.Ngo@oracle.com>,  Tom Talpey <tom@talpey.com>
Cc: Rick Macklem <rmacklem@uoguelph.ca>,
	linux-nfs@vger.kernel.org,  netdev@vger.kernel.org,
	Chuck Lever <cel@kernel.org>
Subject: [PATCH v8 09/14] SUNRPC: Publish reply positions for upper-layer consumers
Date: Wed, 04 Nov 2026 09:11:09 -0500	[thread overview]
Message-ID: <20261104-duplicate-reply-cache-v8-9-eea5b90e4318@kernel.org> (raw)
In-Reply-To: <20261104-duplicate-reply-cache-v8-0-eea5b90e4318@kernel.org>

An RPC server's duplicate reply cache has to hold a cached reply
until the client has received it. SUNRPC gives an upper layer no way
to learn that, so a cache entry lives for the full retention
interval even when the reply was delivered at once.

Add two transport-neutral values that an upper layer can compare:
the position of each reply on its transport, recorded in the svc_rqst
by xpo_sendto(), and the position up to which the peer has
acknowledged, recorded on the svc_xprt. The units are private to the
transport (bytes for a stream, Send sequence for RDMA). A consumer
learns only whether one is less than or equal to the other. A reply
that xpo_sendto() does not hand to the wire keeps position zero and
is never reported as acknowledged.

The acknowledged position is an atomic64_t. A transport publishes it
outside any lock the consumer holds, and a torn 64-bit read on a
32-bit host could run ahead of the peer. A transport raises the
position through svc_xprt_advance_acked(), a plain read and store
that computes a maximum only while one context per transport
writes. A cmpxchg loop would make the helper safe from any context
at the cost of a locked instruction on every acknowledgment.

An acknowledgment proves the peer holds the reply, so no retransmit
of that call follows. A retransmit the peer sent before the reply
reached it precedes the acknowledgment in the byte stream and can
still be ahead of its lookup when the transport refreshes the
position: unread in the socket, or read and parked in
svc_authenticate(), which can sleep for seconds in the ip_map upcall
or defer the request. A consumer that evicts on the refreshed
position sends the retransmit through the procedure again.

Count a transport's requests from first read until the consumer
reports the lookup with svc_xprt_recv_done(), and carry the count
across a deferral. A revisited request repeats its lookup, so a
procedure that defers after the first lookup was reported puts the
request back under the count. Publish the position through
svc_xprt_acked_pos(), so the transport can withhold the position
while any request but the caller's is counted. A request that never
reaches a lookup is uncounted when it is released.

Add a per-service hook that runs once per request when its reply
phase ends, whether svc_send() sent the reply or svc_process()
dropped it, so an upper layer can record the reply's position
against its own state either way. On the drop path the position is
zero.

Link: https://patch.msgid.link/20260921-duplicate-reply-cache-v6-8-db5e13fd9944@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 include/linux/sunrpc/svc.h      | 18 +++++++++++++
 include/linux/sunrpc/svc_xprt.h | 25 ++++++++++++++++++
 net/sunrpc/svc.c                |  3 +++
 net/sunrpc/svc_xprt.c           | 56 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 102 insertions(+)

diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h
index b1dff5c8aa89..fc83b24934b0 100644
--- a/include/linux/sunrpc/svc.h
+++ b/include/linux/sunrpc/svc.h
@@ -59,6 +59,8 @@ enum {
 };
 
 
+struct svc_rqst;
+
 /*
  * RPC service.
  *
@@ -101,6 +103,13 @@ struct svc_serv {
 						 * connection */
 	bool			sv_bc_enabled;	/* service uses backchannel */
 #endif /* CONFIG_SUNRPC_BACKCHANNEL */
+
+	/*
+	 * Called once per request after its reply phase, whether
+	 * xpo_sendto() succeeded, failed, or was never reached because
+	 * svc_process() dropped the reply. See rq_reply_pos.
+	 */
+	void			(*sv_reply_sent)(struct svc_rqst *rqstp);
 };
 
 /* This is used by pool_stats to find and lock an svc */
@@ -272,6 +281,13 @@ struct svc_rqst {
 	unsigned int		bc_to_retries;
 	unsigned int		rq_status_counter; /* RPC processing counter */
 	void			*rq_private;	/* For use by the service thread */
+
+	/*
+	 * Transport position of this request's reply, set by
+	 * xpo_sendto(); zero when the reply did not reach the wire.
+	 * Comparable only with the xpt_acked_pos of the same svc_xprt.
+	 */
+	u64			rq_reply_pos;
 };
 
 /* bits for rq_flags */
@@ -282,6 +298,7 @@ enum {
 	RQ_DROPME,		/* drop current reply */
 	RQ_VICTIM,		/* Have agreed to shut down */
 	RQ_DATA,		/* request has data */
+	RQ_RECV_PENDING,	/* counted in xpt_recv_pending */
 };
 
 #define SVC_NET(rqst) (rqst->rq_xprt ? rqst->rq_xprt->xpt_net : rqst->rq_bc_net)
@@ -364,6 +381,7 @@ static inline void svc_thread_init_status(struct svc_rqst *rqstp, int err)
 struct svc_deferred_req {
 	u32			prot;	/* protocol (UDP or TCP) */
 	bool			secure;	/* RQ_SECURE of the original request */
+	bool			recv_pending; /* RQ_RECV_PENDING, carried over */
 	struct svc_xprt		*xprt;
 	struct sockaddr_storage	addr;	/* where reply must go */
 	size_t			addrlen;
diff --git a/include/linux/sunrpc/svc_xprt.h b/include/linux/sunrpc/svc_xprt.h
index 52082d8ea6dd..aea98ddbfe52 100644
--- a/include/linux/sunrpc/svc_xprt.h
+++ b/include/linux/sunrpc/svc_xprt.h
@@ -28,6 +28,7 @@ struct svc_xprt_ops {
 	void		(*xpo_free)(struct svc_xprt *);
 	void		(*xpo_kill_temp_xprt)(struct svc_xprt *);
 	void		(*xpo_handshake)(struct svc_xprt *xprt);
+	u64		(*xpo_acked_pos)(struct svc_rqst *rqstp);
 };
 
 struct svc_xprt_class {
@@ -66,6 +67,10 @@ struct svc_xprt {
 	atomic_t		xpt_reserved;	/* outq space rsvd, UDP only */
 	atomic_t		xpt_nr_rqsts;	/* Number of requests */
 	struct mutex		xpt_mutex;	/* to serialize sending data */
+	atomic64_t		xpt_acked_pos;	/* replies up to this position
+						 * are acknowledged; 0 = none */
+	/* requests read but not yet past svc_xprt_recv_done() */
+	atomic_t		xpt_recv_pending;
 	spinlock_t		xpt_lock;	/* protects sk_deferred
 						 * and xpt_auth_cache */
 	void			*xpt_auth_cache;/* auth cache */
@@ -161,6 +166,24 @@ static inline bool svc_xprt_is_dead(const struct svc_xprt *xprt)
 		(test_bit(XPT_CLOSE, &xprt->xpt_flags) != 0);
 }
 
+/**
+ * svc_xprt_advance_acked - Raise a transport's acknowledged position
+ * @xprt: transport that sent the replies
+ * @pos: position the peer has acknowledged, in @xprt's own units
+ *
+ * Context: Any context. Callers must serialize against other writers
+ *	of @xprt's xpt_acked_pos.
+ * Return: %true if the position was raised, %false if @pos does not
+ *	exceed the recorded position.
+ */
+static inline bool svc_xprt_advance_acked(struct svc_xprt *xprt, u64 pos)
+{
+	if (pos <= atomic64_read(&xprt->xpt_acked_pos))
+		return false;
+	atomic64_set(&xprt->xpt_acked_pos, pos);
+	return true;
+}
+
 int	svc_reg_xprt_class(struct svc_xprt_class *);
 void	svc_unreg_xprt_class(struct svc_xprt_class *);
 void	svc_xprt_init(struct net *, struct svc_xprt_class *, struct svc_xprt *,
@@ -175,6 +198,8 @@ int	svc_xprt_create(struct svc_serv *serv, const char *xprt_name,
 void	svc_xprt_destroy_all(struct svc_serv *serv, struct net *net,
 			     bool unregister);
 void	svc_xprt_received(struct svc_xprt *xprt);
+void	svc_xprt_recv_done(struct svc_rqst *rqstp);
+u64	svc_xprt_acked_pos(struct svc_rqst *rqstp);
 void	svc_xprt_enqueue(struct svc_xprt *xprt);
 void	svc_xprt_put(struct svc_xprt *xprt);
 void	svc_xprt_copy_addrs(struct svc_rqst *rqstp, struct svc_xprt *xprt);
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index f261a4f47276..0dcb1085ecb1 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -1503,6 +1503,7 @@ svc_process_common(struct svc_rqst *rqstp)
 
 	/* Reset the accept_stat for the RPC */
 	rqstp->rq_accept_statp = NULL;
+	rqstp->rq_reply_pos = 0;
 
 	/* Will be turned off only when NFSv4 Sessions are used */
 	set_bit(RQ_USEDEFERRAL, &rqstp->rq_flags);
@@ -1748,6 +1749,8 @@ void svc_process(struct svc_rqst *rqstp)
 		goto out_baddir;
 
 	if (!svc_process_common(rqstp)) {
+		if (rqstp->rq_server->sv_reply_sent)
+			rqstp->rq_server->sv_reply_sent(rqstp);
 		svc_release_rqst(rqstp);
 		goto out_drop;
 	}
diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c
index 15c14d75ec12..4baeb82d02c0 100644
--- a/net/sunrpc/svc_xprt.c
+++ b/net/sunrpc/svc_xprt.c
@@ -593,14 +593,57 @@ static void free_deferred(struct svc_xprt *xprt, struct svc_deferred_req *dr)
 	if (!dr)
 		return;
 
+	if (dr->recv_pending)
+		atomic_dec(&xprt->xpt_recv_pending);
 	xprt->xpt_ops->xpo_release_ctxt(xprt, dr->xprt_ctxt);
 	kfree(dr);
 }
 
+/**
+ * svc_xprt_recv_done - a request has been looked up in the reply cache
+ * @rqstp: request that has been looked up
+ *
+ * A transport counts each request from its read until this call, and
+ * svc_xprt_acked_pos() withholds the acknowledged position while the
+ * count is non-zero: a retransmit the peer sent before the
+ * acknowledgment may be among the requests counted. Call this once
+ * the request has been looked up, before the reply cache acts on the
+ * position. A request that never reaches a lookup is uncounted when
+ * it is released.
+ *
+ * Context: any context.
+ */
+void svc_xprt_recv_done(struct svc_rqst *rqstp)
+{
+	if (test_and_clear_bit(RQ_RECV_PENDING, &rqstp->rq_flags))
+		atomic_dec(&rqstp->rq_xprt->xpt_recv_pending);
+}
+EXPORT_SYMBOL_GPL(svc_xprt_recv_done);
+
+/**
+ * svc_xprt_acked_pos - acknowledged position the server may act on
+ * @rqstp: the request on whose behalf the position is read
+ *
+ * Context: process context. May take and release the socket lock.
+ * Return: the position up to which the peer has acknowledged replies
+ * and no earlier request can still be ahead of its lookup, or 0 when
+ * the transport publishes no position or cannot yet vouch for it.
+ */
+u64 svc_xprt_acked_pos(struct svc_rqst *rqstp)
+{
+	struct svc_xprt *xprt = rqstp->rq_xprt;
+
+	if (!xprt->xpt_ops->xpo_acked_pos)
+		return 0;
+	return xprt->xpt_ops->xpo_acked_pos(rqstp);
+}
+EXPORT_SYMBOL_GPL(svc_xprt_acked_pos);
+
 static void svc_xprt_release(struct svc_rqst *rqstp)
 {
 	struct svc_xprt	*xprt = rqstp->rq_xprt;
 
+	svc_xprt_recv_done(rqstp);
 	xprt->xpt_ops->xpo_release_ctxt(xprt, rqstp->rq_xprt_ctxt);
 	rqstp->rq_xprt_ctxt = NULL;
 
@@ -1037,6 +1080,8 @@ void svc_send(struct svc_rqst *rqstp)
 	trace_svc_stats_latency(rqstp);
 
 	status = xprt->xpt_ops->xpo_sendto(rqstp);
+	if (xprt->xpt_server->sv_reply_sent)
+		xprt->xpt_server->sv_reply_sent(rqstp);
 
 	trace_svc_send(rqstp, status);
 }
@@ -1352,6 +1397,13 @@ static struct cache_deferred_req *svc_defer(struct cache_req *req)
 	}
 	dr->xprt_ctxt = rqstp->rq_xprt_ctxt;
 	rqstp->rq_xprt_ctxt = NULL;
+	/*
+	 * The revisit repeats the lookup, so a request a procedure
+	 * defers after its lookup was reported is counted again.
+	 */
+	if (!test_and_clear_bit(RQ_RECV_PENDING, &rqstp->rq_flags))
+		atomic_inc(&rqstp->rq_xprt->xpt_recv_pending);
+	dr->recv_pending = true;
 	trace_svc_defer(rqstp);
 	svc_xprt_get(rqstp->rq_xprt);
 	dr->xprt = rqstp->rq_xprt;
@@ -1386,6 +1438,10 @@ static noinline int svc_deferred_recv(struct svc_rqst *rqstp)
 	 * it handled last. Restore the value computed at receive time.
 	 */
 	assign_bit(RQ_SECURE, &rqstp->rq_flags, dr->secure);
+	if (dr->recv_pending) {
+		set_bit(RQ_RECV_PENDING, &rqstp->rq_flags);
+		dr->recv_pending = false;
+	}
 
 	dr->xprt_ctxt = NULL;
 	svc_xprt_received(rqstp->rq_xprt);

-- 
2.56.0


  parent reply	other threads:[~2026-11-04 14:11 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 14:11 [PATCH v8 00/14] Improve the scalability of NFSD's classic DRC Chuck Lever
2026-11-04 14:11 ` [PATCH v8 01/14] SUNRPC: Reject an AUTH_TLS probe inside a TLS session Chuck Lever
2026-11-04 14:11 ` [PATCH v8 02/14] NFSD: Make the DRC size limit independent of page size Chuck Lever
2026-11-04 14:11 ` [PATCH v8 03/14] NFSD: Remove hard cap on duplicate reply cache size Chuck Lever
2026-11-04 14:11 ` [PATCH v8 04/14] SUNRPC: Assign a unique identifier to each svc_xprt Chuck Lever
2026-11-04 14:11 ` [PATCH v8 05/14] NFSD: Track transport in DRC entries Chuck Lever
2026-11-04 14:11 ` [PATCH v8 06/14] NFSD: Prepare bucket pruning for additional eviction reasons Chuck Lever
2026-11-04 14:11 ` [PATCH v8 07/14] NFSD: Add tracepoints for DRC entry eviction Chuck Lever
2026-11-04 14:11 ` [PATCH v8 08/14] NFSD: Record DRC population in lookup tracepoints Chuck Lever
2026-11-04 14:11 ` Chuck Lever [this message]
2026-11-04 14:11 ` [PATCH v8 10/14] SUNRPC: Publish TCP reply positions Chuck Lever
2026-11-04 14:11 ` [PATCH v8 11/14] svcrdma: Publish RDMA " Chuck Lever
2026-11-04 14:11 ` [PATCH v8 12/14] NFSD: Evict acknowledged DRC entries Chuck Lever
2026-11-04 14:11 ` [PATCH v8 13/14] NFSD: Remove DRC checksum and payload_misses stat Chuck Lever
2026-11-04 14:11 ` [PATCH v8 14/14] SUNRPC: Advance the acked position on TLS connections Chuck Lever

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=20261104-duplicate-reply-cache-v8-9-eea5b90e4318@kernel.org \
    --to=cel@kernel.org \
    --cc=Dai.Ngo@oracle.com \
    --cc=jlayton@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=netdev@vger.kernel.org \
    --cc=okorniev@redhat.com \
    --cc=rmacklem@uoguelph.ca \
    --cc=tom@talpey.com \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox