]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
NFS: Fix a deadlock involving nfs_release_folio()
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Wed, 31 Dec 2025 16:42:31 +0000 (11:42 -0500)
committerTrond Myklebust <trond.myklebust@hammerspace.com>
Mon, 5 Jan 2026 04:03:24 +0000 (23:03 -0500)
Wang Zhaolong reports a deadlock involving NFSv4.1 state recovery
waiting on kthreadd, which is attempting to reclaim memory by calling
nfs_release_folio(). The latter cannot make progress due to state
recovery being needed.

It seems that the only safe thing to do here is to kick off a writeback
of the folio, without waiting for completion, or else kicking off an
asynchronous commit.

Reported-by: Wang Zhaolong <wangzhaolong@huaweicloud.com>
Fixes: 96780ca55e3c ("NFS: fix up nfs_release_folio() to try to release the page")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
fs/nfs/file.c
fs/nfs/nfstrace.h
fs/nfs/write.c
include/linux/nfs_fs.h

index d020aab40c64ebda30d130b6acee1b9194621457..d1c138a416cfb565f12eff2f735890a1ba3577aa 100644 (file)
@@ -511,7 +511,8 @@ static bool nfs_release_folio(struct folio *folio, gfp_t gfp)
                if ((current_gfp_context(gfp) & GFP_KERNEL) != GFP_KERNEL ||
                    current_is_kswapd() || current_is_kcompactd())
                        return false;
-               if (nfs_wb_folio(folio->mapping->host, folio) < 0)
+               if (nfs_wb_folio_reclaim(folio->mapping->host, folio) < 0 ||
+                   folio_test_private(folio))
                        return false;
        }
        return nfs_fscache_release_folio(folio, gfp);
index 6ce55e8e6b67c0cc31f30a6b7d334133178fb818..9f9ce4a565ea68bf2964690ffbfba12a93f4950d 100644 (file)
@@ -1062,6 +1062,9 @@ DECLARE_EVENT_CLASS(nfs_folio_event_done,
 DEFINE_NFS_FOLIO_EVENT(nfs_aop_readpage);
 DEFINE_NFS_FOLIO_EVENT_DONE(nfs_aop_readpage_done);
 
+DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio_reclaim);
+DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_reclaim_done);
+
 DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio);
 DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_done);
 
index 336c510f3750202dc71fdc6ab0448559f4ea6bf3..bf412455e8edff3823983fe1273330bee492fbe1 100644 (file)
@@ -2024,6 +2024,39 @@ int nfs_wb_folio_cancel(struct inode *inode, struct folio *folio)
        return ret;
 }
 
+/**
+ * nfs_wb_folio_reclaim - Write back all requests on one page
+ * @inode: pointer to page
+ * @folio: pointer to folio
+ *
+ * Assumes that the folio has been locked by the caller
+ */
+int nfs_wb_folio_reclaim(struct inode *inode, struct folio *folio)
+{
+       loff_t range_start = folio_pos(folio);
+       size_t len = folio_size(folio);
+       struct writeback_control wbc = {
+               .sync_mode = WB_SYNC_ALL,
+               .nr_to_write = 0,
+               .range_start = range_start,
+               .range_end = range_start + len - 1,
+               .for_sync = 1,
+       };
+       int ret;
+
+       if (folio_test_writeback(folio))
+               return -EBUSY;
+       if (folio_clear_dirty_for_io(folio)) {
+               trace_nfs_writeback_folio_reclaim(inode, range_start, len);
+               ret = nfs_writepage_locked(folio, &wbc);
+               trace_nfs_writeback_folio_reclaim_done(inode, range_start, len,
+                                                      ret);
+               return ret;
+       }
+       nfs_commit_inode(inode, 0);
+       return 0;
+}
+
 /**
  * nfs_wb_folio - Write back all requests on one page
  * @inode: pointer to page
index a6624edb7226a16b63a6c3fd592b04d3ff34f58c..8dd79a3f3d662bfe8ef8f72bb88ad1e2cd40527e 100644 (file)
@@ -637,6 +637,7 @@ extern int  nfs_update_folio(struct file *file, struct folio *folio,
 extern int nfs_sync_inode(struct inode *inode);
 extern int nfs_wb_all(struct inode *inode);
 extern int nfs_wb_folio(struct inode *inode, struct folio *folio);
+extern int nfs_wb_folio_reclaim(struct inode *inode, struct folio *folio);
 int nfs_wb_folio_cancel(struct inode *inode, struct folio *folio);
 extern int  nfs_commit_inode(struct inode *, int);
 extern struct nfs_commit_data *nfs_commitdata_alloc(void);