[PATCH RFC 1/2] fs: add superblock deferred iput infrastructure

Mikhail Rudenko <[email protected]>
Newsgroups org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <20260813-deferred-dirtytime-iput-v1-1-ad2fb3ad6bdb@yandex-team.ru>
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 <[email protected]>
---
 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
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.