Re: [RFC PATCH v3 4/6] mm: mglru: improve scan_folios() exhaustion detection

Barry Song <[email protected]>
Newsgroups org.kvack.linux-mm,org.kernel.vger.linux-kernel
Message-ID <CAGsJ_4y_v1PNyAEg1Xj8FciPNSkaoFifGnxM34QuUWFWMup79Q@mail.gmail.com>
On Fri, Jul 31, 2026 at 4:39 PM Barry Song (Xiaomi) <[email protected]> wrote:
>
> Commit 16b475d2ac3c ("mm/mglru: avoid reclaim type fall back when
> isolation makes no progress") uses scanned == 0 to determine
> whether scan_folios() has exhausted a reclaim type. However,
> this is not always sufficient. It is possible for scanned > 0,
> while the oldest reclaimable generation is exhausted after the
> first scan_folios() call.
>
> Add an exhausted output argument to scan_folios() so it can
> explicitly report whether the reclaimable lists for the current
> type have been exhausted.
>
> Signed-off-by: Barry Song (Xiaomi) <[email protected]>
> ---
>  mm/vmscan.c | 18 +++++++++++++-----
>  1 file changed, 13 insertions(+), 5 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 0038f33aa318..2e7fef6975b6 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -4707,7 +4707,8 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca
>
>  static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
>                        struct scan_control *sc, int type, int tier,
> -                      struct list_head *list, int *isolatedp)
> +                      struct list_head *list, int *isolatedp,
> +                      bool *exhausted)
>  {
>         int i;
>         int gen;
> @@ -4723,8 +4724,10 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
>         VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
>         VM_WARN_ON_ONCE(!list_empty(list));
>
> -       if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
> +       if (get_nr_gens(lruvec, type) == MIN_NR_GENS) {
> +               *exhausted = true;
>                 return 0;
> +       }
>
>  next_gen:
>         gen = lru_gen_from_seq(min_seq);
> @@ -4785,6 +4788,11 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
>                                 scanned, skipped, isolated,
>                                 type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
>
> +       /*
> +        * This scan exhausted the reclaimable lists before reaching the
> +        * scan target or accumulating enough isolated folios.
> +        */
> +       *exhausted = remaining > 0 && isolated < MIN_LRU_BATCH;

https://sashiko.dev/#/patchset/20260731083843.37811-1-baohua%40kernel.org

"Does this logic properly account for early loop termination when large folios
are skipped?
Looking at the scanning loop earlier in scan_folios():
} else {
      list_move(&folio->lru, &moved);
      skipped_zone += delta;
}
if (!--remaining || max(isolated, skipped_zone) >= MIN_LRU_BATCH)
      break;
If a large folio is skipped, skipped_zone can instantly reach or exceed
MIN_LRU_BATCH. This causes the loop to break early while remaining is still
greater than zero.
Because isolated would also be less than MIN_LRU_BATCH at this point, could
this incorrectly evaluate the exhausted flag to true? This appears like it
would signal to isolate_folios() that the current reclaim type is completely
empty, triggering a premature fallback to the other reclaim type and breaking
the cost-based reclaim balancing logic."

This is a good point. We could break out of a zone once
skipped_zone >= MIN_LRU_BATCH, provided the zone still
contains folios.

It seems I can fix this by checking whether we exited
early after reaching the scan target due to any reason:

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 902d85afc5f6..c0e14048ab7f 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4720,6 +4720,7 @@ static int scan_folios(unsigned long nr_to_scan,
struct lruvec *lruvec,
        unsigned long remaining = nr_to_scan;
        struct lru_gen_folio *lrugen = &lruvec->lrugen;
        unsigned long min_seq = lrugen->min_seq[type];
+       bool reached_scan_target = false;

        VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
        VM_WARN_ON_ONCE(!list_empty(list));
@@ -4758,8 +4759,10 @@ static int scan_folios(unsigned long
nr_to_scan, struct lruvec *lruvec,
                                skipped_zone += delta;
                        }

-                       if (!--remaining || max(isolated,
skipped_zone) >= MIN_LRU_BATCH)
+                       if (!--remaining || max(isolated,
skipped_zone) >= MIN_LRU_BATCH) {
+                               reached_scan_target = true;
                                break;
+                       }
                }

                if (skipped_zone) {
@@ -4768,8 +4771,10 @@ static int scan_folios(unsigned long
nr_to_scan, struct lruvec *lruvec,
                        skipped += skipped_zone;
                }

-               if (!remaining || isolated >= MIN_LRU_BATCH)
+               if (!remaining || isolated >= MIN_LRU_BATCH) {
+                       reached_scan_target = true;
                        break;
+               }
        }

        /*
@@ -4788,11 +4793,7 @@ static int scan_folios(unsigned long
nr_to_scan, struct lruvec *lruvec,
                                scanned, skipped, isolated,
                                type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);

-       /*
-        * This scan exhausted the reclaimable lists before reaching the
-        * scan target or accumulating enough isolated folios.
-        */
-       *exhausted = remaining > 0 && isolated < MIN_LRU_BATCH;
+       *exhausted = !reached_scan_target;
        *isolatedp = isolated;
        return scanned;
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.