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

From: Mikhail Rudenko

Date: Thu Aug 13 2026 - 06:02:20 EST


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 <xyzzy@xxxxxxxxxxxxxx>
---
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