4cf99ad
From 675f11241a9f5b434effc7aee9eb84bf3d17d685 Mon Sep 17 00:00:00 2001
4cf99ad
From: Trond Myklebust <trond.myklebust@hammerspace.com>
4cf99ad
Date: Wed, 20 Jun 2018 17:53:34 -0400
4cf99ad
Subject: [PATCH] NFSv4.1: Avoid false retries when RPC calls are interrupted
4cf99ad
4cf99ad
A 'false retry' in NFSv4.1 occurs when the client attempts to transmit a
4cf99ad
new RPC call using a slot+sequence number combination that references an
4cf99ad
already cached one. Currently, the Linux NFS client will do this if a
4cf99ad
user process interrupts an RPC call that is in progress.
4cf99ad
The problem with doing so is that we defeat the main mechanism used by
4cf99ad
the server to differentiate between a new call and a replayed one. Even
4cf99ad
if the server is able to perfectly cache the arguments of the old call,
4cf99ad
it cannot know if the client intended to replay or send a new call.
4cf99ad
4cf99ad
The obvious fix is to bump the sequence number pre-emptively if an
4cf99ad
RPC call is interrupted, but in order to deal with the corner cases
4cf99ad
where the interrupted call is not actually received and processed by
4cf99ad
the server, we need to interpret the error NFS4ERR_SEQ_MISORDERED
4cf99ad
as a sign that we need to either wait or locate a correct sequence
4cf99ad
number that lies between the value we sent, and the last value that
4cf99ad
was acked by a SEQUENCE call on that slot.
4cf99ad
4cf99ad
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
4cf99ad
---
4cf99ad
 fs/nfs/nfs4proc.c    | 105 ++++++++++++++++++++-----------------------
4cf99ad
 fs/nfs/nfs4session.c |   5 ++-
4cf99ad
 fs/nfs/nfs4session.h |   5 ++-
4cf99ad
 3 files changed, 55 insertions(+), 60 deletions(-)
4cf99ad
4cf99ad
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
4cf99ad
index 64ac80ec6b7b..3a6a9c9ee369 100644
4cf99ad
--- a/fs/nfs/nfs4proc.c
4cf99ad
+++ b/fs/nfs/nfs4proc.c
4cf99ad
@@ -730,13 +730,25 @@ static void nfs41_sequence_free_slot(struct nfs4_sequence_res *res)
4cf99ad
 	res->sr_slot = NULL;
4cf99ad
 }
4cf99ad
 
4cf99ad
+static void nfs4_slot_sequence_record_sent(struct nfs4_slot *slot,
4cf99ad
+		u32 seqnr)
4cf99ad
+{
4cf99ad
+	if ((s32)(seqnr - slot->seq_nr_highest_sent) > 0)
4cf99ad
+		slot->seq_nr_highest_sent = seqnr;
4cf99ad
+}
4cf99ad
+static void nfs4_slot_sequence_acked(struct nfs4_slot *slot,
4cf99ad
+		u32 seqnr)
4cf99ad
+{
4cf99ad
+	slot->seq_nr_highest_sent = seqnr;
4cf99ad
+	slot->seq_nr_last_acked = seqnr;
4cf99ad
+}
4cf99ad
+
4cf99ad
 static int nfs41_sequence_process(struct rpc_task *task,
4cf99ad
 		struct nfs4_sequence_res *res)
