Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context
@ 2026-08-13  9:43 Mikhail Rudenko
  2026-08-13  9:43 ` [PATCH RFC 1/2] fs: add superblock deferred iput infrastructure Mikhail Rudenko
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Mikhail Rudenko @ 2026-08-13  9:43 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Jan Kara
  Cc: linux-fsdevel, linux-kernel, Mikhail Rudenko

The motivation for this small series is the following kernel WARNING:

[  893.915584] ------------[ cut here ]------------
[  893.915589] WARNING: mm/page_alloc.c:4749 at __alloc_pages_slowpath.constprop.0+0xd4d/0x10f0, CPU#1: stress-ng-vm/3784
[  893.915599] Modules linked in: kheaders squashfs intel_rapl_msr intel_rapl_common kvm_intel kvm i2c_i801 ppdev cfg80211 i2c_mux irqbypass i2c_smbus lpc_ich parport_pc ghash_clmulni_intel mfd_core parport rfkill input_leds tpm_tis tpm_tis_core binfmt_misc sch_fq_codel dm_multipath drm msr fuse efi_pstore configfs nfnetlink dmi_sysfs qemu_fw_cfg autofs4
[  893.915629] CPU: 1 UID: 0 PID: 3784 Comm: stress-ng-vm Not tainted 7.0.11 #87 PREEMPT(lazy)
[  893.915633] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS Arch Linux 1.17.0-2-2 04/01/2014
[  893.915635] RIP: 0010:__alloc_pages_slowpath.constprop.0+0xd4d/0x10f0
[  893.915639] Code: 48 c7 44 24 18 00 00 00 00 e9 da fd ff ff 0f 0b c7 44 24 38 00 00 00 00 65 48 8b 05 85 a2 5f 02 f6 40 2d 08 0f 84 10 f3 ff ff <0f> 0b e9 09 f3 ff ff f3 90 8b 05 74 6f 6b 02 a8 01 75 f4 89 44 24
[  893.915642] RSP: 0000:ffffc900058cb3d0 EFLAGS: 00010202
[  893.915645] RAX: ffff8881fb95a500 RBX: 0000000000000000 RCX: 0000000000000002
[  893.915647] RDX: ffffc900058cb4c0 RSI: 0000000000000000 RDI: 0000000000008000
[  893.915648] RBP: ffffc900058cb4c0 R08: 0000000000000801 R09: 000000000000304f
[  893.915649] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000148c48
[  893.915650] R13: 0000000000000000 R14: 0000000000148c48 R15: 0000000000000000
[  893.915652] FS:  00007fce6009eac0(0000) GS:ffff8881f8136000(0000) knlGS:0000000000000000
[  893.915653] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  893.915655] CR2: 00007fce52f50000 CR3: 00000001fd821004 CR4: 0000000000f72ef0
[  893.915660] PKRU: 55555554
[  893.915662] Call Trace:
[  893.915664]  <TASK>
[  893.915668]  ? get_page_from_freelist+0x6a5/0x1090
[  893.915673]  ? mod_memcg_lruvec_state+0xab/0x1e0
[  893.915678]  __alloc_frozen_pages_noprof+0x377/0x390
[  893.915683]  alloc_pages_mpol+0x7b/0x160
[  893.915687]  folio_alloc_noprof+0x14/0x90
[  893.915691]  __filemap_get_folio_mpol+0x219/0x440
[  893.915696]  __getblk_slow+0x88/0x270
[  893.915701]  __ext4_get_inode_loc+0x148/0x570
[  893.915707]  ? __ext4_handle_dirty_metadata+0x70/0x1f0
[  893.915711]  ? ext4_get_inode_loc+0x41/0xa0
[  893.915714]  ext4_get_inode_loc+0x41/0xa0
[  893.915717]  ext4_reserve_inode_write+0x4d/0x100
[  893.915720]  __ext4_mark_inode_dirty+0x6b/0x240
[  893.915723]  ? jbd2__journal_start+0xfe/0x210
[  893.915729]  ext4_dirty_inode+0x5c/0x80
[  893.915732]  __mark_inode_dirty+0x63/0x3f0
[  893.915737]  sync_lazytime+0xe7/0x100
[  893.915741]  iput+0x6a/0x220
[  893.915745]  __dentry_kill+0x74/0x190
[  893.915749]  shrink_dentry_list+0xa3/0x160
[  893.915753]  prune_dcache_sb+0x57/0x80
[  893.915756]  super_cache_scan+0x120/0x1c0
[  893.915762]  do_shrink_slab+0x14a/0x380
[  893.915766]  shrink_slab+0x2c5/0x3e0
[  893.915769]  shrink_node+0x2df/0x830
[  893.915774]  do_try_to_free_pages+0xf0/0x560
[  893.915779]  try_to_free_pages+0xd8/0x210
[  893.915784]  __alloc_pages_slowpath.constprop.0+0x2f8/0x10f0
[  893.915788]  ? mod_memcg_state+0x9c/0x1d0
[  893.915792]  __alloc_frozen_pages_noprof+0x377/0x390
[  893.915796]  alloc_pages_mpol+0x7b/0x160
[  893.915798]  vma_alloc_folio_noprof+0x65/0x130
[  893.915802]  do_anonymous_page+0x260/0x820
[  893.915806]  ? __pte_offset_map+0x1b/0xd0
[  893.915810]  __handle_mm_fault+0xaf8/0xf70
[  893.915815]  handle_mm_fault+0x101/0x330
[  893.915818]  do_user_addr_fault+0x21b/0x6c0
[  893.915824]  exc_page_fault+0x80/0x1c0
[  893.915829]  asm_exc_page_fault+0x26/0x30
[  893.915833] RIP: 0033:0x5601e3ae5af5
[  893.915837] Code: ec 08 48 8b 15 b4 89 92 00 4c 8b 59 10 48 01 fa 4c 39 ca 0f 83 c4 00 00 00 31 c9 66 2e 0f 1f 84 00 00 00 00 00 89 cf 83 c1 01 <40> 88 3a 48 8b 3d 81 89 92 00 48 01 fa 4c 39 ca 72 e9 31 d2 48 89
[  893.915839] RSP: 002b:00007fff792403d0 EFLAGS: 00010202
[  893.915842] RAX: 0000000000000010 RBX: 00007fce5fc8b398 RCX: 00000000075bd401
[  893.915843] RDX: 00007fce52f50000 RSI: 00000001e1400000 RDI: 00000000075bd400
[  893.915845] RBP: 00007fff792403f0 R08: 0000000000000000 R09: 00007fce5d400000
[  893.915847] R10: 00007fcc7c000000 R11: 0000000000000000 R12: 00007fce5fc8b398
[  893.915848] R13: 00000001e1400000 R14: 0000000000000000 R15: 00007fff792405d0
[  893.915851]  </TASK>
[  893.915852] ---[ end trace 0000000000000000 ]---

It was initially observed in the wild on linux 6.12.y, but later I
reproduced it in Qemu with linux 7.0.11. I believe it is still an
issue in the mainline master. Similar issue was reported earlier [1],
but looks like no real analysis was performed then.

The warning is triggered when a process with PF_MEMALLOC flag set
requests a __GFP_NOFAIL page allocation. In this case, the allocation
comes from ext4_dirty_inode() called when shrinker frees a dentry,
resulting in i_count of an inode going zero, which in it turn can
trigger sync_lazytime() if said inode has I_DIRTY_TIME flag, resulting
in inode becoming dirty. On ext4 this causes an immediate journal
transaction, which may lead to __GFP_NOFAIL allocation in the
slowpath.

The proposed approach to fix this basically boils down to "if need to
do sync_lazytime() in PF_MEMALLOC context, defer instead".

Patch 1 adds basic per-superblock infrastracture for deferring iputs:
a workqueue, a work_struct, a list, a bool flag, a spinlock, and some
glue code. A tricky place is superblock shutdown: we should stop
accepting deferred iputs not too early, so that no legit cases fail to
defer, and not too late, so that superblock is still operational and
we can flush the workqueue. Since we cannot enter
deactivate_locked_super() from PF_MEMALLOC context, stopping after
shrinker_free() seems safe.

Patch 2 uses this infrastructure to defer iputs of inodes with
I_DIRTY_TIME, which would trigger sync_lazytime() otherwise, when
running in PF_MEMALLOC context. Clumping default and PF_MEMALLOC paths
together in iput() would have resulted in decreased readablity, so I
avoided that by setting up a separate iput_memalloc(), at cost of
slight code duplication.

[1] https://lkml.iu.edu/2601.2/06130.html

---
Mikhail Rudenko (2):
      fs: add superblock deferred iput infrastructure
      fs: defer dirtytime iput from PF_MEMALLOC context

 fs/dcache.c                    |  1 +
 fs/inode.c                     | 45 ++++++++++++++++++++-
 fs/internal.h                  |  3 ++
 fs/super.c                     | 92 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/fs/super_types.h |  6 +++
 5 files changed, 146 insertions(+), 1 deletion(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260812-deferred-dirtytime-iput-ae7f2f0bb605

Best regards,
--  
Mikhail Rudenko <xyzzy@yandex-team.ru>


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH RFC 1/2] fs: add superblock deferred iput infrastructure
  2026-08-13  9:43 [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context Mikhail Rudenko
@ 2026-08-13  9:43 ` Mikhail Rudenko
  2026-08-13  9:43 ` [PATCH RFC 2/2] fs: defer dirtytime iput from PF_MEMALLOC context Mikhail Rudenko
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Mikhail Rudenko @ 2026-08-13  9:43 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Jan Kara
  Cc: linux-fsdevel, linux-kernel, Mikhail Rudenko

Add the superblock side of deferred final iput processing. The new
helper queues an inode on a per-superblock list and schedules an
unbound WQ_MEM_RECLAIM worker to retry iput() outside the caller's
context.

Superblock teardown shuts the queue down and flushes pending work after
the shrinker is unregistered and before ->kill_sb(). This processes
queued inodes while the superblock and filesystem are still alive.

This is preparation for deferring dirtytime final iput from PF_MEMALLOC
context.

Signed-off-by: Mikhail Rudenko <xyzzy@yandex-team.ru>
---
 fs/dcache.c                    |  1 +
 fs/internal.h                  |  3 ++
 fs/super.c                     | 92 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/fs/super_types.h |  6 +++
 4 files changed, 102 insertions(+)

diff --git a/fs/dcache.c b/fs/dcache.c
index 3e9af9de7074..5465d8dbfdd2 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3511,6 +3511,7 @@ void __init vfs_caches_init(void)
 {
 	filename_init();
 	dcache_init();
+	super_init();
 	inode_init();
 	files_init();
 	files_maxfiles_init();
diff --git a/fs/internal.h b/fs/internal.h
index 355d93f92208..641b9a0c612d 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -9,6 +9,7 @@ struct super_block;
 struct file_system_type;
 struct iomap;
 struct iomap_ops;
+struct inode;
 struct linux_binprm;
 struct path;
 struct mount;
@@ -138,6 +139,8 @@ extern bool super_trylock_shared(struct super_block *sb);
 struct super_block *user_get_super(dev_t, bool excl);
 void put_super(struct super_block *sb);
 extern bool mount_capable(struct fs_context *);
+void __init super_init(void);
+int super_defer_iput(struct inode *inode);
 
 /*
  * Prepare superblock for changing its read-only state (i.e., either remount
diff --git a/fs/super.c b/fs/super.c
index a8fd61136aaf..2f318694c98b 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -37,6 +37,7 @@
 #include <linux/user_namespace.h>
 #include <linux/fs_context.h>
 #include <linux/fserror.h>
+#include <linux/workqueue.h>
 #include <uapi/linux/mount.h>
 #include "internal.h"
 
@@ -45,6 +46,7 @@ static int thaw_super_locked(struct super_block *sb, enum freeze_holder who,
 
 static LIST_HEAD(super_blocks);
 static DEFINE_SPINLOCK(sb_lock);
+static struct workqueue_struct *super_deferred_iput_wq __ro_after_init;
 
 static char *sb_writers_name[SB_FREEZE_LEVELS] = {
 	"sb_writers",
@@ -52,6 +54,89 @@ static char *sb_writers_name[SB_FREEZE_LEVELS] = {
 	"sb_internal",
 };
 
+/*
+ * sb->s_deferred_iput_lock protects sb->s_deferred_iputs and inode->i_lru
+ * while the inode is queued there.
+ */
+static void super_deferred_iput_work(struct work_struct *work)
+{
+	struct super_block *sb = container_of(work, struct super_block,
+					      s_deferred_iput_work);
+	LIST_HEAD(pending);
+
+	for (;;) {
+		spin_lock(&sb->s_deferred_iput_lock);
+		list_splice_init(&sb->s_deferred_iputs, &pending);
+		spin_unlock(&sb->s_deferred_iput_lock);
+
+		if (list_empty(&pending))
+			break;
+
+		while (!list_empty(&pending)) {
+			struct inode *inode;
+
+			inode = list_first_entry(&pending, struct inode, i_lru);
+			list_del_init(&inode->i_lru);
+			iput(inode);
+			cond_resched();
+		}
+	}
+}
+
+static void __init super_deferred_iput_wq_init(void)
+{
+	super_deferred_iput_wq = alloc_workqueue("super_deferred_iput",
+						 WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
+	if (!super_deferred_iput_wq)
+		panic("Failed to allocate super deferred iput workqueue\n");
+}
+
+void __init super_init(void)
+{
+	super_deferred_iput_wq_init();
+}
+
+static void super_deferred_iput_init(struct super_block *sb)
+{
+	spin_lock_init(&sb->s_deferred_iput_lock);
+	INIT_LIST_HEAD(&sb->s_deferred_iputs);
+	INIT_WORK(&sb->s_deferred_iput_work, super_deferred_iput_work);
+	sb->s_deferred_iput_shutdown = false;
+}
+
+static void super_deferred_iput_shutdown(struct super_block *sb)
+{
+	bool empty;
+
+	spin_lock(&sb->s_deferred_iput_lock);
+	sb->s_deferred_iput_shutdown = true;
+	spin_unlock(&sb->s_deferred_iput_lock);
+
+	flush_work(&sb->s_deferred_iput_work);
+
+	spin_lock(&sb->s_deferred_iput_lock);
+	empty = list_empty(&sb->s_deferred_iputs);
+	spin_unlock(&sb->s_deferred_iput_lock);
+	WARN_ON_ONCE(!empty);
+}
+
+int super_defer_iput(struct inode *inode)
+{
+	struct super_block *sb = inode->i_sb;
+	int ret = -ESHUTDOWN;
+
+	spin_lock(&sb->s_deferred_iput_lock);
+	if (!sb->s_deferred_iput_shutdown) {
+		list_add_tail(&inode->i_lru, &sb->s_deferred_iputs);
+		/* Queue under the lock so shutdown cannot miss this work. */
+		queue_work(super_deferred_iput_wq, &sb->s_deferred_iput_work);
+		ret = 0;
+	}
+	spin_unlock(&sb->s_deferred_iput_lock);
+
+	return ret;
+}
+
 static inline void __super_lock(struct super_block *sb, bool excl)
 {
 	if (excl)
@@ -363,6 +448,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
 	mutex_init(&s->s_sync_lock);
 	INIT_LIST_HEAD(&s->s_inodes);
 	spin_lock_init(&s->s_inode_list_lock);
+	super_deferred_iput_init(s);
 	INIT_LIST_HEAD(&s->s_inodes_wb);
 	spin_lock_init(&s->s_inode_wblist_lock);
 	fserror_mount(s);
@@ -474,6 +560,12 @@ void deactivate_locked_super(struct super_block *s)
 	struct file_system_type *fs = s->s_type;
 	if (atomic_dec_and_test(&s->s_active)) {
 		shrinker_free(s->s_shrink);
+		/*
+		 * The shrinker can leave final inode references queued for
+		 * processing outside reclaim. Drain them before
+		 * filesystem-specific shutdown starts.
+		 */
+		super_deferred_iput_shutdown(s);
 		fs->kill_sb(s);
 
 		kill_super_notify(s);
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ef7941e9dc79..9bd4abbdbd51 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -255,6 +255,12 @@ struct super_block {
 	 */
 	struct list_lru				s_dentry_lru;
 	struct list_lru				s_inode_lru;
+	/* Protects s_deferred_iputs and s_deferred_iput_shutdown. */
+	spinlock_t				s_deferred_iput_lock;
+	/* Inodes whose final iput was deferred from PF_MEMALLOC context. */
+	struct list_head			s_deferred_iputs;
+	struct work_struct			s_deferred_iput_work;
+	bool					s_deferred_iput_shutdown;
 	struct rcu_head				rcu;
 	struct work_struct			destroy_work;
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH RFC 2/2] fs: defer dirtytime iput from PF_MEMALLOC context
  2026-08-13  9:43 [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context Mikhail Rudenko
  2026-08-13  9:43 ` [PATCH RFC 1/2] fs: add superblock deferred iput infrastructure Mikhail Rudenko
@ 2026-08-13  9:43 ` Mikhail Rudenko
  2026-09-01 10:10 ` [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in " Mikhail Rudenko
  2026-09-01 12:24 ` Jan Kara
  3 siblings, 0 replies; 6+ messages in thread
From: Mikhail Rudenko @ 2026-08-13  9:43 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Jan Kara
  Cc: linux-fsdevel, linux-kernel, Mikhail Rudenko

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@yandex-team.ru>
---
 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


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context
  2026-08-13  9:43 [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context Mikhail Rudenko
  2026-08-13  9:43 ` [PATCH RFC 1/2] fs: add superblock deferred iput infrastructure Mikhail Rudenko
  2026-08-13  9:43 ` [PATCH RFC 2/2] fs: defer dirtytime iput from PF_MEMALLOC context Mikhail Rudenko
@ 2026-09-01 10:10 ` Mikhail Rudenko
  2026-09-01 12:24 ` Jan Kara
  3 siblings, 0 replies; 6+ messages in thread
From: Mikhail Rudenko @ 2026-09-01 10:10 UTC (permalink / raw)
  To: Mikhail Rudenko
  Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
	linux-kernel

Gentle ping!

This is my first contribution to VFS (but not the first to the
kernel). At this point I'd like to know whether my approach makes sense
at all.

--
Best regards,
Mikhail Rudenko

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context
  2026-08-13  9:43 [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context Mikhail Rudenko
                   ` (2 preceding siblings ...)
  2026-09-01 10:10 ` [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in " Mikhail Rudenko
@ 2026-09-01 12:24 ` Jan Kara
  2026-09-01 12:54   ` Mikhail Rudenko
  3 siblings, 1 reply; 6+ messages in thread
From: Jan Kara @ 2026-09-01 12:24 UTC (permalink / raw)
  To: Mikhail Rudenko
  Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
	linux-kernel

I'm sorry for the delayed reply. Due to vacation and backlog after it it
took me long to get back to this.

On Thu 13-08-26 12:43:28, Mikhail Rudenko wrote:
> The motivation for this small series is the following kernel WARNING:
> 
> [  893.915584] ------------[ cut here ]------------
> [  893.915589] WARNING: mm/page_alloc.c:4749 at __alloc_pages_slowpath.constprop.0+0xd4d/0x10f0, CPU#1: stress-ng-vm/3784

<snip>
 
> It was initially observed in the wild on linux 6.12.y, but later I
> reproduced it in Qemu with linux 7.0.11. I believe it is still an
> issue in the mainline master. Similar issue was reported earlier [1],
> but looks like no real analysis was performed then.
> 
> The warning is triggered when a process with PF_MEMALLOC flag set
> requests a __GFP_NOFAIL page allocation. In this case, the allocation
> comes from ext4_dirty_inode() called when shrinker frees a dentry,
> resulting in i_count of an inode going zero, which in it turn can
> trigger sync_lazytime() if said inode has I_DIRTY_TIME flag, resulting
> in inode becoming dirty. On ext4 this causes an immediate journal
> transaction, which may lead to __GFP_NOFAIL allocation in the
> slowpath.

Yes, we are well aware of this problem. It actually goes significantly
further than just lazytime processing.

> The proposed approach to fix this basically boils down to "if need to
> do sync_lazytime() in PF_MEMALLOC context, defer instead".
> 
> Patch 1 adds basic per-superblock infrastracture for deferring iputs:
> a workqueue, a work_struct, a list, a bool flag, a spinlock, and some
> glue code. A tricky place is superblock shutdown: we should stop
> accepting deferred iputs not too early, so that no legit cases fail to
> defer, and not too late, so that superblock is still operational and
> we can flush the workqueue. Since we cannot enter
> deactivate_locked_super() from PF_MEMALLOC context, stopping after
> shrinker_free() seems safe.
> 
> Patch 2 uses this infrastructure to defer iputs of inodes with
> I_DIRTY_TIME, which would trigger sync_lazytime() otherwise, when
> running in PF_MEMALLOC context. Clumping default and PF_MEMALLOC paths
> together in iput() would have resulted in decreased readablity, so I
> avoided that by setting up a separate iput_memalloc(), at cost of
> slight code duplication.

Thanks for your patches. I've actually submitted patches to address this
problem back at the end of April [1]. In particularly the problem you
report was addressed somewhat differently by patch 1. We've then discussed
them at LSF/MM/BPF summit and people mostly agreed with the approach, just
there were some requests for better parallelization of deferred inode
reclaim (which I so far failed to implement). But it's currently high on my
todo list so I should hopefully send v2 of the patches for the next merge
window.

								Honza

[1] https://lore.kernel.org/all/20260429174850.18223-1-jack@suse.cz/
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context
  2026-09-01 12:24 ` Jan Kara
@ 2026-09-01 12:54   ` Mikhail Rudenko
  0 siblings, 0 replies; 6+ messages in thread
From: Mikhail Rudenko @ 2026-09-01 12:54 UTC (permalink / raw)
  To: Jan Kara; +Cc: Alexander Viro, Christian Brauner, linux-fsdevel, linux-kernel


Hi Jan,

On 2026-09-01 at 14:24 +02, Jan Kara <jack@suse.cz> wrote:

> Thanks for your patches. I've actually submitted patches to address this
> problem back at the end of April [1]. In particularly the problem you
> report was addressed somewhat differently by patch 1. We've then discussed
> them at LSF/MM/BPF summit and people mostly agreed with the approach, just
> there were some requests for better parallelization of deferred inode
> reclaim (which I so far failed to implement). But it's currently high on my
> todo list so I should hopefully send v2 of the patches for the next merge
> window.
>
> 								Honza
>
> [1] https://lore.kernel.org/all/20260429174850.18223-1-jack@suse.cz/

My bad, should've done better prior art reasearch. :) Looking forward
to your v2 then.

--
Best regards,
Mikhail Rudenko

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-01 13:00 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13  9:43 [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in PF_MEMALLOC context Mikhail Rudenko
2026-08-13  9:43 ` [PATCH RFC 1/2] fs: add superblock deferred iput infrastructure Mikhail Rudenko
2026-08-13  9:43 ` [PATCH RFC 2/2] fs: defer dirtytime iput from PF_MEMALLOC context Mikhail Rudenko
2026-09-01 10:10 ` [PATCH RFC 0/2] Defer final iputs for dirty-time inodes in " Mikhail Rudenko
2026-09-01 12:24 ` Jan Kara
2026-09-01 12:54   ` Mikhail Rudenko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox