[PATCH RFC 2/2] fs: defer dirtytime iput from PF_MEMALLOC context

Mikhail Rudenko <[email protected]>
Newsgroups org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <20260813-deferred-dirtytime-iput-v1-2-ad2fb3ad6bdb@yandex-team.ru>
Both kswapd and direct reclaim run shrinkers with PF_MEMALLOC set.
Dcache reclaim can remove a dentry and drop the final reference to a
linked inode with I_DIRTY_TIME. iput() then calls sync_lazytime(), which
promotes the update to I_DIRTY_SYNC and invokes the filesystem
dirty_inode callback. ext4 can allocate an inode-table buffer with
__GFP_NOFAIL from there, triggering the PF_MEMALLOC allocator warning.

Use the superblock deferred-iput infrastructure to transfer that final
inode reference to an unbound worker instead. Remove the inode from the
inode LRU first so i_lru can serve as the allocation-free queue entry.

This preserves normal dentry eviction policy and only defers work where
iput() would otherwise promote lazytime state from PF_MEMALLOC context.

Signed-off-by: Mikhail Rudenko <[email protected]>
---
 fs/inode.c | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/fs/inode.c b/fs/inode.c
index 31c5b9ee3a81..e71750bf7c82 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -18,6 +18,7 @@
 #include <linux/mount.h>
 #include <linux/posix_acl.h>
 #include <linux/ratelimit.h>
+#include <linux/sched.h>
 #include <linux/list_lru.h>
 #include <linux/iversion.h>
 #include <linux/rw_hint.h>
@@ -35,7 +36,7 @@
  * inode->i_lock protects:
  *   inode->i_state, inode->i_hash, __iget(), inode->i_io_list
  * Inode LRU list locks protect:
- *   inode->i_sb->s_inode_lru, inode->i_lru
+ *   inode->i_sb->s_inode_lru, inode->i_lru when on the inode LRU
  * inode->i_sb->s_inode_list_lock protects:
  *   inode->i_sb->s_inodes, inode->i_sb_list
  * bdi->wb.list_lock protects:
@@ -2019,6 +2020,43 @@ static void iput_final(struct inode *inode)
 	evict(inode);
 }
 
+/* Like iput(), but defer sync_lazytime(). Used in reclaim paths. */
+static void iput_memalloc(struct inode *inode)
+{
+	spin_lock(&inode->i_lock);
+	if (unlikely((inode_state_read(inode) & I_DIRTY_TIME) &&
+		     inode->i_nlink)) {
+		int ret;
+
+		if (atomic_add_unless(&inode->i_count, -1, 1)) {
+			spin_unlock(&inode->i_lock);
+			return;
+		}
+
+		inode_lru_list_del(inode);
+		spin_unlock(&inode->i_lock);
+
+		ret = super_defer_iput(inode);
+		if (!ret)
+			return;
+
+		/*
+		 * The superblock is no longer accepting deferred iputs.
+		 * This shouldn't happen.
+		 */
+		WARN_ON_ONCE(ret == -ESHUTDOWN);
+		spin_lock(&inode->i_lock);
+		inode_state_clear(inode, I_DIRTY_TIME);
+	}
+
+	if (!atomic_dec_and_test(&inode->i_count)) {
+		spin_unlock(&inode->i_lock);
+		return;
+	}
+
+	iput_final(inode);
+}
+
 /**
  *	iput	- put an inode
  *	@inode: inode to put
@@ -2047,6 +2085,11 @@ void iput(struct inode *inode)
 	if (atomic_add_unless(&inode->i_count, -1, 1))
 		return;
 
+	if (unlikely(current->flags & PF_MEMALLOC)) {
+		iput_memalloc(inode);
+		return;
+	}
+
 	if (inode->i_nlink && sync_lazytime(inode))
 		goto retry;
 

-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.