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(-)
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
Hello Andreas!
On Mon 21-09-26 23:17:11, Andreas Gruenbacher wrote:
> 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.
So for situations like this I've written a patch set to allow filesystem
to mark inode for deferred reclaim (from a workqueue) [1]. Would that work
for you? It will definitely solve your problems with warnings in memory
allocator.
I don't want to have two different mechanisms for the same issue so if my
solution doesn't quite work for you, let's discuss how to make it better
:).
Honza
[1] https://lore.kernel.org/all/20260911081309.14137-1-jack@suse.cz
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
Jan,
On Tue, Sep 22, 2026 at 1:22 PM Jan Kara <jack@suse.cz> wrote:
> Hello Andreas!
>
> On Mon 21-09-26 23:17:11, Andreas Gruenbacher wrote:
> > 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.
>
> So for situations like this I've written a patch set to allow filesystem
> to mark inode for deferred reclaim (from a workqueue) [1]. Would that work
> for you? It will definitely solve your problems with warnings in memory
> allocator.
>
> I don't want to have two different mechanisms for the same issue so if my
> solution doesn't quite work for you, let's discuss how to make it better
> :).
thanks, this would solve some of my problems. In the gfs2 case, inodes
are only in "deferred reclaim" state for a while (while they have
outstanding revokes) before becoming directly reclaimable again. So
I'd also need a way to clear the I_DEFER_RECLAIM flag again.
The I_DEFER_RECLAIM flag setting and clearing code would be racy and
ugly though, so I'm wondering if we can change the ->evict_inode()
super operation to return a DIRECT / DEFERRED status code instead. The
filesystem could then look at the filesystem-internal state and return
how to proceed with the inode. For example, fs2_evict_inode() would
probably request a DEFERED evict when GLF_LFLUSH is set, PF_MEMALLOC
is set, and PF_KSWAPD is not set.
Thanks,
Andreas
On Wed 23-09-26 23:01:52, Andreas Gruenbacher wrote:
> On Tue, Sep 22, 2026 at 1:22 PM Jan Kara <jack@suse.cz> wrote:
> > Hello Andreas!
> >
> > On Mon 21-09-26 23:17:11, Andreas Gruenbacher wrote:
> > > 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.
> >
> > So for situations like this I've written a patch set to allow filesystem
> > to mark inode for deferred reclaim (from a workqueue) [1]. Would that work
> > for you? It will definitely solve your problems with warnings in memory
> > allocator.
> >
> > I don't want to have two different mechanisms for the same issue so if my
> > solution doesn't quite work for you, let's discuss how to make it better
> > :).
>
> thanks, this would solve some of my problems. In the gfs2 case, inodes
> are only in "deferred reclaim" state for a while (while they have
> outstanding revokes) before becoming directly reclaimable again. So
> I'd also need a way to clear the I_DEFER_RECLAIM flag again.
Well, that's kind of the case for other filesystems as well. But since
deferred reclaim is relatively cheap my current thinking was that if
somebody did something expensive to the inode like allocating blocks
(that's what triggers this for ext4, not sure what exactly triggers the
state for GFS2), then we can afford also the additional small cost of
offloading the reclaim to a workqueue (regardless whether it is really
needed at the time reclaim actually happens). We just don't want to
unconditionally offload because if someone is just statting millions of
inodes on some filesystem, he's basically bound by the speed of reclaim on
sufficiently fast storage and the additional delay of reclaim and the cost
of offloading would become visible.
> The I_DEFER_RECLAIM flag setting and clearing code would be racy and
> ugly though, so I'm wondering if we can change the ->evict_inode()
> super operation to return a DIRECT / DEFERRED status code instead. The
> filesystem could then look at the filesystem-internal state and return
> how to proceed with the inode. For example, fs2_evict_inode() would
> probably request a DEFERED evict when GLF_LFLUSH is set, PF_MEMALLOC
> is set, and PF_KSWAPD is not set.
Right, but I really want filesystems to mess with process reclaim flags as
little as possible because that smells with layering violation and
generally tends to be fragile. In some code paths such as iput_final()
or evict_inodes() I really prefer to be sure that the filesystem just frees
the inode without having to check details of all the implementations making
sure they don't do something stupid and offload when they shouldn't.
So if we do something like this, I'd probably prefer explicit flag to
->evict() telling that offloading is possible and in that case filesystem
can return special value meaning it requests offloading and prune_icache()
would handle that.
Another problem I see with this scheme is that VFS is completely oblivious
to the number of inodes for which reclaim will be deferred and so there's
no chance for throttling places that create such inodes when the reclaim is
getting overloaded.
So overall I'd prefer we avoid all this complexity at least until we have a
real-world case where the performance impact of unnecessary offloading is
visible.
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
© 2016 - 2026 Red Hat, Inc.