Linux NFS development
 help / color / mirror / Atom feed
* [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight
@ 2026-10-30 15:30 Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 1/6] NFSv4: add a tracepoint for bulk CB_LAYOUTRECALL replies Benjamin Coddington
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

A client can reply NFS4ERR_NOMATCHING_LAYOUT to a CB_LAYOUTRECALL while
flexfiles DS I/O on that layout is still outstanding.  The server then
treats the layout as returned, and that I/O can reach the storage device
after the recall has completed.  We found two ways to get there:

 - A bulk recall (FSID or ALL) that finds segments pinned by I/O
   invalidates the layout stateid and replies NFS4ERR_DELAY.  The resent
   recall skips layouts with an invalid stateid, finds nothing, and gets
   NFS4ERR_NOMATCHING_LAYOUT.

 - If another path has already invalidated the stateid without a
   LAYOUTRETURN, for example pnfs_destroy_layout() after an ESTALE, then
   either kind of recall gets NFS4ERR_NOMATCHING_LAYOUT right away.

Patch 1 adds a tracepoint for bulk recall replies.  Patch 2 clears
NFS_LAYOUT_BULK_RECALL once an invalid layout's last segment is freed;
flexfiles marks every segment for return when its I/O completes, so the
flag otherwise stays set until the layout header is freed.  Patch 3 fixes
a leak of segment references when ->prepare_layoutcommit() fails, which
would otherwise leave the next two patches answering NFS4ERR_DELAY
forever.  Patch 4 has the bulk walker collect invalid layouts that still
have segments, so the resent recall gets NFS4ERR_DELAY until they drain.
Patch 5 does the same for a file recall, and sets NFS_LAYOUT_BULK_RECALL
so that I/O needing a new layout doesn't wait on a LAYOUTGET the server
won't grant while the recall is outstanding.  Patch 6 lets flexfiles
writes go through the MDS while that recall drains, even when the layout
carries FF_FLAGS_NO_IO_THRU_MDS, which RFC 8435 makes a hint; without it
those writers retry without any delay until the drain finishes.

Patch 4 was posted earlier on its own; its changelog is below the fold.

Tested by sending FSID, ALL and FILE recalls on demand, resent every
second after NFS4ERR_DELAY, while the client's DS traffic was held (DS
stopped, or dropped with iptables) during a 64MB O_DIRECT write:

 - Unpatched, an FSID recall gets NFS4ERR_DELAY, then
   NFS4ERR_NOMATCHING_LAYOUT on the first resend with the write still
   held.

 - Patched, FSID and ALL recalls get NFS4ERR_DELAY until the DS I/O
   completes or times out, then NFS4ERR_NOMATCHING_LAYOUT.  With no I/O
   outstanding the first reply is NFS4ERR_NOMATCHING_LAYOUT.

 - Patched, with the layout invalidated by NFS4ERR_STALE on an
   OPEN_DOWNGRADE, a file recall gets NFS4ERR_DELAY until the I/O
   drains.  With clora_changed set the I/O is cancelled and the resend
   gets NFS4ERR_NOMATCHING_LAYOUT.

 - With FF_FLAGS_NO_IO_THRU_MDS, a second O_DIRECT writer to a file
   whose recall was draining wrote its 8MB through the MDS in 23ms with
   patch 6.  Without it, the writer retried about 330,000 times in ten
   seconds until the hold was released.

Patch 2 was not exercised directly: in these runs the layout header was
freed once the I/O drained.

Changes in v2:
 - Patch 3: only reschedule the LAYOUTCOMMIT if the layout stateid is
   still valid.  Putting the segment references could free the layout
   header and leave NFS_INO_LAYOUTCOMMIT set, so the next
   pnfs_layoutcommit_inode() dereferenced a NULL nfsi->layout.

v1: https://lore.kernel.org/linux-nfs/cover.1793298149.git.bcodding@hammerspace.com/

