]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nfs: keep PG_UPTODATE clear after read errors in page groups
authorClark Wang <xiaoning.wang@nxp.com>
Tue, 2 Jun 2026 11:04:38 +0000 (19:04 +0800)
committerAnna Schumaker <anna.schumaker@hammerspace.com>
Mon, 8 Jun 2026 16:06:41 +0000 (12:06 -0400)
When a read request is split into multiple subrequests, earlier
completions may advance PG_UPTODATE state for the page group once
their bytes fall within hdr->good_bytes. If a later subrequest in
the same group then completes with NFS_IOHDR_ERROR, the read path
needs to clear any accumulated PG_UPTODATE state and keep later
completions from rebuilding it.

Otherwise, a subsequent successful subrequest can re-enter
nfs_page_group_set_uptodate(), restore the page-group sync state,
and leave stale PG_UPTODATE behind for nfs_page_group_destroy()
to trip over in nfs_free_request().

Add a sticky page-group read-failed flag. Once any subrequest in
the group is known to be bad, mark the group failed, clear any
accumulated PG_UPTODATE state, and refuse further PG_UPTODATE
synchronization for the rest of the completion walk.

Fixes: 67d0338edd71 ("nfs: page group syncing in read path")
Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
fs/nfs/read.c
include/linux/nfs_page.h

index e1fe78d7b8d0ff1227d7568df9aa013c9ef616be..2b70bd2b934b9a746e73b1701fafacae97af3f41 100644 (file)
@@ -132,10 +132,32 @@ static void nfs_readpage_release(struct nfs_page *req, int error)
 
 static void nfs_page_group_set_uptodate(struct nfs_page *req)
 {
-       if (nfs_page_group_sync_on_bit(req, PG_UPTODATE))
+       bool uptodate = false;
+
+       nfs_page_group_lock(req);
+       if (!test_bit(PG_READ_FAILED, &req->wb_head->wb_flags) &&
+           nfs_page_group_sync_on_bit_locked(req, PG_UPTODATE))
+               uptodate = true;
+       nfs_page_group_unlock(req);
+
+       if (uptodate)
                folio_mark_uptodate(nfs_page_to_folio(req));
 }
 
+static void nfs_page_group_mark_read_failed(struct nfs_page *req)
+{
+       struct nfs_page *tmp;
+
+       nfs_page_group_lock(req);
+       set_bit(PG_READ_FAILED, &req->wb_head->wb_flags);
+       tmp = req;
+       do {
+               clear_bit(PG_UPTODATE, &tmp->wb_flags);
+               tmp = tmp->wb_this_page;
+       } while (tmp != req);
+       nfs_page_group_unlock(req);
+}
+
 static void nfs_read_completion(struct nfs_pgio_header *hdr)
 {
        unsigned long bytes = 0;
@@ -172,6 +194,7 @@ static void nfs_read_completion(struct nfs_pgio_header *hdr)
                        if (bytes <= hdr->good_bytes)
                                nfs_page_group_set_uptodate(req);
                        else {
+                               nfs_page_group_mark_read_failed(req);
                                error = hdr->error;
                                xchg(&nfs_req_openctx(req)->error, error);
                        }
index afe1d8f09d89f8d2be426227791243916f3290fe..4b9a35dbc062da25f0c549aa78981c21d53ca1c0 100644 (file)
@@ -33,6 +33,7 @@ enum {
        PG_TEARDOWN,            /* page group sync for destroy */
        PG_UNLOCKPAGE,          /* page group sync bit in read path */
        PG_UPTODATE,            /* page group sync bit in read path */
+       PG_READ_FAILED,         /* page group saw a read error */
        PG_WB_END,              /* page group sync bit in write path */
        PG_REMOVE,              /* page group sync bit in write path */
        PG_CONTENDED1,          /* Is someone waiting for a lock? */