4cf99ad
 {
4cf99ad
 	struct nfs4_session *session;
4cf99ad
 	struct nfs4_slot *slot = res->sr_slot;
4cf99ad
 	struct nfs_client *clp;
4cf99ad
-	bool interrupted = false;
4cf99ad
 	int ret = 1;
4cf99ad
 
4cf99ad
 	if (slot == NULL)
4cf99ad
@@ -747,16 +759,12 @@ static int nfs41_sequence_process(struct rpc_task *task,
4cf99ad
 
4cf99ad
 	session = slot->table->session;
4cf99ad
 
4cf99ad
-	if (slot->interrupted) {
4cf99ad
-		if (res->sr_status != -NFS4ERR_DELAY)
4cf99ad
-			slot->interrupted = 0;
4cf99ad
-		interrupted = true;
4cf99ad
-	}
4cf99ad
-
4cf99ad
 	trace_nfs4_sequence_done(session, res);
4cf99ad
 	/* Check the SEQUENCE operation status */
4cf99ad
 	switch (res->sr_status) {
4cf99ad
 	case 0:
4cf99ad
+		/* Mark this sequence number as having been acked */
4cf99ad
+		nfs4_slot_sequence_acked(slot, slot->seq_nr);
4cf99ad
 		/* Update the slot's sequence and clientid lease timer */
4cf99ad
 		slot->seq_done = 1;
4cf99ad
 		clp = session->clp;
4cf99ad
@@ -771,9 +779,9 @@ static int nfs41_sequence_process(struct rpc_task *task,
4cf99ad
 		 * sr_status remains 1 if an RPC level error occurred.
4cf99ad
 		 * The server may or may not have processed the sequence
4cf99ad
 		 * operation..
4cf99ad
-		 * Mark the slot as having hosted an interrupted RPC call.
4cf99ad
 		 */
4cf99ad
-		slot->interrupted = 1;
4cf99ad
+		nfs4_slot_sequence_record_sent(slot, slot->seq_nr);
4cf99ad
+		slot->seq_done = 1;
4cf99ad
 		goto out;
4cf99ad
 	case -NFS4ERR_DELAY:
4cf99ad
 		/* The server detected a resend of the RPC call and
4cf99ad
@@ -784,6 +792,7 @@ static int nfs41_sequence_process(struct rpc_task *task,
4cf99ad
 			__func__,
4cf99ad
 			slot->slot_nr,
4cf99ad
 			slot->seq_nr);
4cf99ad
+		nfs4_slot_sequence_acked(slot, slot->seq_nr);
4cf99ad
 		goto out_retry;
4cf99ad
 	case -NFS4ERR_RETRY_UNCACHED_REP:
4cf99ad
 	case -NFS4ERR_SEQ_FALSE_RETRY:
4cf99ad
@@ -791,6 +800,7 @@ static int nfs41_sequence_process(struct rpc_task *task,
4cf99ad
 		 * The server thinks we tried to replay a request.
4cf99ad
 		 * Retry the call after bumping the sequence ID.
4cf99ad
 		 */
4cf99ad
+		nfs4_slot_sequence_acked(slot, slot->seq_nr);
4cf99ad
 		goto retry_new_seq;
4cf99ad
 	case -NFS4ERR_BADSLOT:
4cf99ad
 		/*
4cf99ad
@@ -801,21 +811,28 @@ static int nfs41_sequence_process(struct rpc_task *task,
4cf99ad
 			goto session_recover;
4cf99ad
 		goto retry_nowait;
4cf99ad
 	case -NFS4ERR_SEQ_MISORDERED:
4cf99ad
+		nfs4_slot_sequence_record_sent(slot, slot->seq_nr);
4cf99ad
 		/*
4cf99ad
-		 * Was the last operation on this sequence interrupted?
4cf99ad
-		 * If so, retry after bumping the sequence number.
4cf99ad
+		 * Were one or more calls using this slot interrupted?
4cf99ad
+		 * If the server never received the request, then our
4cf99ad
+		 * transmitted slot sequence number may be too high.
4cf99ad
 		 */
4cf99ad
-		if (interrupted)
4cf99ad
-			goto retry_new_seq;
4cf99ad
-		/*
4cf99ad
-		 * Could this slot have been previously retired?
4cf99ad
-		 * If so, then the server may be expecting seq_nr = 1!
4cf99ad
-		 */
4cf99ad
-		if (slot->seq_nr != 1) {
4cf99ad
-			slot->seq_nr = 1;
4cf99ad
+		if ((s32)(slot->seq_nr - slot->seq_nr_last_acked) > 1) {
4cf99ad
+			slot->seq_nr--;
4cf99ad
 			goto retry_nowait;
4cf99ad
 		}
4cf99ad
-		goto session_recover;
4cf99ad
+		/*
4cf99ad
+		 * RFC5661:
4cf99ad
+		 * A retry might be sent while the original request is
4cf99ad
+		 * still in progress on the replier. The replier SHOULD
4cf99ad
+		 * deal with the issue by returning NFS4ERR_DELAY as the
4cf99ad
+		 * reply to SEQUENCE or CB_SEQUENCE operation, but
4cf99ad
+		 * implementations MAY return NFS4ERR_SEQ_MISORDERED.
4cf99ad
+		 *
4cf99ad
+		 * Restart the search after a delay.
4cf99ad
+		 */
4cf99ad
+		slot->seq_nr = slot->seq_nr_highest_sent;
4cf99ad
+		goto out_retry;
4cf99ad
 	default:
4cf99ad
 		/* Just update the slot sequence no. */
4cf99ad
 		slot->seq_done = 1;
4cf99ad
@@ -906,17 +923,6 @@ static const struct rpc_call_ops nfs41_call_sync_ops = {
4cf99ad
 	.rpc_call_done = nfs41_call_sync_done,
4cf99ad
 };
4cf99ad
 
4cf99ad
-static void
4cf99ad
-nfs4_sequence_process_interrupted(struct nfs_client *client,
4cf99ad
-		struct nfs4_slot *slot, const struct cred *cred)
4cf99ad
-{
4cf99ad
-	struct rpc_task *task;
4cf99ad
-
4cf99ad
-	task = _nfs41_proc_sequence(client, cred, slot, true);
4cf99ad
-	if (!IS_ERR(task))
4cf99ad
-		rpc_put_task_async(task);
4cf99ad
-}
4cf99ad
-
4cf99ad
 #else	/* !CONFIG_NFS_V4_1 */
4cf99ad
 
4cf99ad
 static int nfs4_sequence_process(struct rpc_task *task, struct nfs4_sequence_res *res)
4cf99ad
@@ -937,14 +943,6 @@ int nfs4_sequence_done(struct rpc_task *task,
4cf99ad
 }
4cf99ad
 EXPORT_SYMBOL_GPL(nfs4_sequence_done);
4cf99ad
 
4cf99ad
-static void
4cf99ad
-nfs4_sequence_process_interrupted(struct nfs_client *client,
4cf99ad
-		struct nfs4_slot *slot, const struct cred *cred)
4cf99ad
-{
4cf99ad
-	WARN_ON_ONCE(1);
4cf99ad
-	slot->interrupted = 0;
4cf99ad
-}
4cf99ad
-
4cf99ad
 #endif	/* !CONFIG_NFS_V4_1 */
4cf99ad
 
4cf99ad
 static void nfs41_sequence_res_init(struct nfs4_sequence_res *res)
4cf99ad
@@ -985,26 +983,19 @@ int nfs4_setup_sequence(struct nfs_client *client,
4cf99ad
 		task->tk_timeout = 0;
4cf99ad
 	}
4cf99ad
 
4cf99ad
-	for (;;) {
4cf99ad
-		spin_lock(&tbl->slot_tbl_lock);
4cf99ad
-		/* The state manager will wait until the slot table is empty */
4cf99ad
-		if (nfs4_slot_tbl_draining(tbl) && !args->sa_privileged)
4cf99ad
-			goto out_sleep;
4cf99ad
-
4cf99ad
-		slot = nfs4_alloc_slot(tbl);
4cf99ad
-		if (IS_ERR(slot)) {
4cf99ad
-			/* Try again in 1/4 second */
4cf99ad
-			if (slot == ERR_PTR(-ENOMEM))
4cf99ad
-				task->tk_timeout = HZ >> 2;
4cf99ad
-			goto out_sleep;
4cf99ad
-		}
4cf99ad
-		spin_unlock(&tbl->slot_tbl_lock);
4cf99ad
+	spin_lock(&tbl->slot_tbl_lock);
4cf99ad
+	/* The state manager will wait until the slot table is empty */
4cf99ad
+	if (nfs4_slot_tbl_draining(tbl) && !args->sa_privileged)
4cf99ad
+		goto out_sleep;
4cf99ad
 
4cf99ad
-		if (likely(!slot->interrupted))
4cf99ad
-			break;
4cf99ad
-		nfs4_sequence_process_interrupted(client,
4cf99ad
-				slot, task->tk_msg.rpc_cred);
4cf99ad
+	slot = nfs4_alloc_slot(tbl);
4cf99ad
+	if (IS_ERR(slot)) {
4cf99ad
+		/* Try again in 1/4 second */
4cf99ad
+		if (slot == ERR_PTR(-ENOMEM))
4cf99ad
+			task->tk_timeout = HZ >> 2;
4cf99ad
+		goto out_sleep;
4cf99ad
 	}
4cf99ad
+	spin_unlock(&tbl->slot_tbl_lock);
4cf99ad
 
4cf99ad
 	nfs4_sequence_attach_slot(args, res, slot);
4cf99ad
 
4cf99ad
diff --git a/fs/nfs/nfs4session.c b/fs/nfs/nfs4session.c
4cf99ad
index a5489d70a724..39962c19744f 100644
4cf99ad
--- a/fs/nfs/nfs4session.c
4cf99ad
+++ b/fs/nfs/nfs4session.c
4cf99ad
@@ -110,6 +110,8 @@ static struct nfs4_slot *nfs4_new_slot(struct nfs4_slot_table  *tbl,
4cf99ad
 		slot->table = tbl;
4cf99ad
 		slot->slot_nr = slotid;
4cf99ad
 		slot->seq_nr = seq_init;
4cf99ad
+		slot->seq_nr_highest_sent = seq_init;
4cf99ad
+		slot->seq_nr_last_acked = seq_init - 1;
4cf99ad
 	}
4cf99ad
 	return slot;
4cf99ad
 }
4cf99ad
@@ -276,7 +278,8 @@ static void nfs4_reset_slot_table(struct nfs4_slot_table *tbl,
4cf99ad
 	p = &tbl->slots;
4cf99ad
 	while (*p) {
4cf99ad
 		(*p)->seq_nr = ivalue;
4cf99ad
-		(*p)->interrupted = 0;
4cf99ad
+		(*p)->seq_nr_highest_sent = ivalue;
4cf99ad
+		(*p)->seq_nr_last_acked = ivalue - 1;
4cf99ad
 		p = &(*p)->next;
4cf99ad
 	}
4cf99ad
 	tbl->highest_used_slotid = NFS4_NO_SLOT;
4cf99ad
diff --git a/fs/nfs/nfs4session.h b/fs/nfs/nfs4session.h
4cf99ad
index 3c550f297561..230509b77121 100644
4cf99ad
--- a/fs/nfs/nfs4session.h
4cf99ad
+++ b/fs/nfs/nfs4session.h
4cf99ad
@@ -23,8 +23,9 @@ struct nfs4_slot {
4cf99ad
 	unsigned long		generation;
4cf99ad
 	u32			slot_nr;
4cf99ad
 	u32		 	seq_nr;
4cf99ad
-	unsigned int		interrupted : 1,
4cf99ad
-				privileged : 1,
4cf99ad
+	u32		 	seq_nr_last_acked;
4cf99ad
+	u32		 	seq_nr_highest_sent;
4cf99ad
+	unsigned int		privileged : 1,
4cf99ad
 				seq_done : 1;
4cf99ad
 };
4cf99ad
 
4cf99ad
-- 
4cf99ad
2.20.1
4cf99ad