Benjamin Coddington (6):
  NFSv4: add a tracepoint for bulk CB_LAYOUTRECALL replies
  pNFS: clear NFS_LAYOUT_BULK_RECALL when an invalid layout's last
    segment is freed
  pNFS: don't leak layout segments when prepare_layoutcommit fails
  pNFS: don't send _NOMATCHING_LAYOUT if still doing IO on a layout
  pNFS: answer a file recall with NFS4ERR_DELAY while I/O drains
  pNFS/flexfiles: write through the MDS while a recalled layout drains

 fs/nfs/callback_proc.c                 |  19 ++++-
 fs/nfs/flexfilelayout/flexfilelayout.c |   5 +-
 fs/nfs/nfs4trace.h                     |  49 ++++++++++++
 fs/nfs/pnfs.c                          | 105 +++++++++++++++++++++----
 fs/nfs/pnfs.h                          |   4 +
 5 files changed, 163 insertions(+), 19 deletions(-)


base-commit: ef30b3efb7e8bae94d790a6503a595e1051323e7
-- 
2.53.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v2 1/6] NFSv4: add a tracepoint for bulk CB_LAYOUTRECALL replies
  2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
@ 2026-10-30 15:30 ` Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 2/6] pNFS: clear NFS_LAYOUT_BULK_RECALL when an invalid layout's last segment is freed Benjamin Coddington
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

There is a tracepoint for the reply to a CB_LAYOUTRECALL of a file, but
none for a recall by fsid or of all layouts.  Add
nfs4_cb_layoutrecall_bulk, which records the recall type, the fsid, and
whether the client replied NFS4ERR_DELAY or NFS4ERR_NOMATCHING_LAYOUT.

Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/callback_proc.c |  7 +++++-
 fs/nfs/nfs4trace.h     | 49 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index 9d836cb72e78..722bb5475a48 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -352,8 +352,13 @@ __be32 nfs4_callback_layoutrecall(void *argp, void *resp,
 	struct cb_layoutrecallargs *args = argp;
 	u32 res = NFS4ERR_OP_NOT_IN_SESSION;
 
-	if (cps->clp)
+	if (cps->clp) {
 		res = do_callback_layoutrecall(cps->clp, args, cps);
+		if (args->cbl_recall_type != RETURN_FILE)
+			trace_nfs4_cb_layoutrecall_bulk(cps->clp,
+							args->cbl_recall_type,
+							&args->cbl_fsid, -res);
+	}
 	return cpu_to_be32(res);
 }
 
diff --git a/fs/nfs/nfs4trace.h b/fs/nfs/nfs4trace.h
index b5c89eeef2bc..e5abc186ecc4 100644
--- a/fs/nfs/nfs4trace.h
+++ b/fs/nfs/nfs4trace.h
@@ -1571,6 +1571,55 @@ TRACE_EVENT(nfs4_cb_layoutrecall_file,
 		)
 );
 
+TRACE_DEFINE_ENUM(RETURN_FSID);
+TRACE_DEFINE_ENUM(RETURN_ALL);
+
+#define show_layoutrecall_type(type) \
+	__print_symbolic(type, \
+		{ RETURN_FSID,	"FSID" }, \
+		{ RETURN_ALL,	"ALL" })
+
+TRACE_EVENT(nfs4_cb_layoutrecall_bulk,
+		TP_PROTO(
+			const struct nfs_client *clp,
+			u32 recall_type,
+			const struct nfs_fsid *fsid,
+			int error
+		),
+
+		TP_ARGS(clp, recall_type, fsid, error),
+
+		TP_STRUCT__entry(
+			__field(unsigned long, error)
+			__field(u32, recall_type)
+			__field(u64, fsid_major)
+			__field(u64, fsid_minor)
+			__string(dstaddr, clp ? clp->cl_hostname : "unknown")
+		),
+
+		TP_fast_assign(
+			__entry->error = error < 0 ? -error : 0;
+			__entry->recall_type = recall_type;
+			if (recall_type == RETURN_FSID) {
+				__entry->fsid_major = fsid->major;
+				__entry->fsid_minor = fsid->minor;
+			} else {
+				__entry->fsid_major = 0;
+				__entry->fsid_minor = 0;
+			}
+			__assign_str(dstaddr);
+		),
+
+		TP_printk(
+			"error=%ld (%s) type=%s fsid=%llx:%llx dstaddr=%s",
+			-__entry->error,
+			show_nfs4_status(__entry->error),
+			show_layoutrecall_type(__entry->recall_type),
+			__entry->fsid_major, __entry->fsid_minor,
+			__get_str(dstaddr)
+		)
+);
+
 #define show_stateid_type(type) \
 	__print_symbolic(type, \
 		{ NFS4_INVALID_STATEID_TYPE,	"INVALID" }, \
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 2/6] pNFS: clear NFS_LAYOUT_BULK_RECALL when an invalid layout's last segment is freed
  2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 1/6] NFSv4: add a tracepoint for bulk CB_LAYOUTRECALL replies Benjamin Coddington
@ 2026-10-30 15:30 ` Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 3/6] pNFS: don't leak layout segments when prepare_layoutcommit fails Benjamin Coddington
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

pnfs_layout_remove_lseg() clears NFS_LAYOUT_BULK_RECALL when the last
segment is removed, but not if that segment is marked for return or the
layout has a return requested or in progress.  I/O errors in any layout
driver can mark segments for return, and flexfiles marks a segment for
return on every I/O completion, so after a bulk recall that had to
wait for outstanding I/O the flag usually stays set until the layout
header is freed.  While it is set, pnfs_update_layout() sends I/O through
the MDS without checking whether the fail bits have expired, so if a fail
bit holds the layout header, I/O goes through the MDS until the layout is
destroyed.

NFS_LAYOUT_BULK_RECALL is only set on a layout with an invalid stateid,
and the stateid can't become valid again while the flag is set.  Clear the
flag whenever the last segment of an invalid layout is removed, ahead of
the checks that guard setting NFS_LAYOUT_INVALID_STID.

Fixes: abb3e1c8777e ("pNFS: Don't mark the layout as freed if the last lseg is marked for return")
Fixes: 7b650994ab07 ("pNFS: Don't clear the layout stateid if a layout return is outstanding")
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/pnfs.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index a057a06318c7..6d391985fb21 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -582,15 +582,15 @@ pnfs_layout_remove_lseg(struct pnfs_layout_hdr *lo,
 	list_del_init(&lseg->pls_list);
 	/* Matched by pnfs_get_layout_hdr in pnfs_layout_insert_lseg */
 	refcount_dec(&lo->plh_refcount);
+	if (list_empty(&lo->plh_segs) && !pnfs_layout_is_valid(lo))
+		clear_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags);
 	if (test_bit(NFS_LSEG_LAYOUTRETURN, &lseg->pls_flags))
 		return;
 	if (list_empty(&lo->plh_segs) &&
 	    !test_bit(NFS_LAYOUT_RETURN_REQUESTED, &lo->plh_flags) &&
-	    !test_bit(NFS_LAYOUT_RETURN, &lo->plh_flags)) {
-		if (atomic_read(&lo->plh_outstanding) == 0)
-			set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags);
-		clear_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags);
-	}
+	    !test_bit(NFS_LAYOUT_RETURN, &lo->plh_flags) &&
+	    atomic_read(&lo->plh_outstanding) == 0)
+		set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags);
 }
 
 static bool
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 3/6] pNFS: don't leak layout segments when prepare_layoutcommit fails
  2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 1/6] NFSv4: add a tracepoint for bulk CB_LAYOUTRECALL replies Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 2/6] pNFS: clear NFS_LAYOUT_BULK_RECALL when an invalid layout's last segment is freed Benjamin Coddington
