Linux filesystem development
 help / color / mirror / Atom feed
From: Andreas Gruenbacher <agruenba@redhat.com>
To: Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
	Christoph Hellwig <hch@infradead.org>
Cc: Andreas Gruenbacher <agruenba@redhat.com>,
	Matthew Wilcox <willy@infradead.org>,
	gfs2@lists.linux.dev, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH] fs/gfs2: prevent premature evict in prune_icache_sb
Date: Mon, 21 Sep 2026 23:17:11 +0200	[thread overview]
Message-ID: <20260921211712.1563575-1-agruenba@redhat.com> (raw)

Introduce a new I_NOPRUNE i_state flag for preventing prune_icache_sb()
from pruning specific inodes.

prune_icache_sb() prunes clean inodes under memory pressure ("direct
reclaim").  An inode is considered clean when none of the i_state flags
are set; the assumption is that evicting inodes that don't have any
i_state flags set will be fairly cheap.

Unfortunately, on gfs2, inodes can be clean in the sense that they won't
require writing back to disk, but they may still have outstanding
revokes (as indicated by the GLF_LFLUSH inode glock flag).  To evict one
of those inodes, those outstanding revokes need to be written out first.

This requires flushing the log, which is already an expensive operation.
When in data=ordered mode, all the ordered data needs to be written out
before the log can be flushed, which makes things even worse.

As previously discussed [*], we are currently also running into the
following warning in iomap_writepages() when flushing ordered data:

    /*
     * Writeback from reclaim context should never happen except in the case
     * of a VM regression so warn about it and refuse to write the data.
     */
    if (WARN_ON_ONCE((current->flags & (PF_MEMALLOC | PF_KSWAPD)) ==
                    PF_MEMALLOC))
            return -EIO;

So we need to prevent prune_icache_sb() from evicting inodes that have
any outstanding revokes.  This patch achieves that by introducing a new
I_NOPRUNE i_state flag.  When that new flag (or any other flag) is set,
prune_icache_sb() will skip those inodes.

In the filesystem code, we set and clear the I_NOPRUNE flag in sync with
the GLF_LFLUSH inode glock flag.

An alternative approach might be to allow filesystems to refuse evicting
inodes that prune_icache_sb() has already selected.  This could be
achieved by changing the ->evict_inode super operation to return a
lru_status code or similar.  prune_icache_sb() would then have to
resurrect inodes that were already marked I_FREEING and put them back
onto the lru list.  This approach doesn't seem obviously better than
introducing I_NOPRUNE, so I haven't pursued this any further.

[*] https://lore.kernel.org/linux-fsdevel/CAHc6FU4tz8-HmEf2_XKT0NT8N=rv5OMcY79PxTACkXAVLOAUpg@mail.gmail.com/

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/gfs2/incore.h   |  3 +++
 fs/gfs2/inode.c    |  2 +-
 fs/gfs2/log.c      | 43 +++++++++++++++++++++++++++++++++++++++++--
 fs/gfs2/lops.c     |  2 +-
 fs/gfs2/trans.c    |  4 ++--
 include/linux/fs.h |  3 ++-
 6 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h
index 61465777826a..e37e5d057608 100644
--- a/fs/gfs2/incore.h
+++ b/fs/gfs2/incore.h
@@ -379,6 +379,9 @@ static inline u64 glock_number(const struct gfs2_glock *gl)
 	return gl->gl_name.ln_number;
 }
 
+void glock_set_lflush(struct gfs2_glock *gl);
+void glock_clear_lflush(struct gfs2_glock *gl);
+
 enum {
 	GIF_QD_LOCKED		= 1,
 	GIF_SW_PAGED		= 3,
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 9fce2d3e8e7d..22b5b404b8d5 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -466,7 +466,7 @@ static void gfs2_final_release_pages(struct gfs2_inode *ip)
 	truncate_inode_pages(&inode->i_data, 0);
 
 	if (atomic_read(&gl->gl_revokes) == 0) {
-		clear_bit(GLF_LFLUSH, &gl->gl_flags);
+		glock_clear_lflush(gl);
 		clear_bit(GLF_DIRTY, &gl->gl_flags);
 	}
 }
diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c
index 78bba8cc10b8..1f0bb3da4df6 100644
--- a/fs/gfs2/log.c
+++ b/fs/gfs2/log.c
@@ -24,6 +24,7 @@
 #include "incore.h"
 #include "bmap.h"
 #include "glock.h"
+#include "glops.h"
 #include "log.h"
 #include "lops.h"
 #include "meta_io.h"
@@ -760,6 +761,44 @@ void gfs2_ordered_del_inode(struct gfs2_inode *ip)
 	spin_unlock(&sdp->sd_ordered_lock);
 }
 
+void glock_set_lflush(struct gfs2_glock *gl)
+{
+	struct inode *inode;
+
+	set_bit(GLF_LFLUSH, &gl->gl_flags);
+
+	if (gl->gl_ops != &gfs2_inode_glops)
+		return;
+
+	spin_lock(&gl->gl_lockref.lock);
+	inode = gl->gl_object;
+	if (inode && !(GFS2_I(inode)->i_diskflags & GFS2_DIF_SYSTEM)) {
+		spin_lock(&inode->i_lock);
+		inode_state_set(inode, I_NOPRUNE);
+		spin_unlock(&inode->i_lock);
+	}
+	spin_unlock(&gl->gl_lockref.lock);
+}
+
+void glock_clear_lflush(struct gfs2_glock *gl)
+{
+	struct inode *inode;
+
+	clear_bit(GLF_LFLUSH, &gl->gl_flags);
+
+	if (gl->gl_ops != &gfs2_inode_glops)
+		return;
+
+	spin_lock(&gl->gl_lockref.lock);
+	inode = gl->gl_object;
+	if (inode && !(GFS2_I(inode)->i_diskflags & GFS2_DIF_SYSTEM)) {
+		spin_lock(&inode->i_lock);
+		inode_state_clear(inode, I_NOPRUNE);
+		spin_unlock(&inode->i_lock);
+	}
+	spin_unlock(&gl->gl_lockref.lock);
+}
+
 void gfs2_add_revoke(struct gfs2_sbd *sdp, struct gfs2_bufdata *bd)
 {
 	struct buffer_head *bh = bd->bd_bh;
@@ -772,14 +811,14 @@ void gfs2_add_revoke(struct gfs2_sbd *sdp, struct gfs2_bufdata *bd)
 	bd->bd_blkno = bh->b_blocknr;
 	gfs2_remove_from_ail(bd); /* drops ref on bh */
 	bd->bd_bh = NULL;
-	set_bit(GLF_LFLUSH, &gl->gl_flags);
+	glock_set_lflush(gl);
 	list_add(&bd->bd_list, &sdp->sd_log_revokes);
 }
 
 void gfs2_glock_remove_revoke(struct gfs2_glock *gl)
 {
 	if (atomic_dec_return(&gl->gl_revokes) == 0) {
-		clear_bit(GLF_LFLUSH, &gl->gl_flags);
+		glock_clear_lflush(gl);
 		gfs2_glock_put_async(gl);
 	}
 }
diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c
index 6dabe73ad790..8363c02f8b9e 100644
--- a/fs/gfs2/lops.c
+++ b/fs/gfs2/lops.c
@@ -127,7 +127,7 @@ static void gfs2_unpin(struct gfs2_sbd *sdp, struct buffer_head *bh,
 	list_add(&bd->bd_ail_st_list, &tr->tr_ail1_list);
 	spin_unlock(&sdp->sd_ail_lock);
 
-	clear_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
+	glock_clear_lflush(bd->bd_gl);
 	trace_gfs2_pin(bd, 0);
 	unlock_buffer(bh);
 	atomic_dec(&sdp->sd_log_pinned);
diff --git a/fs/gfs2/trans.c b/fs/gfs2/trans.c
index 65cbe06e301a..7a9da7253246 100644
--- a/fs/gfs2/trans.c
+++ b/fs/gfs2/trans.c
@@ -222,7 +222,7 @@ void gfs2_trans_add_data(struct gfs2_glock *gl, struct buffer_head *bh)
 	gfs2_assert(sdp, bd->bd_gl == gl);
 	set_bit(TR_TOUCHED, &tr->tr_flags);
 	if (list_empty(&bd->bd_list)) {
-		set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
+		glock_set_lflush(bd->bd_gl);
 		set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
 		gfs2_pin(sdp, bd->bd_bh);
 		tr->tr_num_databuf_new++;
@@ -287,7 +287,7 @@ void gfs2_trans_add_meta(struct gfs2_glock *gl, struct buffer_head *bh)
 	set_bit(TR_TOUCHED, &tr->tr_flags);
 	if (!list_empty(&bd->bd_list))
 		goto out_unlock;
-	set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
+	glock_set_lflush(bd->bd_gl);
 	set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
 	mh = (struct gfs2_meta_header *)bd->bd_bh->b_data;
 	if (unlikely(mh->mh_magic != cpu_to_be32(GFS2_MAGIC))) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index d10897b3a1e3..270aa3dead91 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -740,7 +740,8 @@ enum inode_state_flags_enum {
 	I_CREATING		= (1U << 15),
 	I_DONTCACHE		= (1U << 16),
 	I_SYNC_QUEUED		= (1U << 17),
-	I_PINNING_NETFS_WB	= (1U << 18)
+	I_PINNING_NETFS_WB	= (1U << 18),
+	I_NOPRUNE		= (1U << 19)
 };
 
 #define I_DIRTY_INODE (I_DIRTY_SYNC | I_DIRTY_DATASYNC)
-- 
2.55.0


             reply	other threads:[~2026-09-21 21:17 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-21 21:17 Andreas Gruenbacher [this message]
2026-09-22 11:22 ` [PATCH] fs/gfs2: prevent premature evict in prune_icache_sb Jan Kara
2026-09-23 21:01   ` Andreas Gruenbacher
2026-09-24  9:52     ` Jan Kara
2026-09-29 21:15       ` Andreas Gruenbacher

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260921211712.1563575-1-agruenba@redhat.com \
    --to=agruenba@redhat.com \
    --cc=brauner@kernel.org \
    --cc=gfs2@lists.linux.dev \
    --cc=hch@infradead.org \
    --cc=jack@suse.cz \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=willy@infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox