]> git.ipfire.org Git - thirdparty/kernel/stable.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)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 23 Jan 2026 10:18:36 +0000 (11:18 +0100)
[ Upstream commit cce0be6eb4971456b703aaeafd571650d314bcca ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/nfs/file.c
fs/nfs/nfstrace.h
fs/nfs/write.c
include/linux/nfs_fs.h

index a16a619fb8c33bb1e009eec4c1a1ac6443dd014c..7d1840cea4444e7a08ba02f625e4d3d389e2a594 100644 (file)
@@ -461,7 +461,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 1eab98c277fab389f5dbdb02683c28fc97655f38..2989b6f284ff47f4f794c0ae9d673637f8f725a3 100644 (file)
@@ -1039,6 +1039,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 88d0e5168093ad68a5ada801f04af4c297923b08..48a8866220d1a3188781dce9cfa77431d56f19cb 100644 (file)
@@ -2065,6 +2065,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 039898d70954f9a0ef29d4f4d3ae75bdbd6ce2a1..8d2cf10294a428b60d4ec9641d4d6126cb540bbf 100644 (file)
@@ -610,6 +610,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);