@ 2026-10-30 15:30 ` Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 4/6] pNFS: don't send _NOMATCHING_LAYOUT if still doing IO on a layout Benjamin Coddington
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

pnfs_list_write_lseg() clears NFS_LSEG_LAYOUTCOMMIT on each segment that
needs a LAYOUTCOMMIT and moves the reference that flag held onto
data->lseg_list, to be put when the LAYOUTCOMMIT completes.  If
->prepare_layoutcommit() fails with anything but -ENOSPC, no LAYOUTCOMMIT
is sent and data is freed with the segments still on that list, so their
references are never put.  The segments and their layout header are then
never freed.

Before freeing data, give each segment's reference back to
NFS_LSEG_LAYOUTCOMMIT if the layout stateid is still valid and the flag
hasn't been set again, so that the next LAYOUTCOMMIT, which the error path
has already rescheduled, takes it.  That keeps a segment invalidated
meanwhile pinned until its LAYOUTCOMMIT is sent, as it would have been.
Otherwise put the reference with pnfs_list_write_lseg_done(): if the
layout stateid was invalidated, every other LAYOUTCOMMIT reference has
already been dropped, and if the flag was set again, it holds a reference
of its own.

Once the segments can be freed, the layout header can be too, so only
reschedule the LAYOUTCOMMIT if the layout stateid is still valid.
pnfs_mark_layout_stateid_invalid() clears NFS_INO_LAYOUTCOMMIT, and
setting it again would leave the next pnfs_layoutcommit_inode() to
dereference a NULL nfsi->layout.

Fixes: 5f919c9f10c1 ("pnfs: allow splicing pre-encoded pages into the layoutcommit args")
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/pnfs.c | 28 +++++++++++++++++++++++-----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index 6d391985fb21..393de479da3b 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -3634,6 +3634,17 @@ static void pnfs_list_write_lseg(struct inode *inode, struct list_head *listp)
 	}
 }
 
+static void pnfs_list_write_lseg_undo(struct list_head *listp)
+{
+	struct pnfs_layout_segment *lseg, *tmp;
+
+	list_for_each_entry_safe(lseg, tmp, listp, pls_lc_list) {
+		if (pnfs_layout_is_valid(lseg->pls_layout) &&
+		    !test_and_set_bit(NFS_LSEG_LAYOUTCOMMIT, &lseg->pls_flags))
+			list_del_init(&lseg->pls_lc_list);
+	}
+}
+
 static void pnfs_list_write_lseg_done(struct inode *inode, struct list_head *listp)
 {
 	struct pnfs_layout_segment *lseg, *tmp;
@@ -3763,11 +3774,18 @@ pnfs_layoutcommit_inode(struct inode *inode, bool sync)
 			if (status != -ENOSPC)
 				put_cred(data->cred);
 			spin_lock(&inode->i_lock);
-			set_bit(NFS_INO_LAYOUTCOMMIT, &nfsi->flags);
-			if (end_pos > nfsi->layout->plh_lwb)
-				nfsi->layout->plh_lwb = end_pos;
-			if (status != -ENOSPC)
-				goto out_unlock;
+			if (pnfs_layout_is_valid(nfsi->layout)) {
+				set_bit(NFS_INO_LAYOUTCOMMIT, &nfsi->flags);
+				if (end_pos > nfsi->layout->plh_lwb)
+					nfsi->layout->plh_lwb = end_pos;
+			}
+			if (status != -ENOSPC) {
+				pnfs_list_write_lseg_undo(&data->lseg_list);
+				spin_unlock(&inode->i_lock);
+				pnfs_list_write_lseg_done(inode, &data->lseg_list);
+				kfree(data);
+				goto out;
+			}
 			spin_unlock(&inode->i_lock);
 			mark_as_dirty = true;
 		}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 4/6] pNFS: don't send _NOMATCHING_LAYOUT if still doing IO on a layout
  2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
                   ` (2 preceding siblings ...)
  2026-10-30 15:30 ` [PATCH v2 3/6] pNFS: don't leak layout segments when prepare_layoutcommit fails Benjamin Coddington
@ 2026-10-30 15:30 ` Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 5/6] pNFS: answer a file recall with NFS4ERR_DELAY while I/O drains Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 6/6] pNFS/flexfiles: write through the MDS while a recalled layout drains Benjamin Coddington
  5 siblings, 0 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

When a bulk CB_LAYOUTRECALL finds layout segments still referenced by
outstanding I/O, pnfs_layout_free_bulk_destroy_list() invalidates the
layout stateid, sets NFS_LAYOUT_BULK_RECALL, and the client replies
NFS4ERR_DELAY.  If the server resends the recall, then
pnfs_layout_bulk_destroy_byserver_locked() can skip layouts with an invalid
stateid, so the resend finds nothing and the client replies
NFS4ERR_NOMATCHING_LAYOUT with the I/O still in flight.  The server then
treats the layout as gone, and that I/O can reach the storage device after
the recall has completed.  The same happens on the first recall if another
path, such as a DS error, invalidated the layout stateid while I/O was
outstanding.

When answering a bulk recall, don't skip an invalid layout that still has
segments.  pnfs_mark_layout_stateid_invalid() reports them, and the recall
is answered with NFS4ERR_DELAY until the last segment is freed.
CB_RECALL_ANY takes the same path, so it also blocks LAYOUTGET on these
layouts until they drain.  This relies on "pNFS: clear
NFS_LAYOUT_BULK_RECALL when an invalid layout's last segment is freed"
to clear the flag once the layout drains.

The other walkers, used by reboot recovery and when recallable state is
revoked, keep skipping these layouts.  They send no reply to the server,
and reboot recovery would otherwise retry on -EAGAIN without sleeping
until the I/O completed.

Fixes: b85f562049cc ("pNFS: Skip invalid stateids when doing a bulk destroy")
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
v3: test for remaining segments instead of NFS_LAYOUT_BULK_RECALL, so a
    layout invalidated by another path with I/O outstanding is also
    answered with NFS4ERR_DELAY.
v2: only revisit these layouts when answering a bulk recall, so that
    reboot recovery doesn't loop on -EAGAIN while the I/O is outstanding.
---
 fs/nfs/pnfs.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index 393de479da3b..a23bcb8503be 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -846,7 +846,8 @@ pnfs_layout_add_bulk_destroy_list(struct inode *inode,
 static int
 pnfs_layout_bulk_destroy_byserver_locked(struct nfs_client *clp,
 		struct nfs_server *server,
-		struct list_head *layout_list)
+		struct list_head *layout_list,
+		enum pnfs_layout_destroy_mode mode)
 	__must_hold(&clp->cl_lock)
 	__must_hold(RCU)
 {
@@ -854,7 +855,9 @@ pnfs_layout_bulk_destroy_byserver_locked(struct nfs_client *clp,
 	struct inode *inode;
 
 	list_for_each_entry_safe(lo, next, &server->layouts, plh_layouts) {
-		if (test_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags) ||
+		if ((test_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags) &&
+		     (mode != PNFS_LAYOUT_BULK_RETURN ||
+		      list_empty(&lo->plh_segs))) ||
 		    test_bit(NFS_LAYOUT_INODE_FREEING, &lo->plh_flags) ||
 		    !list_empty(&lo->plh_bulk_destroy))
 			continue;
@@ -933,7 +936,7 @@ int pnfs_layout_destroy_byfsid(struct nfs_client *clp, struct nfs_fsid *fsid,
 			continue;
 		if (pnfs_layout_bulk_destroy_byserver_locked(clp,
 				server,
-				&layout_list) != 0)
+				&layout_list, mode) != 0)
 			goto restart;
 	}
 	rcu_read_unlock();
@@ -943,7 +946,8 @@ int pnfs_layout_destroy_byfsid(struct nfs_client *clp, struct nfs_fsid *fsid,
 }
 
 static void pnfs_layout_build_destroy_list_byclient(struct nfs_client *clp,
-						    struct list_head *list)
+						    struct list_head *list,
+						    enum pnfs_layout_destroy_mode mode)
 {
 	struct nfs_server *server;
 
@@ -952,7 +956,7 @@ static void pnfs_layout_build_destroy_list_byclient(struct nfs_client *clp,
 restart:
 	list_for_each_entry_rcu(server, &clp->cl_superblocks, client_link) {
 		if (pnfs_layout_bulk_destroy_byserver_locked(clp, server,
-							     list) != 0)
+							     list, mode) != 0)
 			goto restart;
 	}
 	rcu_read_unlock();
@@ -963,7 +967,7 @@ static int pnfs_layout_do_destroy_byclid(struct nfs_client *clp,
 					 struct list_head *list,
 					 enum pnfs_layout_destroy_mode mode)
 {
-	pnfs_layout_build_destroy_list_byclient(clp, list);
+	pnfs_layout_build_destroy_list_byclient(clp, list, mode);
 	return pnfs_layout_free_bulk_destroy_list(list, mode);
 }
 
@@ -1000,7 +1004,7 @@ static void pnfs_layout_build_recover_list_byclient(struct nfs_client *clp,
 		if (!(server->caps & NFS_CAP_REBOOT_LAYOUTRETURN))
 			continue;
 		if (pnfs_layout_bulk_destroy_byserver_locked(clp, server,
-							     list) != 0)
+					list, PNFS_LAYOUT_INVALIDATE) != 0)
 			goto restart;
 	}
 	rcu_read_unlock();
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 5/6] pNFS: answer a file recall with NFS4ERR_DELAY while I/O drains
  2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
                   ` (3 preceding siblings ...)
  2026-10-30 15:30 ` [PATCH v2 4/6] pNFS: don't send _NOMATCHING_LAYOUT if still doing IO on a layout Benjamin Coddington
@ 2026-10-30 15:30 ` Benjamin Coddington
  2026-10-30 15:30 ` [PATCH v2 6/6] pNFS/flexfiles: write through the MDS while a recalled layout drains Benjamin Coddington
  5 siblings, 0 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

A layout stateid can be invalidated without a LAYOUTRETURN, for example by
pnfs_destroy_layout() after an NFS4ERR_BADHANDLE, while I/O still holds
its layout segments.  pnfs_check_callback_stateid() then answers a
CB_LAYOUTRECALL for the file with NFS4ERR_NOMATCHING_LAYOUT, so the server
treats the layout as returned while that I/O can still reach the storage
device.

If the recall matches the invalid layout stateid and a segment within the
recalled range is still in use, answer NFS4ERR_DELAY.  As for a valid
layout, cancel the I/O on those segments if clora_changed is set, but only
for the first such recall.  Set NFS_LAYOUT_BULK_RECALL so that I/O needing
a new layout goes through the MDS, or is retried if the layout doesn't
allow I/O through the MDS, rather than waiting on a LAYOUTGET the server
won't grant while the recall is outstanding.  The flag is cleared when the
last segment is freed, and the resent recall is answered
NFS4ERR_NOMATCHING_LAYOUT.  This relies on "pNFS: clear
NFS_LAYOUT_BULK_RECALL when an invalid layout's last segment is freed" to
clear the flag.

Fixes: bd3d16a887b0 ("NFSv4.1: Fix a potential layoutget/layoutrecall deadlock")
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/callback_proc.c | 12 ++++++++++++
 fs/nfs/pnfs.c          | 30 ++++++++++++++++++++++++++++++
 fs/nfs/pnfs.h          |  3 +++
 3 files changed, 45 insertions(+)

diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index 722bb5475a48..a132990194cb 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -276,6 +276,18 @@ static u32 initiate_file_draining(struct nfs_client *clp,
 		goto out;
 	}
 	pnfs_get_layout_hdr(lo);
+	if (!pnfs_layout_is_valid(lo) &&
+	    nfs4_stateid_match_other(&lo->plh_stateid, &args->cbl_stateid) &&
+	    pnfs_invalid_layout_busy(lo, &args->cbl_range,
+				     be32_to_cpu(args->cbl_stateid.seqid),
+				     args->cbl_layoutchanged &&
+				     !test_bit(NFS_LAYOUT_BULK_RECALL,
+					       &lo->plh_flags))) {
+		/* Block LAYOUTGET until I/O on the invalidated layout drains */
+		set_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags);
+		rv = NFS4ERR_DELAY;
+		goto unlock;
+	}
 	rv = pnfs_check_callback_stateid(lo, &args->cbl_stateid, cps);
 	if (rv != NFS_OK)
 		goto unlock;
diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index a23bcb8503be..b0bdd40de4dd 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -2766,6 +2766,36 @@ pnfs_mark_matching_lsegs_return(struct pnfs_layout_hdr *lo,
 	return -ENOENT;
 }
 
+/**
+ * pnfs_invalid_layout_busy - check for I/O on segments of an invalid layout
+ * @lo: pointer to the invalid layout header
+ * @recall_range: range being recalled
+ * @seq: seqid of the recall
+ * @cancel_io: cancel I/O on the matching segments
+ *
+ * Returns true if any segment matching the recall is still in use.
+ */
+bool pnfs_invalid_layout_busy(struct pnfs_layout_hdr *lo,
+			      const struct pnfs_layout_range *recall_range,
+			      u32 seq, bool cancel_io)
+{
+	struct nfs_server *server = NFS_SERVER(lo->plh_inode);
+	struct pnfs_layout_segment *lseg;
+	bool busy = false;
+
+	assert_spin_locked(&lo->plh_inode->i_lock);
+
+	list_for_each_entry(lseg, &lo->plh_segs, pls_list) {
+		if (!pnfs_match_lseg_recall(lseg, recall_range, seq))
+			continue;
+		busy = true;
+		if (!cancel_io)
+			break;
+		pnfs_lseg_cancel_io(server, lseg, NULL);
+	}
+	return busy;
+}
+
 static void
 pnfs_mark_layout_for_return(struct inode *inode,
 			    const struct pnfs_layout_range *range,
diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h
index 2774d4adf4c3..0fb3c7fd8a27 100644
--- a/fs/nfs/pnfs.h
+++ b/fs/nfs/pnfs.h
@@ -323,6 +323,9 @@ int pnfs_mark_matching_lsegs_return(struct pnfs_layout_hdr *lo,
 				const struct pnfs_layout_range *recall_range,
 				u32 seq, bool cancel_io,
 				const struct nfs4_deviceid *devid);
+bool pnfs_invalid_layout_busy(struct pnfs_layout_hdr *lo,
+			      const struct pnfs_layout_range *recall_range,
+			      u32 seq, bool cancel_io);
 int pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo,
 		struct list_head *lseg_list);
 bool pnfs_roc(struct inode *ino, struct nfs4_layoutreturn_args *args,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 6/6] pNFS/flexfiles: write through the MDS while a recalled layout drains
  2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
                   ` (4 preceding siblings ...)
  2026-10-30 15:30 ` [PATCH v2 5/6] pNFS: answer a file recall with NFS4ERR_DELAY while I/O drains Benjamin Coddington
@ 2026-10-30 15:30 ` Benjamin Coddington
  5 siblings, 0 replies; 7+ messages in thread
From: Benjamin Coddington @ 2026-10-30 15:30 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker, Mike Snitzer; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

When a layout carries FF_FLAGS_NO_IO_THRU_MDS and pnfs_update_layout()
returns no layout, ff_layout_pg_get_mirror_count_write() returns -EAGAIN,
and nfs_writepages() and the O_DIRECT reschedule path retry the write at
once.  pnfs_update_layout() returns no layout while NFS_LAYOUT_BULK_RECALL
is set, which lasts until the I/O on a recalled layout completes, so
writers spin for as long as that takes.

FF_FLAGS_NO_IO_THRU_MDS is a hint (RFC 8435, section 5.1.2), so while a
recall is draining, write through the MDS instead.

Fixes: 1d62e659c0bf ("NFSv4/flexfiles: honor FF_FLAGS_NO_IO_THRU_MDS in pg_get_mirror_count_write")
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/flexfilelayout/flexfilelayout.c |  5 ++++-
 fs/nfs/pnfs.c                          | 19 +++++++++++++++++++
 fs/nfs/pnfs.h                          |  1 +
 3 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/flexfilelayout/flexfilelayout.c b/fs/nfs/flexfilelayout/flexfilelayout.c
index 9492d6bc1d1a..9777c83645ad 100644
--- a/fs/nfs/flexfilelayout/flexfilelayout.c
+++ b/fs/nfs/flexfilelayout/flexfilelayout.c
@@ -1222,11 +1222,14 @@ ff_layout_pg_get_mirror_count_write(struct nfs_pageio_descriptor *pgio,
 			NFS_I(pgio->pg_inode)->layout,
 			pgio->pg_lseg);
 	if (NFS_I(pgio->pg_inode)->layout &&
-	    ff_layout_hdr_no_fallback_to_mds(NFS_I(pgio->pg_inode)->layout)) {
+	    ff_layout_hdr_no_fallback_to_mds(NFS_I(pgio->pg_inode)->layout) &&
+	    !pnfs_layout_recall_draining(pgio->pg_inode)) {
 		/*
 		 * FF_FLAGS_NO_IO_THRU_MDS: no current lseg but the server's
 		 * policy forbids MDS fallback.  Surface -EAGAIN so writeback
 		 * retries rather than silently issuing the WRITE via MDS.
+		 * The flag is a hint (RFC 8435 5.1.2), so don't wait for a
+		 * draining recall.
 		 */
 		pgio->pg_error = -EAGAIN;
 		goto out;
diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index b0bdd40de4dd..ea87e95f1083 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -593,6 +593,25 @@ pnfs_layout_remove_lseg(struct pnfs_layout_hdr *lo,
 		set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags);
 }
 
+/**
+ * pnfs_layout_recall_draining - check for a recalled layout still in use
+ * @inode: inode to check
+ *
+ * Returns true while I/O on a recalled layout keeps LAYOUTGET blocked.
+ */
+bool pnfs_layout_recall_draining(struct inode *inode)
+{
+	struct pnfs_layout_hdr *lo;
+	bool ret;
+
+	spin_lock(&inode->i_lock);
+	lo = NFS_I(inode)->layout;
+	ret = lo && test_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags);
+	spin_unlock(&inode->i_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pnfs_layout_recall_draining);
+
 static bool
 pnfs_cache_lseg_for_layoutreturn(struct pnfs_layout_hdr *lo,
 		struct pnfs_layout_segment *lseg)
diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h
index 0fb3c7fd8a27..2a4a1d7699f1 100644
--- a/fs/nfs/pnfs.h
+++ b/fs/nfs/pnfs.h
@@ -310,6 +310,7 @@ bool nfs4_layout_refresh_old_stateid(nfs4_stateid *dst,
 		struct pnfs_layout_range *dst_range,
 		struct inode *inode);
 void pnfs_put_layout_hdr(struct pnfs_layout_hdr *lo);
+bool pnfs_layout_recall_draining(struct inode *inode);
 void pnfs_set_layout_stateid(struct pnfs_layout_hdr *lo,
 			     const nfs4_stateid *new,
 			     const struct cred *cred,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-30 15:30 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 15:30 [PATCH v2 0/6] pNFS: don't reply NOMATCHING to a layout recall with I/O in flight Benjamin Coddington
2026-10-30 15:30 ` [PATCH v2 1/6] NFSv4: add a tracepoint for bulk CB_LAYOUTRECALL replies Benjamin Coddington
2026-10-30 15:30 ` [PATCH v2 2/6] pNFS: clear NFS_LAYOUT_BULK_RECALL when an invalid layout's last segment is freed Benjamin Coddington
2026-10-30 15:30 ` [PATCH v2 3/6] pNFS: don't leak layout segments when prepare_layoutcommit fails Benjamin Coddington
2026-10-30 15:30 ` [PATCH v2 4/6] pNFS: don't send _NOMATCHING_LAYOUT if still doing IO on a layout Benjamin Coddington
2026-10-30 15:30 ` [PATCH v2 5/6] pNFS: answer a file recall with NFS4ERR_DELAY while I/O drains Benjamin Coddington
2026-10-30 15:30 ` [PATCH v2 6/6] pNFS/flexfiles: write through the MDS while a recalled layout drains Benjamin Coddington

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox