* [RFC PATCH 0/2] Defer evicting inodes to a workqueue
@ 2025-09-24 9:09 Matthew Wilcox (Oracle)
2025-09-24 9:09 ` [RFC PATCH 1/2] Add in_reclaim() Matthew Wilcox (Oracle)
` (3 more replies)
0 siblings, 4 replies; 14+ messages in thread
From: Matthew Wilcox (Oracle) @ 2025-09-24 9:09 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara
Cc: Matthew Wilcox (Oracle), linux-fsdevel, Dave Chinner
Evicting an inode is a complex process which may require allocating
memory, running a transaction, etc, etc. Doing it as part of reclaim
is a bad idea and leads to hard-to-reproduce bug reports. This pair of
patches defers it to a workqueue if we're in reclaim.
Bugs:
https://lore.kernel.org/all/CALm_T+3j+dyK02UgPiv9z0f1oj-HM63oxhsB0JF9gVAjeVfm1Q@mail.gmail.com/
https://lore.kernel.org/all/CALm_T+2cEDUJvjh6Lv+6Mg9QJxGBVAHu-CY+okQgh-emWa7-1A@mail.gmail.com/
https://lore.kernel.org/all/20250326105914.3803197-1-matt@readmodwrite.com/
I don't know if this is a good idea, to be honest. We're kind of lying
to reclaim by pretending that we've freed N inodes when actually we've
just queued them for eviction. On the other hand, XFS has been doing
it for years, so perhaps it's not important.
I think the real solution here is to convert the Linux VFS to use the
same inode lifecycle as IRIX, but I don't fully understand the downsides
of that approach. One major pro of course is that XFS wouldn't have to
work around the Linux VFS any more.
I do wonder if a better approach might be:
+++ b/fs/inode.c
@@ -883,6 +883,10 @@ void evict_inodes(struct super_block *sb)
spin_unlock(&inode->i_lock);
continue;
}
+ if (in_reclaim() && (inode->i_state & I_DIRTY_ALL)) {
+ spin_unlock(&inode->i_lock);
+ continue;
+ }
inode->i_state |= I_FREEING;
inode_lru_list_del(inode);
Thoughts?
Matthew Wilcox (Oracle) (2):
Add in_reclaim()
fs: Defer evicting inodes to a workqueue
fs/inode.c | 36 ++++++++++++++++++++++++++++++++++--
include/linux/sched/mm.h | 11 +++++++++++
mm/page_alloc.c | 10 +++++-----
3 files changed, 50 insertions(+), 7 deletions(-)
--
2.47.2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [RFC PATCH 1/2] Add in_reclaim()
2025-09-24 9:09 [RFC PATCH 0/2] Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
@ 2025-09-24 9:09 ` Matthew Wilcox (Oracle)
2025-09-24 11:37 ` Jan Kara
2025-09-24 9:09 ` [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
` (2 subsequent siblings)
3 siblings, 1 reply; 14+ messages in thread
From: Matthew Wilcox (Oracle) @ 2025-09-24 9:09 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara
Cc: Matthew Wilcox (Oracle), linux-fsdevel, Dave Chinner
This is more meaningful than checking PF_MEMALLOC.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
include/linux/sched/mm.h | 11 +++++++++++
mm/page_alloc.c | 10 +++++-----
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h
index 2201da0afecc..a9825ea7c331 100644
--- a/include/linux/sched/mm.h
+++ b/include/linux/sched/mm.h
@@ -468,6 +468,17 @@ static inline void memalloc_pin_restore(unsigned int flags)
memalloc_flags_restore(flags);
}
+/**
+ * in_reclaim - Is the current task doing reclaim?
+ *
+ * This is true if the current task is kswapd or if we've entered
+ * direct reclaim.
+ */
+static inline bool in_reclaim(void)
+{
+ return current->flags & PF_MEMALLOC;
+}
+
#ifdef CONFIG_MEMCG
DECLARE_PER_CPU(struct mem_cgroup *, int_active_memcg);
/**
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index d1d037f97c5f..d27265df56b5 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -4220,7 +4220,7 @@ static bool __need_reclaim(gfp_t gfp_mask)
return false;
/* this guy won't enter reclaim */
- if (current->flags & PF_MEMALLOC)
+ if (in_reclaim())
return false;
if (gfp_mask & __GFP_NOLOCKDEP)
@@ -4455,10 +4455,10 @@ static inline int __gfp_pfmemalloc_flags(gfp_t gfp_mask)
return 0;
if (gfp_mask & __GFP_MEMALLOC)
return ALLOC_NO_WATERMARKS;
- if (in_serving_softirq() && (current->flags & PF_MEMALLOC))
+ if (in_serving_softirq() && in_reclaim())
return ALLOC_NO_WATERMARKS;
if (!in_interrupt()) {
- if (current->flags & PF_MEMALLOC)
+ if (in_reclaim())
return ALLOC_NO_WATERMARKS;
else if (oom_reserves_allowed(current))
return ALLOC_OOM;
@@ -4627,7 +4627,7 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
* because we cannot reclaim anything and only can loop waiting
* for somebody to do a work for us.
*/
- WARN_ON_ONCE(current->flags & PF_MEMALLOC);
+ WARN_ON_ONCE(in_reclaim());
}
restart:
@@ -4774,7 +4774,7 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
goto nopage;
/* Avoid recursion of direct reclaim */
- if (current->flags & PF_MEMALLOC)
+ if (in_reclaim())
goto nopage;
/* Try direct reclaim and then allocating */
--
2.47.2
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:09 [RFC PATCH 0/2] Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
2025-09-24 9:09 ` [RFC PATCH 1/2] Add in_reclaim() Matthew Wilcox (Oracle)
@ 2025-09-24 9:09 ` Matthew Wilcox (Oracle)
2025-09-24 9:28 ` Kiryl Shutsemau
` (2 more replies)
2025-09-24 11:34 ` [RFC PATCH 0/2] " Jan Kara
2025-10-07 0:41 ` Dave Chinner
3 siblings, 3 replies; 14+ messages in thread
From: Matthew Wilcox (Oracle) @ 2025-09-24 9:09 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara
Cc: Matthew Wilcox (Oracle), linux-fsdevel, Dave Chinner
If we're in memory reclaim, evicting inodes is actually a bad idea.
The filesystem may need to allocate more memory to evict the inode
than it will free by evicting the inode. It's better to defer
evicting the inode until a workqueue has time to run.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/inode.c | 36 ++++++++++++++++++++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/fs/inode.c b/fs/inode.c
index 9d882b0fc787..fe7899cdd50c 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -854,6 +854,34 @@ static void dispose_list(struct list_head *head)
}
}
+static DEFINE_SPINLOCK(deferred_inode_lock);
+static LIST_HEAD(deferred_inode_list);
+
+static void dispose_inodes_wq(struct work_struct *work)
+{
+ LIST_HEAD(dispose);
+
+ spin_lock_irq(&deferred_inode_lock);
+ list_splice_init(&deferred_inode_list, &dispose);
+ spin_unlock_irq(&deferred_inode_lock);
+
+ dispose_list(&dispose);
+}
+
+static DECLARE_WORK(dispose_inode_work, dispose_inodes_wq);
+
+static void deferred_dispose_inodes(struct list_head *inodes)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&deferred_inode_lock, flags);
+ list_splice_tail(inodes, &deferred_inode_list);
+ spin_unlock_irqrestore(&deferred_inode_lock, flags);
+
+ printk("deferring some inodes\n");
+ schedule_work(&dispose_inode_work);
+}
+
/**
* evict_inodes - evict all evictable inodes for a superblock
* @sb: superblock to operate on
@@ -897,13 +925,17 @@ void evict_inodes(struct super_block *sb)
if (need_resched()) {
spin_unlock(&sb->s_inode_list_lock);
cond_resched();
- dispose_list(&dispose);
+ if (!in_reclaim())
+ dispose_list(&dispose);
goto again;
}
}
spin_unlock(&sb->s_inode_list_lock);
- dispose_list(&dispose);
+ if (!in_reclaim())
+ dispose_list(&dispose);
+ else
+ deferred_dispose_inodes(&dispose);
}
EXPORT_SYMBOL_GPL(evict_inodes);
--
2.47.2
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:09 ` [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
@ 2025-09-24 9:28 ` Kiryl Shutsemau
2025-09-24 9:31 ` Matthew Wilcox
2025-09-24 10:04 ` Matthew Wilcox
2025-09-24 11:41 ` Jan Kara
2025-09-24 11:55 ` Mateusz Guzik
2 siblings, 2 replies; 14+ messages in thread
From: Kiryl Shutsemau @ 2025-09-24 9:28 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed, Sep 24, 2025 at 10:09:57AM +0100, Matthew Wilcox (Oracle) wrote:
> If we're in memory reclaim, evicting inodes is actually a bad idea.
> The filesystem may need to allocate more memory to evict the inode
> than it will free by evicting the inode. It's better to defer
> evicting the inode until a workqueue has time to run.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> ---
> fs/inode.c | 36 ++++++++++++++++++++++++++++++++++--
> 1 file changed, 34 insertions(+), 2 deletions(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index 9d882b0fc787..fe7899cdd50c 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -854,6 +854,34 @@ static void dispose_list(struct list_head *head)
> }
> }
>
> +static DEFINE_SPINLOCK(deferred_inode_lock);
> +static LIST_HEAD(deferred_inode_list);
> +
> +static void dispose_inodes_wq(struct work_struct *work)
> +{
> + LIST_HEAD(dispose);
> +
> + spin_lock_irq(&deferred_inode_lock);
> + list_splice_init(&deferred_inode_list, &dispose);
> + spin_unlock_irq(&deferred_inode_lock);
> +
> + dispose_list(&dispose);
> +}
> +
> +static DECLARE_WORK(dispose_inode_work, dispose_inodes_wq);
> +
> +static void deferred_dispose_inodes(struct list_head *inodes)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&deferred_inode_lock, flags);
Why _irqsave? I don't see any interactions with interrupts.
> + list_splice_tail(inodes, &deferred_inode_list);
> + spin_unlock_irqrestore(&deferred_inode_lock, flags);
> +
> + printk("deferring some inodes\n");
Debug leftovers?
> + schedule_work(&dispose_inode_work);
> +}
> +
> /**
> * evict_inodes - evict all evictable inodes for a superblock
> * @sb: superblock to operate on
> @@ -897,13 +925,17 @@ void evict_inodes(struct super_block *sb)
> if (need_resched()) {
> spin_unlock(&sb->s_inode_list_lock);
> cond_resched();
> - dispose_list(&dispose);
> + if (!in_reclaim())
> + dispose_list(&dispose);
> goto again;
> }
> }
> spin_unlock(&sb->s_inode_list_lock);
>
> - dispose_list(&dispose);
> + if (!in_reclaim())
> + dispose_list(&dispose);
> + else
> + deferred_dispose_inodes(&dispose);
> }
> EXPORT_SYMBOL_GPL(evict_inodes);
>
> --
> 2.47.2
>
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:28 ` Kiryl Shutsemau
@ 2025-09-24 9:31 ` Matthew Wilcox
2025-09-24 11:42 ` Jan Kara
2025-09-24 10:04 ` Matthew Wilcox
1 sibling, 1 reply; 14+ messages in thread
From: Matthew Wilcox @ 2025-09-24 9:31 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed, Sep 24, 2025 at 10:28:09AM +0100, Kiryl Shutsemau wrote:
> > +static void deferred_dispose_inodes(struct list_head *inodes)
> > +{
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&deferred_inode_lock, flags);
>
> Why _irqsave? I don't see any interactions with interrupts.
Can't we enter reclaim from paths which have interrupts disabled?
I could easily be wrong about that.
> > + list_splice_tail(inodes, &deferred_inode_list);
> > + spin_unlock_irqrestore(&deferred_inode_lock, flags);
> > +
> > + printk("deferring some inodes\n");
>
> Debug leftovers?
Oops. Thanks.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:28 ` Kiryl Shutsemau
2025-09-24 9:31 ` Matthew Wilcox
@ 2025-09-24 10:04 ` Matthew Wilcox
1 sibling, 0 replies; 14+ messages in thread
From: Matthew Wilcox @ 2025-09-24 10:04 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed, Sep 24, 2025 at 10:28:09AM +0100, Kiryl Shutsemau wrote:
> > + printk("deferring some inodes\n");
>
> Debug leftovers?
$ grep defer kern.out
I have low confidence in the correctness of this patch as I don't have a
reliable reproducer. I haven't seen the printk trigger yet. Maybe I
should force it to trigger sometimes.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 0/2] Defer evicting inodes to a workqueue
2025-09-24 9:09 [RFC PATCH 0/2] Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
2025-09-24 9:09 ` [RFC PATCH 1/2] Add in_reclaim() Matthew Wilcox (Oracle)
2025-09-24 9:09 ` [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
@ 2025-09-24 11:34 ` Jan Kara
2025-09-24 17:45 ` Joanne Koong
2025-10-07 0:41 ` Dave Chinner
3 siblings, 1 reply; 14+ messages in thread
From: Jan Kara @ 2025-09-24 11:34 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed 24-09-25 10:09:55, Matthew Wilcox (Oracle) wrote:
> Evicting an inode is a complex process which may require allocating
> memory, running a transaction, etc, etc. Doing it as part of reclaim
> is a bad idea and leads to hard-to-reproduce bug reports. This pair of
> patches defers it to a workqueue if we're in reclaim.
>
> Bugs:
> https://lore.kernel.org/all/CALm_T+3j+dyK02UgPiv9z0f1oj-HM63oxhsB0JF9gVAjeVfm1Q@mail.gmail.com/
> https://lore.kernel.org/all/CALm_T+2cEDUJvjh6Lv+6Mg9QJxGBVAHu-CY+okQgh-emWa7-1A@mail.gmail.com/
> https://lore.kernel.org/all/20250326105914.3803197-1-matt@readmodwrite.com/
>
> I don't know if this is a good idea, to be honest. We're kind of lying
> to reclaim by pretending that we've freed N inodes when actually we've
> just queued them for eviction. On the other hand, XFS has been doing
> it for years, so perhaps it's not important.
Well, I guess as soon as your page allocation forward progress depends on
inode cache eviction (or any other slab cache in fact), you are trashing
hard and you are generally in a very bad situation (TM). So I as long as
you can give dentry + inode cache a kick "hey, you're too big for the
workload" and we'll shrink it within some reasonable time, we should be
mostly OK. But also note that XFS inode reclaim has various throttling
mechanisms so that we don't queue unbounded amount of work - because some
workloads may generate insane amounts of inodes and you eventually want to
throttle inode allocation rate to the rate you can reclaim them and so far
there's no other mechanism for that than blocking reclaim.
> I think the real solution here is to convert the Linux VFS to use the
> same inode lifecycle as IRIX, but I don't fully understand the downsides
> of that approach. One major pro of course is that XFS wouldn't have to
> work around the Linux VFS any more.
>
> I do wonder if a better approach might be:
>
> +++ b/fs/inode.c
> @@ -883,6 +883,10 @@ void evict_inodes(struct super_block *sb)
Why evict_inodes? I think you want prune_icache_sb() -> inode_lru_isolate()?
> spin_unlock(&inode->i_lock);
> continue;
> }
> + if (in_reclaim() && (inode->i_state & I_DIRTY_ALL)) {
Also I_DIRTY_ALL is far from matching all reasons why fs may take long to
reclaim the inode. There may be block preallocation to trim or some
journalling machinery cleanup to do etc...
> + spin_unlock(&inode->i_lock);
> + continue;
> + }
>
> inode->i_state |= I_FREEING;
> inode_lru_list_del(inode);
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 1/2] Add in_reclaim()
2025-09-24 9:09 ` [RFC PATCH 1/2] Add in_reclaim() Matthew Wilcox (Oracle)
@ 2025-09-24 11:37 ` Jan Kara
0 siblings, 0 replies; 14+ messages in thread
From: Jan Kara @ 2025-09-24 11:37 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed 24-09-25 10:09:56, Matthew Wilcox (Oracle) wrote:
> This is more meaningful than checking PF_MEMALLOC.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
This is IMO a good cleanup regardless of the other change. I always have to
lookup details in reclaim code when I need to deal with PF_MEMALLOC :).
Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> include/linux/sched/mm.h | 11 +++++++++++
> mm/page_alloc.c | 10 +++++-----
> 2 files changed, 16 insertions(+), 5 deletions(-)
>
> diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h
> index 2201da0afecc..a9825ea7c331 100644
> --- a/include/linux/sched/mm.h
> +++ b/include/linux/sched/mm.h
> @@ -468,6 +468,17 @@ static inline void memalloc_pin_restore(unsigned int flags)
> memalloc_flags_restore(flags);
> }
>
> +/**
> + * in_reclaim - Is the current task doing reclaim?
> + *
> + * This is true if the current task is kswapd or if we've entered
> + * direct reclaim.
> + */
> +static inline bool in_reclaim(void)
> +{
> + return current->flags & PF_MEMALLOC;
> +}
> +
> #ifdef CONFIG_MEMCG
> DECLARE_PER_CPU(struct mem_cgroup *, int_active_memcg);
> /**
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index d1d037f97c5f..d27265df56b5 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -4220,7 +4220,7 @@ static bool __need_reclaim(gfp_t gfp_mask)
> return false;
>
> /* this guy won't enter reclaim */
> - if (current->flags & PF_MEMALLOC)
> + if (in_reclaim())
> return false;
>
> if (gfp_mask & __GFP_NOLOCKDEP)
> @@ -4455,10 +4455,10 @@ static inline int __gfp_pfmemalloc_flags(gfp_t gfp_mask)
> return 0;
> if (gfp_mask & __GFP_MEMALLOC)
> return ALLOC_NO_WATERMARKS;
> - if (in_serving_softirq() && (current->flags & PF_MEMALLOC))
> + if (in_serving_softirq() && in_reclaim())
> return ALLOC_NO_WATERMARKS;
> if (!in_interrupt()) {
> - if (current->flags & PF_MEMALLOC)
> + if (in_reclaim())
> return ALLOC_NO_WATERMARKS;
> else if (oom_reserves_allowed(current))
> return ALLOC_OOM;
> @@ -4627,7 +4627,7 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
> * because we cannot reclaim anything and only can loop waiting
> * for somebody to do a work for us.
> */
> - WARN_ON_ONCE(current->flags & PF_MEMALLOC);
> + WARN_ON_ONCE(in_reclaim());
> }
>
> restart:
> @@ -4774,7 +4774,7 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
> goto nopage;
>
> /* Avoid recursion of direct reclaim */
> - if (current->flags & PF_MEMALLOC)
> + if (in_reclaim())
> goto nopage;
>
> /* Try direct reclaim and then allocating */
> --
> 2.47.2
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:09 ` [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
2025-09-24 9:28 ` Kiryl Shutsemau
@ 2025-09-24 11:41 ` Jan Kara
2025-09-24 11:55 ` Mateusz Guzik
2 siblings, 0 replies; 14+ messages in thread
From: Jan Kara @ 2025-09-24 11:41 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed 24-09-25 10:09:57, Matthew Wilcox (Oracle) wrote:
> If we're in memory reclaim, evicting inodes is actually a bad idea.
> The filesystem may need to allocate more memory to evict the inode
> than it will free by evicting the inode. It's better to defer
> evicting the inode until a workqueue has time to run.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> ---
> fs/inode.c | 36 ++++++++++++++++++++++++++++++++++--
> 1 file changed, 34 insertions(+), 2 deletions(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index 9d882b0fc787..fe7899cdd50c 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -854,6 +854,34 @@ static void dispose_list(struct list_head *head)
> }
> }
>
> +static DEFINE_SPINLOCK(deferred_inode_lock);
> +static LIST_HEAD(deferred_inode_list);
> +
> +static void dispose_inodes_wq(struct work_struct *work)
> +{
> + LIST_HEAD(dispose);
> +
> + spin_lock_irq(&deferred_inode_lock);
> + list_splice_init(&deferred_inode_list, &dispose);
> + spin_unlock_irq(&deferred_inode_lock);
> +
> + dispose_list(&dispose);
> +}
> +
> +static DECLARE_WORK(dispose_inode_work, dispose_inodes_wq);
> +
> +static void deferred_dispose_inodes(struct list_head *inodes)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&deferred_inode_lock, flags);
> + list_splice_tail(inodes, &deferred_inode_list);
> + spin_unlock_irqrestore(&deferred_inode_lock, flags);
> +
> + printk("deferring some inodes\n");
> + schedule_work(&dispose_inode_work);
> +}
> +
> /**
> * evict_inodes - evict all evictable inodes for a superblock
> * @sb: superblock to operate on
> @@ -897,13 +925,17 @@ void evict_inodes(struct super_block *sb)
As mentioned in my reply to cover letter this is a wrong function to patch.
You'll never see this triggering, unless kswapd can trigger fs unmount :)
I guess let's keep the discussion about the approach threaded behind the
cover letter so I won't comment more here.
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:31 ` Matthew Wilcox
@ 2025-09-24 11:42 ` Jan Kara
0 siblings, 0 replies; 14+ messages in thread
From: Jan Kara @ 2025-09-24 11:42 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Kiryl Shutsemau, Alexander Viro, Christian Brauner, Jan Kara,
linux-fsdevel, Dave Chinner
On Wed 24-09-25 10:31:25, Matthew Wilcox wrote:
> On Wed, Sep 24, 2025 at 10:28:09AM +0100, Kiryl Shutsemau wrote:
> > > +static void deferred_dispose_inodes(struct list_head *inodes)
> > > +{
> > > + unsigned long flags;
> > > +
> > > + spin_lock_irqsave(&deferred_inode_lock, flags);
> >
> > Why _irqsave? I don't see any interactions with interrupts.
>
> Can't we enter reclaim from paths which have interrupts disabled?
> I could easily be wrong about that.
You cannot really do allocation with __GFP_FS set from atomic context?
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue
2025-09-24 9:09 ` [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
2025-09-24 9:28 ` Kiryl Shutsemau
2025-09-24 11:41 ` Jan Kara
@ 2025-09-24 11:55 ` Mateusz Guzik
2 siblings, 0 replies; 14+ messages in thread
From: Mateusz Guzik @ 2025-09-24 11:55 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
Dave Chinner
On Wed, Sep 24, 2025 at 10:09:57AM +0100, Matthew Wilcox (Oracle) wrote:
> If we're in memory reclaim, evicting inodes is actually a bad idea.
> The filesystem may need to allocate more memory to evict the inode
> than it will free by evicting the inode. It's better to defer
> evicting the inode until a workqueue has time to run.
>
There is a bug in the patch (noted below).
As for whether the idea works on paper I have no idea.
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> ---
> fs/inode.c | 36 ++++++++++++++++++++++++++++++++++--
> 1 file changed, 34 insertions(+), 2 deletions(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index 9d882b0fc787..fe7899cdd50c 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -854,6 +854,34 @@ static void dispose_list(struct list_head *head)
> }
> }
>
> +static DEFINE_SPINLOCK(deferred_inode_lock);
> +static LIST_HEAD(deferred_inode_list);
> +
> +static void dispose_inodes_wq(struct work_struct *work)
> +{
> + LIST_HEAD(dispose);
> +
> + spin_lock_irq(&deferred_inode_lock);
> + list_splice_init(&deferred_inode_list, &dispose);
> + spin_unlock_irq(&deferred_inode_lock);
> +
> + dispose_list(&dispose);
> +}
As a side note there is lockless machinery to do this (see
__fput_deferred), but perhaps this is just easier to look at.
> @@ -897,13 +925,17 @@ void evict_inodes(struct super_block *sb)
> if (need_resched()) {
> spin_unlock(&sb->s_inode_list_lock);
> cond_resched();
> - dispose_list(&dispose);
> + if (!in_reclaim())
> + dispose_list(&dispose);
> goto again;
> }
> }
> spin_unlock(&sb->s_inode_list_lock);
>
> - dispose_list(&dispose);
> + if (!in_reclaim())
> + dispose_list(&dispose);
> + else
> + deferred_dispose_inodes(&dispose);
> }
> EXPORT_SYMBOL_GPL(evict_inodes);
I don't think this addresses the problems you linked. For example in the
first crash:
https://lore.kernel.org/all/CALm_T+3j+dyK02UgPiv9z0f1oj-HM63oxhsB0JF9gVAjeVfm1Q@mail.gmail.com/
evict_inodes() is only used on fs teardown AFAICS and I presume this
never executes within memory reclamation.
The trace at hand sports prune_icache_sb() -> inode_lru_isolate();
dispose_list() -> evict(), so I don't think your patch changes the
behavior in that case.
As in, I take it you wanted to patch prune_icache_sb() instead.
Even with this corrected there is a bug I don't blame you for:
weirdly enough the VFS layer expects all inodes to be sorted out in
generic_shutdown_super(). with the stock patch, assuming evict_inodes()
deferred anything and was called from something else than unmount, it
would not wait for deferred processing to finish. Same for deferred
processing in prune_icache_sb() or whatever else-non-unmount.
So for this to work you also need to patch unmount to stall waiting for
this bit to be done with all the inodes.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 0/2] Defer evicting inodes to a workqueue
2025-09-24 11:34 ` [RFC PATCH 0/2] " Jan Kara
@ 2025-09-24 17:45 ` Joanne Koong
2025-09-25 11:15 ` Jan Kara
0 siblings, 1 reply; 14+ messages in thread
From: Joanne Koong @ 2025-09-24 17:45 UTC (permalink / raw)
To: Jan Kara
Cc: Matthew Wilcox (Oracle), Alexander Viro, Christian Brauner,
linux-fsdevel, Dave Chinner
On Wed, Sep 24, 2025 at 4:35 AM Jan Kara <jack@suse.cz> wrote:
>
> On Wed 24-09-25 10:09:55, Matthew Wilcox (Oracle) wrote:
> > +++ b/fs/inode.c
> > @@ -883,6 +883,10 @@ void evict_inodes(struct super_block *sb)
>
> Why evict_inodes? I think you want prune_icache_sb() -> inode_lru_isolate()?
I think prune_dcache_sb() can lead to inode eviction in reclaim as
well (eg prune_dcache_sb() -> shrink_dentry_list() -> shrink_kill() ->
__dentry_kill() -> dentry_unlink_inode() -> evict()), so maybe this
should also be done there too.
Thanks,
Joanne
>
> > spin_unlock(&inode->i_lock);
> > continue;
> > }
> > + if (in_reclaim() && (inode->i_state & I_DIRTY_ALL)) {
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 0/2] Defer evicting inodes to a workqueue
2025-09-24 17:45 ` Joanne Koong
@ 2025-09-25 11:15 ` Jan Kara
0 siblings, 0 replies; 14+ messages in thread
From: Jan Kara @ 2025-09-25 11:15 UTC (permalink / raw)
To: Joanne Koong
Cc: Jan Kara, Matthew Wilcox (Oracle), Alexander Viro,
Christian Brauner, linux-fsdevel, Dave Chinner
On Wed 24-09-25 10:45:35, Joanne Koong wrote:
> On Wed, Sep 24, 2025 at 4:35 AM Jan Kara <jack@suse.cz> wrote:
> >
> > On Wed 24-09-25 10:09:55, Matthew Wilcox (Oracle) wrote:
> > > +++ b/fs/inode.c
> > > @@ -883,6 +883,10 @@ void evict_inodes(struct super_block *sb)
> >
> > Why evict_inodes? I think you want prune_icache_sb() -> inode_lru_isolate()?
>
> I think prune_dcache_sb() can lead to inode eviction in reclaim as
> well (eg prune_dcache_sb() -> shrink_dentry_list() -> shrink_kill() ->
> __dentry_kill() -> dentry_unlink_inode() -> evict()), so maybe this
> should also be done there too.
Well, this will end up removing the inode only in some corner cases (like
inode being deleted or when I_DONTCACHE flags is set. But in the most
common case iput_final() will just insert the inode into the LRU list.
But you're right that if we want kind of guarantee that reclaim won't block
on inode eviction, then this path should be handled as well.
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 0/2] Defer evicting inodes to a workqueue
2025-09-24 9:09 [RFC PATCH 0/2] Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
` (2 preceding siblings ...)
2025-09-24 11:34 ` [RFC PATCH 0/2] " Jan Kara
@ 2025-10-07 0:41 ` Dave Chinner
3 siblings, 0 replies; 14+ messages in thread
From: Dave Chinner @ 2025-10-07 0:41 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel
[sorry I'm late on this, been working on other things]
On Wed, Sep 24, 2025 at 10:09:55AM +0100, Matthew Wilcox (Oracle) wrote:
> Evicting an inode is a complex process which may require allocating
> memory, running a transaction, etc, etc. Doing it as part of reclaim
> is a bad idea and leads to hard-to-reproduce bug reports. This pair of
> patches defers it to a workqueue if we're in reclaim.
>
> Bugs:
> https://lore.kernel.org/all/CALm_T+3j+dyK02UgPiv9z0f1oj-HM63oxhsB0JF9gVAjeVfm1Q@mail.gmail.com/
> https://lore.kernel.org/all/CALm_T+2cEDUJvjh6Lv+6Mg9QJxGBVAHu-CY+okQgh-emWa7-1A@mail.gmail.com/
> https://lore.kernel.org/all/20250326105914.3803197-1-matt@readmodwrite.com/
So eviction running filesystem shrinkers that then do blocking IO,
folio operations and/or memory allocation and triggering warnings?
Seems like a problem GFP_NOFS was invented to solve, yes?
> I don't know if this is a good idea, to be honest. We're kind of lying
> to reclaim by pretending that we've freed N inodes when actually we've
> just queued them for eviction. On the other hand, XFS has been doing
> it for years, so perhaps it's not important.
tl;dr: we aren't lying, but because most people don't understand how
shrinker-based reclaim work is accounted for it can appear that way.
Long story:
memory relcaim (vmscan.c) does not track reclaim progress via the
number of objects the shrinkers report as freed. It tracks reclaim
progress via the amount of memory freed by the shrinker task. This
is what mm_account_reclaimed_pages() does, and it is implicit in
freeing operations as it is called from the slab code when it frees
the backing memory for a slab cache.
For other shrinkers that manually free memory, they have direct
calls to this function to record memory freeing. e.g. in the XFS
buffer cache when the shrinker disposes of a buffer and calls
xfs_buf_free().
This is a completely separate mechanism to the shrinker work
equalisation mechanism that uses object counts to apply the same
pressure to all shrinkable caches in the system. The shrinkers need
to scan a specific number of objects, and return a count based on
the progress they made. This returned count is ignored by the memory
reclaim code, it is only used as measure of progress being made
in certain siutations (e.g. drop_caches) and so is meaningless in
the course of this discussion.
IOWs, all the shrinker has to do is scan that number of objects for
freeing. It does not have to free them, nor does the returned
"objects freed" imply that any memory was actually freed.
Indeed, the VFS does lazy LRU removal, so it's common for the inode
cache shrinker to remove referenced objects from the LRU and not
free them. In that case, the reclaimable object count goes down, but
the freed count does not go up. The cache got "smaller", but we
didn't free anything. This is perfectly fine.
Indeed, in the case of the superblock shrinker, the "freed" count
tracks how many objects had their VFS lifecycle terminated (e.g.
indoes that were evicted), but this does not imply that the objects
were actually freed. They are simply no longer tracked as freeable
cached objects by the VFS caches.
Why is this distinction important? Remember what I said above about
mm_account_reclaimed_pages() accounting actually freed memory? Now
consider that dentries and inodes are RCU freed.
What does that imply about the superblock shrinker actually freeing
memory?
What this means is that the superblock shrinker -never- frees inodes
or dentry objects directly. The memory being freed is -never-
accounted to the shrinker, because the shrinker task has to schedule
before an RCU grace period can expire and run the RCU callbacks to
free the objects.
IOWs, shrinkers are intended to terminate the life cycle of cached
objects, but the life cycle of the allocated object can (and does)
extend beyond the shrinker that terminates the life cycle.
In the case of XFS, the VFS inode is embedded in the XFS inode, and
the VFS lifecycle is a subset of the XFS inode lifecycle. When
the VFS inode is evicted, it's lifecycle is terminated, but it still
a valid, tracked object at the XFS level. Indeed, XFs puts clean
inodes straight back on the internal reclaim list and accounts for
it as reclaimable. So the number of objects the superblock shrinker
is tracking does not actually go down - it is now tracked via the
sb->s_op->nr_cached_objects(sb, sc) callouts in the superblock
shrinker as a reclaimable object.
If the inode needs eviction work, (i.e. inodegc) it doesn't
immediately get re-accounted as a reclaimable object. We do the
work, then once the inode is in a reclaimable state, we put it on
the reclaim queue and account for it at that point.
Put simply: XFS is not "lying" about anything to the
mm/reclaim/shrinker subystems. We are simply taking advantage of the
fact that the superblock shrinker always frees the VFS inodes
asynchronously via RCU. Hence there is always a disconnect between
between VFS object life cycle termination and the object being
freed. XFS inserts it's own "disconnection processing" into that
gap, and actually lets the superblock shrinker track those objects.
> I think the real solution here is to convert the Linux VFS to use the
> same inode lifecycle as IRIX,
This has nothing to do with Irix. I introduced background inode
freeing to XFS in 2011 to avoid the problems of having to wait for
the inode to be fully clean in evict() context. This introduced some
other issues that I solved in ~2020 with non-blocking inode reclaim
processing. And in 2021, we finally moved all the transactional
modifications out of the evict() path with the background inodegc
infrastructure I wrote.
All of this is possible because shrinkers are simply a mechanism for
terminating the life cycle of a given object and there is absolutely
no requirement for life cycle termination to free any memory.
Indeed, they may release references to other resources that pin
memory (e.g. dentries pin inodes) and so memory being freed might
actually be several steps removed...
> but I don't fully understand the downsides
> of that approach. One major pro of course is that XFS wouldn't have to
> work around the Linux VFS any more.
Not true - this stuff is way, way more complex than you imply.
On of the key factors in Josef's active/passive inode reference
infrastructure is that it could allow us to bring the "unreferenced"
part of the XFS inode lifecycle (i.e. everything that happens after
eviction) up into the VFS via passive VFS inode object references.
And at that point, could bring VFS active reference eviction
processing up into the VFS as an async deferal mechanism. But it
would also require us to bring the XFS inode cache VFS inode
recycling code as we could now get cache hits on a passively
referenced VFS inode which then needs the active VFS inodes state
reinstantiated.
There's other interactions as we'd need to lift as well, because
experience has proven that pushing eviction off to a workqueue will
create performance regressions. e.g. Try pushing hundreds of
thousands of inodes a second across dozens of CPUs through a single
workqueue (e.g. highly concurrent find across dozens of directories
under memory pressure).
Then we have to throttle the number of inodes you defer for
eviction. Once you have many thousands deferred, it can take a long
time for the workqueue to catch up, especially if each inode has to
free many, many extents. Unbounded queues are bad, especially when
each unit of queued work can largely be unbounded, too. (e.g.
eviction processing after an unlink of a file with tens of millions
of extents).
IOWs, there's a reason the XFS inodegc queuing is complex - it uses
bound depth per-cpu lockless lists and workqueues for the eviction
processing deferral queue. Avoiding performance impacts due to adding
two context switches to every inode eviction is not simple.
Async inode eviction also has interactions with freeze - what
happens when you have dirty inodes or inodes that require dirtying
to evict queued for eviction and then the fs is frozen? We can't
process those evictions whilst the fs is frozen - we can't modify
anything. Hence there is the possibility that every eviction worker
task can get stuck waiting for thaw, and now inode cache reclaim is
effectively stuck until a thaw occurs....
There is also user visible interactions with space usage (e.g. df vs
rm). Half of the unlink work is done on eviction (i.e. when the last
ref is dropped) and so space isn't freed until eviction processing
is complete. Hence if you run a large rm -rf, it queues all the
inode and extent freeing work to the background queues. From the
user perspective, when the rm -rf completes the directory structure
is gone, but the space hasn't been completely freed yet. It may take
minutes for the space to be freed.
So, yeah, async inode eviction is anything but simple, and from
previous attempts to lift this stuff out of XFS to the VFS, I came
to the conclusion the only way to do it sanely was to convert the
VFS inodes to use active/passive reference counting so that the VFS
could guarantee that the inode was truly unreferenced by the VFS
before cache eviction occurs....
Cheers,
Dave.
--
Dave Chinner
david@fromorbit.com
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2025-10-07 0:41 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-24 9:09 [RFC PATCH 0/2] Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
2025-09-24 9:09 ` [RFC PATCH 1/2] Add in_reclaim() Matthew Wilcox (Oracle)
2025-09-24 11:37 ` Jan Kara
2025-09-24 9:09 ` [RFC PATCH 2/2] fs: Defer evicting inodes to a workqueue Matthew Wilcox (Oracle)
2025-09-24 9:28 ` Kiryl Shutsemau
2025-09-24 9:31 ` Matthew Wilcox
2025-09-24 11:42 ` Jan Kara
2025-09-24 10:04 ` Matthew Wilcox
2025-09-24 11:41 ` Jan Kara
2025-09-24 11:55 ` Mateusz Guzik
2025-09-24 11:34 ` [RFC PATCH 0/2] " Jan Kara
2025-09-24 17:45 ` Joanne Koong
2025-09-25 11:15 ` Jan Kara
2025-10-07 0:41 ` Dave Chinner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox