Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

NFS: Fix a deadlock involving nfs_release_folio()

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>

+39 -1
+2 -1
fs/nfs/file.c
··· 511 511 if ((current_gfp_context(gfp) & GFP_KERNEL) != GFP_KERNEL || 512 512 current_is_kswapd() || current_is_kcompactd()) 513 513 return false; 514 - if (nfs_wb_folio(folio->mapping->host, folio) < 0) 514 + if (nfs_wb_folio_reclaim(folio->mapping->host, folio) < 0 || 515 + folio_test_private(folio)) 515 516 return false; 516 517 } 517 518 return nfs_fscache_release_folio(folio, gfp);
+3
fs/nfs/nfstrace.h
··· 1062 1062 DEFINE_NFS_FOLIO_EVENT(nfs_aop_readpage); 1063 1063 DEFINE_NFS_FOLIO_EVENT_DONE(nfs_aop_readpage_done); 1064 1064 1065 + DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio_reclaim); 1066 + DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_reclaim_done); 1067 + 1065 1068 DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio); 1066 1069 DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_done); 1067 1070
+33
fs/nfs/write.c
··· 2025 2025 } 2026 2026 2027 2027 /** 2028 + * nfs_wb_folio_reclaim - Write back all requests on one page 2029 + * @inode: pointer to page 2030 + * @folio: pointer to folio 2031 + * 2032 + * Assumes that the folio has been locked by the caller 2033 + */ 2034 + int nfs_wb_folio_reclaim(struct inode *inode, struct folio *folio) 2035 + { 2036 + loff_t range_start = folio_pos(folio); 2037 + size_t len = folio_size(folio); 2038 + struct writeback_control wbc = { 2039 + .sync_mode = WB_SYNC_ALL, 2040 + .nr_to_write = 0, 2041 + .range_start = range_start, 2042 + .range_end = range_start + len - 1, 2043 + .for_sync = 1, 2044 + }; 2045 + int ret; 2046 + 2047 + if (folio_test_writeback(folio)) 2048 + return -EBUSY; 2049 + if (folio_clear_dirty_for_io(folio)) { 2050 + trace_nfs_writeback_folio_reclaim(inode, range_start, len); 2051 + ret = nfs_writepage_locked(folio, &wbc); 2052 + trace_nfs_writeback_folio_reclaim_done(inode, range_start, len, 2053 + ret); 2054 + return ret; 2055 + } 2056 + nfs_commit_inode(inode, 0); 2057 + return 0; 2058 + } 2059 + 2060 + /** 2028 2061 * nfs_wb_folio - Write back all requests on one page 2029 2062 * @inode: pointer to page 2030 2063 * @folio: pointer to folio
+1
include/linux/nfs_fs.h
··· 637 637 extern int nfs_sync_inode(struct inode *inode); 638 638 extern int nfs_wb_all(struct inode *inode); 639 639 extern int nfs_wb_folio(struct inode *inode, struct folio *folio); 640 + extern int nfs_wb_folio_reclaim(struct inode *inode, struct folio *folio); 640 641 int nfs_wb_folio_cancel(struct inode *inode, struct folio *folio); 641 642 extern int nfs_commit_inode(struct inode *, int); 642 643 extern struct nfs_commit_data *nfs_commitdata_alloc(void);