mm: fix deferred split queue races during migration
Subsystems (4)
Lance Yang
reviewerPATCH 1/1 mm: fix deferred split queue races during migration
From: Lance Yang <[email protected]>
migrate_folio_move() records the deferred split queue state from src and replays it on dst. Replaying it after remove_migration_ptes(src, dst, 0) makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN in deferred_split_folio().
Move the requeue before remove_migration_ptes() so dst is back on the deferred split queue before it becomes visible again.
Because migration still holds dst locked at that point, teach deferred_split_scan() to requeue a folio when folio_trylock() fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost from split_queue.
---
[ Backport note ]
This patch is a follow-up fix for 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue"), which is currently only in mm-stable, and should be backported together with it.
Credit for this fix goes to David, thanks!
mm/huge_memory.c | 12 +++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-)
mm/huge_memory.c +7 -5
@@ -4558,7 +4558,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue;if (!split_folio (folio)) { did_split = true;if (underused)
@@ -4569,11 +4569,13 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, next:if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/* -* Only add back to the queue if folio is partially mapped. -* If thp_underused returns false, or if split_folio fails -* in the case it was underused, then consider it used and -* don't add it back to split_queue. +* Add back partially mapped folios, or underused folios +* that we could not lock this round. If thp_underused() +* returns false, or if split_folio() succeeds, or if +* split_folio() fails in the case it was underused, then +* consider it used and don't add it back to split_queue. */ fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {
mm/migrate.c +9 -9
@@ -1385,6 +1385,15 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (rc) goto out; +/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will
@@ -1401,15 +1410,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);
Signature · 1 line
2.49.0
Usama Arif
reviewerQuoted 29 lines · On Wed, 1 Apr 2026 21:10:32 +0800 Lance Yang <[email protected]> wrote:
From: Lance Yang <[email protected]>
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Cc <[email protected]>Suggested-by David Hildenbrand (Arm) <[email protected]>Signed-off-by Lance Yang <[email protected]>---
[ Backport note ]
This patch is a follow-up fix for8a8ca142a488("mm: migrate: requeue destination folio on deferred split queue"), which is currently only in mm-stable, and should be backported together with it.
Quoted 6 lines
Credit for this fix goes to David, thanks!
mm/huge_memory.c | 12 +++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-)
Thanks for the fix! And sorry for introducing the bug in migrate_folio_move() :)
So I am happy with the migrate_folio_move() change, it makes sense.
The goto next if folio is locked in deferred_split_scan() was actually on purpose. The reasoning was that if the folio is locked, we consider it as in use by someone and therefore we shouldnt split it. Eventhough thp_underused() does a zero-filled check, the whole point of the shrinker was to split THPs that are "not in use", and in my mind, locked folio is a folio in use. So not sure about that change..
Quoted 71 lines
mm/huge_memory.c +7
-5@@ -4558,7 +4558,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue;if (!split_folio (folio)) { did_split = true;if (underused)@@ -4569,11 +4569,13 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, next:if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/* -* Only add back to the queue if folio is partially mapped. -* If thp_underused returns false, or if split_folio fails -* in the case it was underused, then consider it used and -* don't add it back to split_queue. +* Add back partially mapped folios, or underused folios +* that we could not lock this round. If thp_underused() +* returns false, or if split_folio() succeeds, or if +* split_folio() fails in the case it was underused, then +* consider it used and don't add it back to split_queue. */ fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {mm/migrate.c +9
-9@@ -1385,6 +1385,15 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (rc) goto out; +/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will @@ -1401,15 +1410,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);Signature · 1 line
2.49.0
David Hildenbrand (Arm)
maintainerQuoted 42 lines · Usama Arif
Quoted 29 lines · On Wed, 1 Apr 2026 21:10:32 +0800 Lance Yang <[email protected]> wrote:
From: Lance Yang <[email protected]>
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Cc <[email protected]>Suggested-by David Hildenbrand (Arm) <[email protected]>Signed-off-by Lance Yang <[email protected]>---
[ Backport note ]
This patch is a follow-up fix for8a8ca142a488("mm: migrate: requeue destination folio on deferred split queue"), which is currently only in mm-stable, and should be backported together with it.Quoted 6 lines
Credit for this fix goes to David, thanks!
mm/huge_memory.c | 12 +++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-)Thanks for the fix! And sorry for introducing the bug in
migrate_folio_move():)
Quoted 8 lines
So I am happy with the
migrate_folio_move()change, it makes sense.The goto next if folio is locked in
deferred_split_scan()was actually on purpose. The reasoning was that if the folio is locked, we consider it as in use by someone and therefore we shouldnt split it. Eventhoughthp_underused()does a zero-filled check, the whole point of the shrinker was to split THPs that are "not in use", and in my mind, locked folio is a folio in use. So not sure about that change..
That is a questionable assessment. It's about checking whether folios are *underused* not, if they are used, by whoever in the system (e.g., migration).
Just take a look when anonymous folios are actually locked :)
So the original locked handling here is just bogus.
Signature · 3 lines
Cheers, David
Quoted 15 lines · Lance Yang
From: Lance Yang <[email protected]>
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.
Quoted 8 lines
Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Cc <[email protected]>Suggested-by David Hildenbrand (Arm) <[email protected]>Signed-off-by Lance Yang <[email protected]>---
LGTM, thanks!
Signature · 3 lines
Cheers, David
Quoted 54 lines · Lance Yang
From: Lance Yang <[email protected]>
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Cc <[email protected]>Suggested-by David Hildenbrand (Arm) <[email protected]>Signed-off-by Lance Yang <[email protected]>---
[ Backport note ]
This patch is a follow-up fix for8a8ca142a488("mm: migrate: requeue destination folio on deferred split queue"), which is currently only in mm-stable, and should be backported together with it.Credit for this fix goes to David, thanks!
mm/huge_memory.c | 12 +++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-)mm/huge_memory.c +2
-1@@ -4558,7 +4558,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue;if (!split_folio (folio)) { did_split = true;if (underused)@@ -4569,11 +4569,13 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, next:if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/*
mm/huge_memory.c static unsigned long deferred_split_scan(struct shrinker *shrink, · line 4579
Quoted 10 lines
mm/huge_memory.c +5
-4-* Only add back to the queue if folio is partially mapped. -* If thp_underused returns false, or if split_folio fails -* in the case it was underused, then consider it used and -* don't add it back to split_queue. +* Add back partially mapped folios, or underused folios +* that we could not lock this round. If thp_underused() +* returns false, or if split_folio() succeeds, or if +* split_folio() fails in the case it was underused, then +* consider it used and don't add it back to split_queue. */
Should the sentence
“If thp_underused() returns false, or if split_folio() succeeds, or if split_folio() fails in the case it was underused, then consider it used and don't add it back to split_queue.”
be moved to below label next?
Since “thp_underused() returns false” is describing “if (!underused) goto next”, “split_folio() succeeds” is describing “did_split == true in the if”, “split_folio() fails in the case it was underused” is describing “did_split == false and !folio_test_partially_mapped(folio) in the if”.
The first sentence matches the goto requeue for folio_trylock().
Otherwise, LGTM.
Quoted 40 lines
mm/huge_memory.c +0
-0fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {mm/migrate.c +9
-9@@ -1385,6 +1385,15 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (rc) goto out; +/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will @@ -1401,15 +1410,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);Signature · 1 line
2.49.0
Best Regards,
Yan, Zi
Zi Yan
reviewerQuoted 73 lines · Zi Yan
Quoted 54 lines · Lance Yang
From: Lance Yang <[email protected]>
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Cc <[email protected]>Suggested-by David Hildenbrand (Arm) <[email protected]>Signed-off-by Lance Yang <[email protected]>---
[ Backport note ]
This patch is a follow-up fix for8a8ca142a488("mm: migrate: requeue destination folio on deferred split queue"), which is currently only in mm-stable, and should be backported together with it.Credit for this fix goes to David, thanks!
mm/huge_memory.c | 12 +++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-)mm/huge_memory.c +2
-1@@ -4558,7 +4558,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue;if (!split_folio (folio)) { did_split = true;if (underused)@@ -4569,11 +4569,13 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, next:if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/* mm/huge_memory.c static unsigned long deferred_split_scan(struct shrinker *shrink, · line 4579
Quoted 10 lines
mm/huge_memory.c +5
-4-* Only add back to the queue if folio is partially mapped. -* If thp_underused returns false, or if split_folio fails -* in the case it was underused, then consider it used and -* don't add it back to split_queue. +* Add back partially mapped folios, or underused folios +* that we could not lock this round. If thp_underused() +* returns false, or if split_folio() succeeds, or if +* split_folio() fails in the case it was underused, then +* consider it used and don't add it back to split_queue. */ Should the sentence
“Ifthp_underused()returns false, or ifsplit_folio()succeeds, or ifsplit_folio()fails in the case it was underused, then consider it used and don't add it back to split_queue.”
be moved to below label next?
Quoted 6 lines
Since “thp_underused() returns false” is describing “if (!underused) goto next”, “split_folio() succeeds” is describing “did_split == true in the if”, “split_folio() fails in the case it was underused” is describing “did_split == false and !folio_test_partially_mapped(folio) in the if”.
The first sentence matches the goto requeue for
folio_trylock().
Hi Andrew,
Can you apply the fixup below to move the comment? Lance told me he would be away for a while, so he could not send a fixup to move the comment.
Thanks.
From 6ebeca9f7215cb91905d3f49385dbbafce5a80c2 Mon Sep 17 00:00:00 2001
From: Zi Yan <[email protected]>
Date: Wed, 1 Apr 2026 18:52:43 -0400
Subject: [PATCH] move the comment.
--- mm/huge_memory.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-)
mm/huge_memory.c +8 -5
@@ -4567,15 +4567,18 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, }folio_unlock (folio); next: +/* +* If thp_underused() returns false, or if split_folio() +* succeeds, or if split_folio() fails in the case it was +* underused, then consider it used and don't add it back to +* split_queue. +*/ if (did_split || !folio_test_partially_mapped (folio))continue ; requeue:/* -* Add back partially mapped folios, or underused folios -* that we could not lock this round. If thp_underused() -* returns false, or if split_folio() succeeds, or if -* split_folio() fails in the case it was underused, then -* consider it used and don't add it back to split_queue. +* Add back partially mapped folios, or underused folios that +* we could not lock this round. */ fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {
Signature · 1 line
2.53.0
Quoted 41 lines
Otherwise, LGTM.
Acked-by Zi Yan <[email protected]>Quoted 36 lines
mm/huge_memory.c +0
-0fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {mm/migrate.c +9
-9@@ -1385,6 +1385,15 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (rc) goto out; +/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will @@ -1401,15 +1410,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:
Quoted 8 lines
Quoted 4 lines
mm/migrate.c +0
-0folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);Signature · 1 line
2.49.0Best Regards,
Yan, Zi
Best Regards,
Yan, Zi
Quoted 3 lines · On Wed, 01 Apr 2026 18:55:48 -0400 Zi Yan <[email protected]> wrote:
Can you apply the fixup below to move the comment? Lance told me he would be away for a while, so he could not send a fixup to move the comment.
Thanks. I folded that into Lance's base patch so here's the whole thing:
From: Lance Yang <[email protected]>
Subject: mm: fix deferred split queue races during migration
Date: Wed, 1 Apr 2026 21:10:32 +0800
migrate_folio_move() records the deferred split queue state from src and replays it on dst. Replaying it after remove_migration_ptes(src, dst, 0) makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN in deferred_split_folio().
Move the requeue before remove_migration_ptes() so dst is back on the deferred split queue before it becomes visible again.
Because migration still holds dst locked at that point, teach deferred_split_scan() to requeue a folio when folio_trylock() fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost from split_queue.
---
mm/huge_memory.c | 15 ++++++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-)
mm/huge_memory.c renamed from mm/huge_memory.c~mm-fix-deferred-split-queue-races-during-migration +10 -5
@@ -4542,7 +4542,7 @@ retry: goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue;if (!split_folio (folio)) { did_split = true;if (underused)
@@ -4551,13 +4551,18 @@ retry: }folio_unlock (folio); next: +/* +* If thp_underused() returns false, or if split_folio() +* succeeds, or if split_folio() fails in the case it was +* underused, then consider it used and don't add it back to +* split_queue. +*/ if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/* -* Only add back to the queue if folio is partially mapped. -* If thp_underused returns false, or if split_folio fails -* in the case it was underused, then consider it used and -* don't add it back to split_queue. +* Add back partially mapped folios, or underused folios that +* we could not lock this round. */ fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {
mm/migrate.c renamed from mm/migrate.c~mm-fix-deferred-split-queue-races-during-migration +9 -9
@@ -1384,6 +1384,15 @@ static int migrate_folio_move(free_folio goto out;/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); + +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will * automatically build up the correct dst->mlock_count for it.
@@ -1399,15 +1408,6 @@ static int migrate_folio_move(free_folioif (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);
_
Lance Yang
reviewerQuoted 9 lines · Andrew Morton
Quoted 3 lines · On Wed, 01 Apr 2026 18:55:48 -0400 Zi Yan <[email protected]> wrote:
Can you apply the fixup below to move the comment? Lance told me he would be away for a while, so he could not send a fixup to move the comment.
Thanks. I folded that into Lance's base patch so here's the whole thing:
Thank you all!
Lance
Quoted 128 lines
From: Lance Yang <[email protected]>
Subject: mm: fix deferred split queue races during migration
Date: Wed, 1 Apr 2026 21:10:32 +0800
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.[[email protected]: move the comment]Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Signed-off-by Lance Yang <[email protected]>Signed-off-by Zi Yan <[email protected]>Reported-by [email protected]Suggested-by David Hildenbrand (Arm) <[email protected]>Acked-by Zi Yan <[email protected]>Cc Alistair Popple <[email protected]>Cc Baolin Wang <[email protected]>Cc Barry Song <[email protected]>Cc Byungchul Park <[email protected]>Cc David Hildenbrand <[email protected]>Cc Deepanshu Kartikey <[email protected]>Cc Dev Jain <[email protected]>Cc Gregory Price <[email protected]>Cc "Huang, Ying" <[email protected]>Cc Joshua Hahn <[email protected]>Cc Lance Yang <[email protected]>Cc Liam Howlett <[email protected]>Cc Lorenzo Stoakes (Oracle) <[email protected]>Cc Matthew Brost <[email protected]>Cc Nico Pache <[email protected]>Cc Rakie Kim <[email protected]>Cc Ryan Roberts <[email protected]>Cc Wei Yang <[email protected]>Cc Ying Huang <[email protected]>Cc Usama Arif <[email protected]>Cc <[email protected]>Signed-off-by Andrew Morton <[email protected]>---
mm/huge_memory.c | 15 ++++++++++----- mm/migrate.c | 18 +++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-)mm/huge_memory.c renamed from mm/huge_memory.c~mm-fix-deferred-split-queue-races-during-migration +10
-5@@ -4542,7 +4542,7 @@ retry: goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue;if (!split_folio (folio)) { did_split = true;if (underused)@@ -4551,13 +4551,18 @@ retry: }folio_unlock (folio); next: +/* +* If thp_underused() returns false, or if split_folio() +* succeeds, or if split_folio() fails in the case it was +* underused, then consider it used and don't add it back to +* split_queue. +*/ if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/* -* Only add back to the queue if folio is partially mapped. -* If thp_underused returns false, or if split_folio fails -* in the case it was underused, then consider it used and -* don't add it back to split_queue. +* Add back partially mapped folios, or underused folios that +* we could not lock this round. */ fqueue =folio_split_queue_lock_irqsave (folio, &flags);if (list_empty (&folio->_deferred_list)) {mm/migrate.c renamed from mm/migrate.c~mm-fix-deferred-split-queue-races-during-migration +9
-9@@ -1384,6 +1384,15 @@ static int migrate_folio_move(free_folio goto out;/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); + +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will * automatically build up the correct dst->mlock_count for it. @@ -1399,15 +1408,6 @@ static int migrate_folio_move(free_folioif (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);_
Andrew Morton
maintainerQuoted 7 lines · On Wed, 1 Apr 2026 21:10:32 +0800 Lance Yang <[email protected]> wrote:
From: Lance Yang <[email protected]>
migrate_folio_move()records the deferred split queue state from src and replays it on dst. Replaying it afterremove_migration_ptes(src, dst, 0)makes dst visible before it is requeued, so a concurrent rmap-removal path can mark dst partially mapped and trip the WARN indeferred_split_folio().
Quoted 7 lines
Move the requeue before
remove_migration_ptes()so dst is back on the deferred split queue before it becomes visible again.Because migration still holds dst locked at that point, teach
deferred_split_scan()to requeue a folio whenfolio_trylock()fails. Otherwise a fully mapped underused folio can be dequeued by the shrinker and silently lost fromsplit_queue.
Thanks.
Quoted 7 lines
Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Cc <[email protected]>Suggested-by David Hildenbrand (Arm) <[email protected]>Signed-off-by Lance Yang <[email protected]>
I'll add this to mm-unstable with a plan to move it into the current mm-stable batch in a few days. So that 8a8ca142a488 and this follow-up fix stay in the same bundle.
Quoted 4 lines
[ Backport note ]
This patch is a follow-up fix for8a8ca142a488("mm: migrate: requeue destination folio on deferred split queue"), which is currently only in mm-stable, and should be backported together with it.
As far as I understand it, this should happen automatically. 8a8ca142a488 has cc:stable, this patch has Fixes:8a8ca142a488 and also cc:stable.
There's enough info here for the -stable people to figure it out!