Skip to contentSkip to conversation

mm/thp: clear deferred split shrinker bits when queues drain

PATCH Applied Lance Yang 5 participants 10 messages Jun 2 – Jun 13

Subsystems (3)
lore
Share & tools
lore

Lance Yang

author reviewer
#0 lore raw

PATCH 1/1 mm/thp: clear deferred split shrinker bits when queues drain

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
 mm/huge_memory.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
mm/huge_memory.c +4 -1
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)
 static unsigned long deferred_split_count(struct shrinker *shrink,
 		struct shrink_control *sc)
 {
-	return list_lru_shrink_count(&deferred_split_lru, sc);
+	unsigned long count;
+
+	count = list_lru_shrink_count(&deferred_split_lru, sc);
+	return count ?: SHRINK_EMPTY;
 }
 
 static bool thp_underused(struct folio *folio)
Signature · 1 line
2.49.0
author reviewer
#1 lore raw

Sorry, I missed Johannes in Cc ...

Quoted 33 lines · Lance Yang

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
  mm/huge_memory.c | 5 ++++-
  1 file changed, 4 insertions(+), 1 deletion(-)
mm/huge_memory.c +4 -1
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)
  static unsigned long deferred_split_count(struct shrinker *shrink,
  		struct shrink_control *sc)
  {
-	return list_lru_shrink_count(&deferred_split_lru, sc);
+	unsigned long count;
+
+	count = list_lru_shrink_count(&deferred_split_lru, sc);
+	return count ?: SHRINK_EMPTY;
  }
 
  static bool thp_underused(struct folio *folio)
#2 lore raw
Quoted 11 lines · Lance Yang

Sorry, I missed Johannes in Cc ...

Quoted 8 lines · Lance Yang

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

What's the effect of that? Would we consider it a fix that we'd want to backport?

Quoted 9 lines
Quoted 9 lines

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
  mm/huge_memory.c | 5 ++++-

  1 file changed, 4 insertions(+), 1 deletion(-)
Quoted 17 lines
Quoted 6 lines
mm/huge_memory.c +0 -0
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool
 partially_mapped)

mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool · line 4406

Quoted 10 lines
mm/huge_memory.c +4 -1
   static unsigned long deferred_split_count(struct shrinker *shrink,
           struct shrink_control *sc)
   {
-    return list_lru_shrink_count(&deferred_split_lru, sc);
+    unsigned long count;
+
+    count = list_lru_shrink_count(&deferred_split_lru, sc);
+    return count ?: SHRINK_EMPTY;
   }
     static bool thp_underused(struct folio *folio)

This is against Johannes' work, right?

If this is a fix, likely it would be fixing 87eaceb3faa5 ("mm: thp: make deferred split shrinker memcg aware"), right?

Signature · 3 lines
Cheers,

David
author reviewer
#3 lore raw
Quoted 6 lines · David Hildenbrand (Arm)
Quoted 5 lines · Lance Yang

Sorry, I missed Johannes in Cc ...

Quoted 2 lines · Lance Yang

From: Lance Yang <[email protected]>

Quoted 8 lines
Quoted 6 lines
Quoted 6 lines

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

What's the effect of that? Would we consider it a fix that we'd want to backport?

Just a stale memcg shrinker bit :) I'd treat this patch as a small cleanup.

Once the queue is empty, count_objects() returns 0. That skips the scan, but shrink_slab_memcg() only clears the bit on SHRINK_EMPTY, not 0.

So memcg reclaim can keep calling the shrinker even though there is nothing on that queue.

Quoted 27 lines
Quoted 9 lines
Quoted 9 lines

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
   mm/huge_memory.c | 5 ++++-
   1 file changed, 4 insertions(+), 1 deletion(-)
Quoted 17 lines
Quoted 6 lines
mm/huge_memory.c +0 -0
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool
 partially_mapped)

mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool · line 4406

Quoted 10 lines
mm/huge_memory.c +4 -1
   static unsigned long deferred_split_count(struct shrinker *shrink,
           struct shrink_control *sc)
   {
-    return list_lru_shrink_count(&deferred_split_lru, sc);
+    unsigned long count;
+
+    count = list_lru_shrink_count(&deferred_split_lru, sc);
+    return count ?: SHRINK_EMPTY;
   }
     static bool thp_underused(struct folio *folio)
Quoted 1 line

This is against Johannes' work, right?

Yep, I noticed it there, but the behavior is older.

Quoted 2 lines

If this is a fix, likely it would be fixing 87eaceb3faa5 ("mm: thp: make deferred split shrinker memcg aware"), right?

No missed reclaim, just some extra reclaim work :)

Cheers, Lance

maintainer Reviewed-by
#4 lore raw
Quoted 20 lines · Lance Yang
Quoted 5 lines · David Hildenbrand (Arm)
Quoted 2 lines · Lance Yang

Sorry, I missed Johannes in Cc ...

What's the effect of that? Would we consider it a fix that we'd want to backport?

Just a stale memcg shrinker bit :) I'd treat this patch as a small cleanup.

Once the queue is empty, count_objects() returns 0. That skips the scan, but shrink_slab_memcg() only clears the bit on SHRINK_EMPTY, not 0.

So memcg reclaim can keep calling the shrinker even though there is nothing on that queue.

Quoted 2 lines
Quoted 1 line
Quoted 8 lines
Quoted 1 line

This is against Johannes' work, right?

Yep, I noticed it there, but the behavior is older.

Quoted 2 lines

If this is a fix, likely it would be fixing 87eaceb3faa5 ("mm: thp: make deferred split shrinker memcg aware"), right?

No missed reclaim, just some extra reclaim work :)

Thanks for clarifying! :)

Signature · 3 lines
Cheers,

David
reviewer Acked-by on v1 1/1
#5 lore raw
In reply to Lance Yang
Quoted 5 lines · Lance Yang

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

Quoted 8 lines

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

Same as slab, workingset and others.

maintainer
#6 lore raw
In reply to Lance Yang
Quoted 23 lines · On Tue, 2 Jun 2026 12:34:53 +0800 Lance Yang <[email protected]> wrote:

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
 mm/huge_memory.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
mm/huge_memory.c +0 -0
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)
 static unsigned long deferred_split_count(struct shrinker *shrink,

mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4406

Quoted 10 lines
mm/huge_memory.c +4 -1
 		struct shrink_control *sc)
 {
-	return list_lru_shrink_count(&deferred_split_lru, sc);
+	unsigned long count;
+
+	count = list_lru_shrink_count(&deferred_split_lru, sc);
+	return count ?: SHRINK_EMPTY;
 }
 
 static bool thp_underused(struct folio *folio)

Should this be handled as a fix against hannes's "mm: switch deferred split shrinker to list_lru"?

author reviewer
#7 lore raw
Quoted 34 lines · Andrew Morton
Quoted 32 lines · On Tue, 2 Jun 2026 12:34:53 +0800 Lance Yang <[email protected]> wrote:

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
  mm/huge_memory.c | 5 ++++-
  1 file changed, 4 insertions(+), 1 deletion(-)
mm/huge_memory.c +4 -1
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)
  static unsigned long deferred_split_count(struct shrinker *shrink,
  		struct shrink_control *sc)
  {
-	return list_lru_shrink_count(&deferred_split_lru, sc);
+	unsigned long count;
+
+	count = list_lru_shrink_count(&deferred_split_lru, sc);
+	return count ?: SHRINK_EMPTY;
  }
 
Quoted 4 lines

mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4406

Quoted 1 line
mm/huge_memory.c +0 -0
  static bool thp_underused(struct folio *folio)

Should this be handled as a fix against hannes's "mm: switch deferred split shrinker to list_lru"?

Hmm ... I noticed this while looking at Johannes' work, but the behavior is older than that ...

We also discussed the Fixes tag earlier[1] and decided to leave it out. There is no missed reclaim, only some extra reclaim work :)

[1] https://lore.kernel.org/linux-mm/[email protected]/

Cheers, Lance

#8 lore raw
Quoted 42 lines · Lance Yang
Quoted 30 lines · Andrew Morton
Quoted 28 lines · On Tue,  2 Jun 2026 12:34:53 +0800 Lance Yang <[email protected]> wrote:

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
  mm/huge_memory.c | 5 ++++-

  1 file changed, 4 insertions(+), 1 deletion(-)
mm/huge_memory.c +1 -1
index 72f6caf0fec6..62d598290c3b 100644
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool
 partially_mapped)
   static unsigned long deferred_split_count(struct shrinker *shrink,
           struct shrink_control *sc)
   {
-    return list_lru_shrink_count(&deferred_split_lru, sc);
+    unsigned long count;
Quoted 8 lines

mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4406

Quoted 5 lines
mm/huge_memory.c +3 -0
+
+    count = list_lru_shrink_count(&deferred_split_lru, sc);
+    return count ?: SHRINK_EMPTY;
   }
     static bool thp_underused(struct folio *folio)

Should this be handled as a fix against hannes's "mm: switch deferred split shrinker to list_lru"?

Quoted 5 lines

Hmm ... I noticed this while looking at Johannes' work, but the behavior is older than that ...

We also discussed the Fixes tag earlier[1] and decided to leave it out. There is no missed reclaim, only some extra reclaim work :)

Right, I conclude that this can be a separate patch on top.

Signature · 3 lines
Cheers,

David
Reviewed-by on v1 1/1
#9 lore raw
In reply to Lance Yang
Quoted 22 lines · Lance Yang

From: Lance Yang <[email protected]>

deferred_split_count() returns the raw list_lru count. When the per-memcg, per-node list is empty, that count is 0.

That skips scanning, but it does not tell memcg reclaim that the shrinker is empty. shrink_slab_memcg() only clears the memcg shrinker bit when the count callback reports SHRINK_EMPTY.

Return SHRINK_EMPTY for an empty deferred split list, so the bit can be cleared once the queue has drained.

---
mm/huge_memory.c | 5 ++++-

1 file changed, 4 insertions(+), 1 deletion(-)
mm/huge_memory.c +0 -0
index 72f6caf0fec6..62d598290c3b 100644

mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4404

Quoted 9 lines
mm/huge_memory.c +4 -1
@@ -4397,7 +4397,10 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)
 static unsigned long deferred_split_count(struct shrinker *shrink,
 		struct shrink_control *sc)
 {
-	return list_lru_shrink_count(&deferred_split_lru, sc);
+	unsigned long count;
+
+	count = list_lru_shrink_count(&deferred_split_lru, sc);
+	return count ?: SHRINK_EMPTY;
 }

Looks you are right, thanks.

Quoted 5 lines
mm/huge_memory.c +0 -0
 static bool thp_underused(struct folio *folio)
Signature · 1 line
2.49.0
Signature · 2 lines
Wei Yang
Help you, Help me