Linux NFS development
 help / color / mirror / Atom feed
* [PATCH 0/2] NFS: layout changes while building mirrored writes
@ 2026-10-15 20:58 Benjamin Coddington
  2026-10-15 20:58 ` [PATCH 1/2] NFS: Resend unsent mirror requests when an O_DIRECT write hits -EAGAIN Benjamin Coddington
  2026-10-15 20:58 ` [PATCH 2/2] NFS: Don't warn when a changed layout refuses a split subrequest Benjamin Coddington
  0 siblings, 2 replies; 3+ messages in thread
From: Benjamin Coddington @ 2026-10-15 20:58 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

These two fix trouble the pageio code has when a flexfiles layout is
recalled and comes back with a different mirror count while a write is
being built.  The first is data loss: an O_DIRECT write drops one
mirror's share of the bytes and still returns success.  The second is
a WARN_ON_ONCE in __nfs_pageio_add_request() that the same race trips
when a request has to be split, which looks like a pg_test bug but
isn't.

I reproduced this against a server that alternates each RW layout
between three mirrors and two and recalls it about a millisecond after
granting it, with mirrors at different wsizes (1M and 64K).  Without
these, 5 of 10 64MiB O_DIRECT rewrites left the 1M mirror holding the
previous data in 64K multiples.  With them I haven't seen a loss the
client was responsible for in a few hundred iterations, buffered or
O_DIRECT.  Buffered writes weren't affected by the first problem since
nfs_async_write_error() redirties on -EAGAIN.

Benjamin Coddington (2):
  NFS: Resend unsent mirror requests when an O_DIRECT write hits -EAGAIN
  NFS: Don't warn when a changed layout refuses a split subrequest

 fs/nfs/direct.c         | 28 ++++++++++++++++++++++++++--
 fs/nfs/pagelist.c       | 20 ++++++++++++++++----
 fs/nfs/read.c           |  3 ++-
 fs/nfs/write.c          |  5 +++--
 include/linux/nfs_xdr.h |  5 ++++-
 5 files changed, 51 insertions(+), 10 deletions(-)

-- 
2.53.0


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

* [PATCH 1/2] NFS: Resend unsent mirror requests when an O_DIRECT write hits -EAGAIN
  2026-10-15 20:58 [PATCH 0/2] NFS: layout changes while building mirrored writes Benjamin Coddington
@ 2026-10-15 20:58 ` Benjamin Coddington
  2026-10-15 20:58 ` [PATCH 2/2] NFS: Don't warn when a changed layout refuses a split subrequest Benjamin Coddington
  1 sibling, 0 replies; 3+ messages in thread
From: Benjamin Coddington @ 2026-10-15 20:58 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

nfs_pageio_add_request() adds each page to mirrors 1..n before mirror
0.  When ff_layout_pg_init_write() finds that a recalled layout came
back with a different mirror count, it fails the descriptor with
-EAGAIN, and nfs_pageio_error_cleanup() runs the error_cleanup of every
mirror's queue.  For O_DIRECT that is nfs_write_sync_pgio_error(),
which releases the requests.  Mirrors flush at their own wsize, so the
released requests can be one mirror's share of bytes another mirror
has already written.  nfs_direct_write_schedule_iovec() treats -EAGAIN
as soft and reschedules only the current page and those after it, and
the bytes the other mirror wrote are counted: the write succeeds while
one mirror never receives that part of it.
nfs_direct_write_reschedule() loses requests the same way when the
layout changes during a resend.

Pass the descriptor to error_cleanup, and on -EAGAIN have
nfs_write_sync_pgio_error() mark the queued requests for rescheduling,
as nfs_direct_write_reschedule_io() does for a failed header, instead
of releasing them.  nfs_pageio_resend() passes no descriptor and keeps
its current behaviour.

Fixes: 8b0401373734 ("pNFS/flexfiles: Fix list corruption if the mirror count changes")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/direct.c         | 28 ++++++++++++++++++++++++++--
 fs/nfs/pagelist.c       |  6 +++---
 fs/nfs/read.c           |  3 ++-
 fs/nfs/write.c          |  5 +++--
 include/linux/nfs_xdr.h |  5 ++++-
 5 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c
index ccafdc1ce64d..a5e5fd4b1812 100644
--- a/fs/nfs/direct.c
+++ b/fs/nfs/direct.c
@@ -286,7 +286,8 @@ static void nfs_direct_read_completion(struct nfs_pgio_header *hdr)
 	hdr->release(hdr);
 }
 
-static void nfs_read_sync_pgio_error(struct list_head *head, int error)
+static void nfs_read_sync_pgio_error(struct list_head *head, int error,
+				     struct nfs_pageio_descriptor *desc)
 {
 	struct nfs_page *req;
 
@@ -796,10 +797,33 @@ static void nfs_direct_write_completion(struct nfs_pgio_header *hdr)
 	hdr->release(hdr);
 }
 
-static void nfs_write_sync_pgio_error(struct list_head *head, int error)
+static void nfs_write_sync_pgio_error(struct list_head *head, int error,
+				      struct nfs_pageio_descriptor *desc)
 {
+	struct nfs_direct_req *dreq = desc ? desc->pg_dreq : NULL;
+	struct nfs_commit_info cinfo;
 	struct nfs_page *req;
 
+	/*
+	 * The layout changed under the descriptor: the queued requests were
+	 * never sent, and may be another mirror's share of a write that has
+	 * already completed elsewhere.  Resend them with the rest.
+	 */
+	if (error == -EAGAIN && dreq) {
+		nfs_init_cinfo_from_dreq(&cinfo, dreq);
+		spin_lock(&dreq->lock);
+		if (dreq->error == 0)
+			dreq->flags = NFS_ODIRECT_RESCHED_WRITES;
+		spin_unlock(&dreq->lock);
+		while (!list_empty(head)) {
+			req = nfs_list_entry(head->next);
+			nfs_list_remove_request(req);
+			nfs_unlock_request(req);
+			nfs_mark_request_commit(req, NULL, &cinfo, 0);
+		}
+		return;
+	}
+
 	while (!list_empty(head)) {
 		req = nfs_list_entry(head->next);
 		nfs_list_remove_request(req);
diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c
index 7dd478ffc2fa..06e301c8c291 100644
--- a/fs/nfs/pagelist.c
+++ b/fs/nfs/pagelist.c
@@ -1162,7 +1162,7 @@ nfs_pageio_cleanup_request(struct nfs_pageio_descriptor *desc,
 	LIST_HEAD(head);
 
 	nfs_list_move_request(req, &head);
-	desc->pg_completion_ops->error_cleanup(&head, desc->pg_error);
+	desc->pg_completion_ops->error_cleanup(&head, desc->pg_error, desc);
 }
 
 /**
@@ -1291,7 +1291,7 @@ static void nfs_pageio_error_cleanup(struct nfs_pageio_descriptor *desc)
 	for (midx = 0; midx < desc->pg_mirror_count; midx++) {
 		mirror = nfs_pgio_get_mirror(desc, midx);
 		desc->pg_completion_ops->error_cleanup(&mirror->pg_list,
-				desc->pg_error);
+				desc->pg_error, desc);
 	}
 }
 
@@ -1394,7 +1394,7 @@ int nfs_pageio_resend(struct nfs_pageio_descriptor *desc,
 	nfs_pageio_complete(desc);
 	if (!list_empty(&pages)) {
 		int err = desc->pg_error < 0 ? desc->pg_error : -EIO;
-		hdr->completion_ops->error_cleanup(&pages, err);
+		hdr->completion_ops->error_cleanup(&pages, err, NULL);
 		nfs_set_pgio_error(hdr, err, hdr->io_start);
 		return err;
 	}
diff --git a/fs/nfs/read.c b/fs/nfs/read.c
index 2b70bd2b934b..1e6a88d5ea7c 100644
--- a/fs/nfs/read.c
+++ b/fs/nfs/read.c
@@ -220,7 +220,8 @@ static void nfs_initiate_read(struct nfs_pgio_header *hdr,
 }
 
 static void
-nfs_async_read_error(struct list_head *head, int error)
+nfs_async_read_error(struct list_head *head, int error,
+		     struct nfs_pageio_descriptor *desc)
 {
 	struct nfs_page	*req;
 
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index 623e7ef1f73d..50f381b19fab 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -1375,7 +1375,8 @@ static void nfs_redirty_request(struct nfs_page *req)
 	nfs_release_request(req);
 }
 
-static void nfs_async_write_error(struct list_head *head, int error)
+static void nfs_async_write_error(struct list_head *head, int error,
+				  struct nfs_pageio_descriptor *desc)
 {
 	struct nfs_page	*req;
 
@@ -1391,7 +1392,7 @@ static void nfs_async_write_error(struct list_head *head, int error)
 
 static void nfs_async_write_reschedule_io(struct nfs_pgio_header *hdr)
 {
-	nfs_async_write_error(&hdr->pages, 0);
+	nfs_async_write_error(&hdr->pages, 0, NULL);
 }
 
 static const struct nfs_pgio_completion_ops nfs_async_write_completion_ops = {
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 7ed8fdb930d6..858d2bba8003 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -1739,8 +1739,11 @@ struct nfs_commit_data {
 	unsigned long		flags;
 };
 
+struct nfs_pageio_descriptor;
+
 struct nfs_pgio_completion_ops {
-	void	(*error_cleanup)(struct list_head *head, int);
+	void	(*error_cleanup)(struct list_head *head, int error,
+				 struct nfs_pageio_descriptor *desc);
 	void	(*init_hdr)(struct nfs_pgio_header *hdr);
 	void	(*completion)(struct nfs_pgio_header *hdr);
 	void	(*reschedule_io)(struct nfs_pgio_header *hdr);
-- 
2.53.0


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

* [PATCH 2/2] NFS: Don't warn when a changed layout refuses a split subrequest
  2026-10-15 20:58 [PATCH 0/2] NFS: layout changes while building mirrored writes Benjamin Coddington
  2026-10-15 20:58 ` [PATCH 1/2] NFS: Resend unsent mirror requests when an O_DIRECT write hits -EAGAIN Benjamin Coddington
@ 2026-10-15 20:58 ` Benjamin Coddington
  1 sibling, 0 replies; 3+ messages in thread
From: Benjamin Coddington @ 2026-10-15 20:58 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs

From: Benjamin Coddington <bcodding@hammerspace.com>

When __nfs_pageio_add_request() splits a request, it sizes the
subrequest with pg_test and then adds it.  The current mirror's list
is still empty at that point, because sizing added nothing, so
nfs_pageio_do_add_request() runs pg_init a second time.  For pNFS,
pg_init re-checks the layout segment.  A layout recalled between the
two calls can make the second pg_init fail the descriptor, for
example with -EAGAIN from ff_layout_pg_init_write() when the mirror
count changed, or leave a smaller pg_bsize, and the subrequest is
then not accepted in full.

That trips WARN_ON_ONCE(subreq != req), which was meant to catch
pg_test changing its answer for an unchanged descriptor.  The cleanup
in that branch already handles a refused subrequest correctly.

Warn only when the descriptor has no error and still has the layout
segment and bsize the subrequest was sized against.

Fixes: 44a65a0c2783 ("NFS: Reverse the submission order of requests in __nfs_pageio_add_request()")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 fs/nfs/pagelist.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c
index 06e301c8c291..cbcf19d5f61e 100644
--- a/fs/nfs/pagelist.c
+++ b/fs/nfs/pagelist.c
@@ -1181,8 +1181,10 @@ static int __nfs_pageio_add_request(struct nfs_pageio_descriptor *desc,
 			   struct nfs_page *req)
 {
 	struct nfs_pgio_mirror *mirror = nfs_pgio_current_mirror(desc);
+	struct pnfs_layout_segment *lseg = NULL;
 	struct nfs_page *subreq;
 	unsigned int size, subreq_size;
+	size_t bsize = 0;
 
 	nfs_page_group_lock(req);
 
@@ -1204,7 +1206,15 @@ static int __nfs_pageio_add_request(struct nfs_pageio_descriptor *desc,
 			subreq = req;
 			continue;
 		}
-		if (WARN_ON_ONCE(subreq != req)) {
+		if (subreq != req) {
+			/*
+			 * An empty mirror runs pg_init again for the subrequest,
+			 * and a layout that changed since it was sized can refuse
+			 * it.  Anything else means pg_test changed its answer.
+			 */
+			WARN_ON_ONCE(desc->pg_error >= 0 &&
+				     desc->pg_lseg == lseg &&
+				     mirror->pg_bsize == bsize);
 			nfs_page_group_unlock(req);
 			nfs_pageio_cleanup_request(desc, subreq);
 			subreq = req;
@@ -1227,6 +1237,8 @@ static int __nfs_pageio_add_request(struct nfs_pageio_descriptor *desc,
 		if (IS_ERR(subreq))
 			goto err_ptr;
 		subreq_size = size;
+		lseg = desc->pg_lseg;
+		bsize = mirror->pg_bsize;
 	}
 
 	nfs_page_group_unlock(req);
-- 
2.53.0


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

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

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 20:58 [PATCH 0/2] NFS: layout changes while building mirrored writes Benjamin Coddington
2026-10-15 20:58 ` [PATCH 1/2] NFS: Resend unsent mirror requests when an O_DIRECT write hits -EAGAIN Benjamin Coddington
2026-10-15 20:58 ` [PATCH 2/2] NFS: Don't warn when a changed layout refuses a split subrequest Benjamin Coddington

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