Skip to contentSkip to conversation

mm/vmscan: fix swappiness=max and clean up per-node proactive reclaim

PATCH Superseded Ridong Chen 3 participants 18 messages Jul 18 – Jul 23

Subsystems (3)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

v2 cover mm/vmscan: fix swappiness=max and clean up per-node proactive reclaim

From: Ridong Chen <[email protected]>

Fixes and one cleanup.

Patch 1 fixes "swappiness=max": the anon-only test in get_scan_count() sat after the "cannot reclaim anon" check, so when no anon was reclaimable the request fell back to SCAN_FILE and evicted page cache instead.

Patch 2 fixes reclaim_store() collapsing every error into -EAGAIN, so callers can no longer tell an invalid argument from a busy interface; propagate the real error code, matching the memcg path.

Patch 3 drops the now-unused gfp_mask parameter from __node_reclaim().

Patch 4 fixes the same "swappiness=max" issue for MGLRU.

---

Changes since v1:
- Collected review tags for patches 1-3
- Added patch 4 to fix the same issue in the MGLRU path.

Ridong Chen (4):
mm/vmscan: fix anon-only reclaim evicting file pages when
swappiness=max
mm: vmscan: propagate real error code from per-node proactive reclaim
mm: vmscan: drop unused gfp_mask parameter from __node_reclaim() mm/mglru: fix anon-only reclaim evicting file pages when
swappiness=max

 mm/vmscan.c | 43 +++++++++++++++++++++++++++++--------------
 1 file changed, 29 insertions(+), 14 deletions(-)
Signature · 1 line
2.43.0
#1 lore raw

PATCH v2 1/4 mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

From: Ridong Chen <[email protected]>

As Qi mentioned [1], when swappiness=max (SWAPPINESS_ANON_ONLY) is set, the reclaim logic is expected to reclaim anonymous pages exclusively. However, due to the current ordering of checks in get_scan_count(), file pages may still be evicted if can_reclaim_anon_pages() returns false, which contradicts the semantics of SWAPPINESS_ANON_ONLY.

Reproducer in a cgroup holding 64M of file cache, with no swap configured:

Before (file cache is wrongly evicted):

    # cat memory.stat
    anon 196608
    file 67178496
    pgscan_proactive 0
    # echo "64M swappiness=max" > memory.reclaim
    # cat memory.stat
    anon 208896
    file 4096                 <- page cache evicted
    pgsteal_proactive 16400
    pgscan_proactive 16400

After (file cache is left intact):

    # cat memory.stat
    anon 200704
    file 67178496
    pgscan_proactive 0
    # echo "64M swappiness=max" > memory.reclaim
    -bash: echo: write error: Resource temporarily unavailable
    # cat memory.stat
    anon 208896
    file 67178496             <- page cache untouched
    pgsteal_proactive 0
    pgscan_proactive 0

Fix this by bailing out early when SWAPPINESS_ANON_ONLY is set and no anonymous pages are reclaimable, before falling back to file reclaim.

[1] https://lore.kernel.org/cgroups/[email protected]/

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
---
 mm/vmscan.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
mm/vmscan.c +11 -7
index 35c3bb15ae96..d6b383d96a0c 100644
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
 	enum scan_balance scan_balance;
 	enum lru_list lru;
 
+	/* Proactive reclaim initiated by userspace for anonymous memory only */
+	if (swappiness == SWAPPINESS_ANON_ONLY) {
+		WARN_ON_ONCE(!sc->proactive);
+		if (!can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
+			memset(nr, 0, sizeof(*nr) * NR_LRU_LISTS);
+			return;
+		}
+		scan_balance = SCAN_ANON;
+		goto out;
+	}
+
 	/* If we have no swap space, do not bother scanning anon folios. */
 	if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
 		scan_balance = SCAN_FILE;
@@ -2519,13 +2530,6 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
 		goto out;
 	}
 
-	/* Proactive reclaim initiated by userspace for anonymous memory only */
-	if (swappiness == SWAPPINESS_ANON_ONLY) {
-		WARN_ON_ONCE(!sc->proactive);
-		scan_balance = SCAN_ANON;
-		goto out;
-	}
-
 	/*
 	 * Do not apply any pressure balancing cleverness when the
 	 * system is close to OOM, scan both anon and file equally
Signature · 1 line
2.43.0
#8 lore raw

PATCH v2 2/4 mm: vmscan: propagate real error code from per-node proactive reclaim

In reply to Ridong Chen

From: Ridong Chen <[email protected]>

It has been observed that per-node proactive reclaim always returns -EAGAIN when any error occurs. As discussed in the mailing list [1], the interface should distinguish between cases where no reclaimable memory is left and where another entity is concurrently using the same interface. Propagate the real error code, consistent with how memcg proactive reclaim handles errors.

[1] https://lore.kernel.org/all/20250717235604.2atyx2aobwowpge3@offworld/T/#m3514718be82a31b05726a49da9b61fbfc69a589e

Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")
---
 mm/vmscan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/vmscan.c +1 -1
index d6b383d96a0c..cfe4556c96d7 100644
@@ -8017,7 +8017,7 @@ static ssize_t reclaim_store(struct device *dev,
 	int ret, nid = dev->id;
 
 	ret = user_proactive_reclaim((char *)buf, NULL, NODE_DATA(nid));
-	return ret ? -EAGAIN : count;
+	return ret ? ret : count;
 }
 
 static DEVICE_ATTR_WO(reclaim);
Signature · 1 line
2.43.0
#11 lore raw

PATCH v2 3/4 mm: vmscan: drop unused gfp_mask parameter from __node_reclaim()

In reply to Ridong Chen

From: Ridong Chen <[email protected]>

Commit 57972c78e678 ("mm/vmscan: make __node_reclaim() more generic") moved the scan_control construction out to the callers and passed the struct in by pointer. After that change every use of the gfp mask inside __node_reclaim() goes through sc->gfp_mask, leaving the gfp_mask parameter unused. Just remove the dead parameter and update the callers accordingly. No functional change.

---
 mm/vmscan.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)
mm/vmscan.c +4 -5
index cfe4556c96d7..e1ef6350a300 100644
@@ -7742,7 +7742,7 @@ static unsigned long node_pagecache_reclaimable(struct pglist_data *pgdat)
 /*
  * Try to free up some pages from this node through reclaim.
  */
-static unsigned long __node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask,
+static unsigned long __node_reclaim(struct pglist_data *pgdat,
 				    unsigned long nr_pages,
 				    struct scan_control *sc)
 {
@@ -7834,7 +7834,7 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order)
 	if (test_and_set_bit_lock(PGDAT_RECLAIM_LOCKED, &pgdat->flags))
 		return NODE_RECLAIM_NOSCAN;
 
-	ret = __node_reclaim(pgdat, gfp_mask, nr_pages, &sc) >= nr_pages;
+	ret = __node_reclaim(pgdat, nr_pages, &sc) >= nr_pages;
 	clear_bit_unlock(PGDAT_RECLAIM_LOCKED, &pgdat->flags);
 
 	if (ret)
@@ -7847,7 +7847,7 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order)
 
 #else
 
-static unsigned long __node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask,
+static unsigned long __node_reclaim(struct pglist_data *pgdat,
 				    unsigned long nr_pages,
 				    struct scan_control *sc)
 {
@@ -7949,8 +7949,7 @@ int user_proactive_reclaim(char *buf,
 						  &pgdat->flags))
 				return -EBUSY;
 
-			reclaimed = __node_reclaim(pgdat, gfp_mask,
-						   batch_size, &sc);
+			reclaimed = __node_reclaim(pgdat, batch_size, &sc);
 			clear_bit_unlock(PGDAT_RECLAIM_LOCKED, &pgdat->flags);
 		}
 
Signature · 1 line
2.43.0
#14 lore raw

PATCH v2 4/4 mm/mglru: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Ridong Chen

From: Ridong Chen <[email protected]>

The previous patch fixes this issue for the traditional LRU, which also exists in MGLRU [1]. Fix this by checking whether swappiness is SWAPPINESS_ANON_ONLY in get_swappiness() first, and returning 0 from get_nr_to_scan() when swappiness is SWAPPINESS_ANON_ONLY and anon pages cannot be reclaimed, to avoid useless work.

The test result:
Before fix:

  # cat /sys/kernel/mm/lru_gen/enabled
  0x0007
  # cat memory.stat
  anon 204800
  file 67108864
  ...
  pgsteal_proactive 0
  pgscan_proactive 0
  # echo "64M swappiness=max" > memory.reclaim
  # cat memory.stat
  anon 208896
  file 0
  ...
  pgsteal_proactive 16384
  pgscan_proactive 16384

After fix:

  # cat memory.stat
  anon 188416
  file 67215360
  kernel 1970176
  ...
  pgsteal_proactive 0
  pgscan_proactive 0
  # echo "64M swappiness=max" > memory.reclaim
  -bash: echo: write error: Resource temporarily unavailable
  # cat memory.stat
  anon 204800
  file 67215360
  ...
  pgsteal_proactive 0
  pgscan_proactive 0

[1] https://sashiko.dev/#/patchset/[email protected]

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
---
 mm/vmscan.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)
mm/vmscan.c +13 -1
index e1ef6350a300..240b04f0a21a 100644
@@ -2702,6 +2702,10 @@ static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
 {
 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
 	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+	int swappiness = sc_swappiness(sc, memcg);
+
+	if (swappiness == SWAPPINESS_ANON_ONLY)
+		return swappiness;
 
 	if (!sc->may_swap)
 		return 0;
@@ -2710,7 +2714,7 @@ static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
 	    mem_cgroup_get_nr_swap_pages(memcg) < MIN_LRU_BATCH)
 		return 0;
 
-	return sc_swappiness(sc, memcg);
+	return swappiness;
 }
 
 static int get_nr_gens(struct lruvec *lruvec, int type)
@@ -4915,6 +4919,14 @@ static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
 			   struct mem_cgroup *memcg, int swappiness)
 {
 	unsigned long nr_to_scan, evictable;
+	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+
+	/* Proactive reclaim: anon-only requested but anon is not reclaimable */
+	if (swappiness == SWAPPINESS_ANON_ONLY &&
+	    !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
+		WARN_ON_ONCE(!sc->proactive);
+		return 0;
+	}
 
 	evictable = lruvec_evictable_size(lruvec, swappiness);
 
Signature · 1 line
2.43.0
reviewer Reviewed-by on v2 3/4
#12 lore raw

Re: [PATCH v2 3/4] mm: vmscan: drop unused gfp_mask parameter from __node_reclaim()

In reply to Ridong Chen
Quoted 10 lines · Ridong Chen

From: Ridong Chen <[email protected]>

Commit 57972c78e678 ("mm/vmscan: make __node_reclaim() more generic") moved the scan_control construction out to the callers and passed the struct in by pointer. After that change every use of the gfp mask inside __node_reclaim() goes through sc->gfp_mask, leaving the gfp_mask parameter unused. Just remove the dead parameter and update the callers accordingly. No functional change.

Quoted 4 lines
reviewer Reviewed-by on v2 1/4
#2 lore raw

Re: [PATCH v2 1/4] mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Ridong Chen
Quoted 39 lines · Ridong Chen

From: Ridong Chen <[email protected]>

As Qi mentioned [1], when swappiness=max (SWAPPINESS_ANON_ONLY) is set, the reclaim logic is expected to reclaim anonymous pages exclusively. However, due to the current ordering of checks in get_scan_count(), file pages may still be evicted if can_reclaim_anon_pages() returns false, which contradicts the semantics of SWAPPINESS_ANON_ONLY.

Reproducer in a cgroup holding 64M of file cache, with no swap configured:

Before (file cache is wrongly evicted):

    # cat memory.stat
    anon 196608
    file 67178496
    pgscan_proactive 0
    # echo "64M swappiness=max" > memory.reclaim
    # cat memory.stat
    anon 208896
    file 4096                 <- page cache evicted
    pgsteal_proactive 16400
    pgscan_proactive 16400

After (file cache is left intact):

    # cat memory.stat
    anon 200704
    file 67178496
    pgscan_proactive 0
    # echo "64M swappiness=max" > memory.reclaim
    -bash: echo: write error: Resource temporarily unavailable
    # cat memory.stat
    anon 208896
    file 67178496             <- page cache untouched
    pgsteal_proactive 0
    pgscan_proactive 0

Fix this by bailing out early when SWAPPINESS_ANON_ONLY is set and no anonymous pages are reclaimable, before falling back to file reclaim.

Quoted 8 lines

[1] https://lore.kernel.org/cgroups/[email protected]/

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
Quoted 9 lines
---
 mm/vmscan.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
mm/vmscan.c +0 -0
index 35c3bb15ae96..d6b383d96a0c 100644

mm/vmscan.c static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc, · line 2506

Quoted 6 lines
mm/vmscan.c +3 -0
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
        enum scan_balance scan_balance;
        enum lru_list lru;
 
+       /* Proactive reclaim initiated by userspace for anonymous memory only */
+       if (swappiness == SWAPPINESS_ANON_ONLY) {
+               WARN_ON_ONCE(!sc->proactive);

I feel it's a bit odd that the WARN_ON_ONCE() is placed here. Maybe adding a short comment would make the intent clearer. For example:
"SWAPPINESS_ANON_ONLY is only allowed for proactive reclaim."

I'm not sure whether it would be better to issue this warning earlier, for example in try_to_free_mem_cgroup_pages(). That said, I don't feel strongly about this.

reviewer
#15 lore raw

Re: [PATCH v2 4/4] mm/mglru: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Ridong Chen
Quoted 8 lines · Ridong Chen

From: Ridong Chen <[email protected]>

The previous patch fixes this issue for the traditional LRU, which also exists in MGLRU [1]. Fix this by checking whether swappiness is SWAPPINESS_ANON_ONLY in get_swappiness() first, and returning 0 from get_nr_to_scan() when swappiness is SWAPPINESS_ANON_ONLY and anon pages cannot be reclaimed, to avoid useless work.

Is this intended to avoid unnecessary work, or to prevent file folios from being reclaimed incorrectly?

Quoted 48 lines

The test result:
Before fix:

  # cat /sys/kernel/mm/lru_gen/enabled
  0x0007
  # cat memory.stat
  anon 204800
  file 67108864
  ...
  pgsteal_proactive 0
  pgscan_proactive 0
  # echo "64M swappiness=max" > memory.reclaim
  # cat memory.stat
  anon 208896
  file 0
  ...
  pgsteal_proactive 16384
  pgscan_proactive 16384

After fix:

  # cat memory.stat
  anon 188416
  file 67215360
  kernel 1970176
  ...
  pgsteal_proactive 0
  pgscan_proactive 0
  # echo "64M swappiness=max" > memory.reclaim
  -bash: echo: write error: Resource temporarily unavailable
  # cat memory.stat
  anon 204800
  file 67215360
  ...
  pgsteal_proactive 0
  pgscan_proactive 0

[1] https://sashiko.dev/#/patchset/[email protected]

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
---
 mm/vmscan.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

[...]

mm/vmscan.c static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc, · line 4927

Quoted 11 lines

static int get_nr_gens(struct lruvec *lruvec, int type)

mm/vmscan.c +6 -0
@@ -4915,6 +4919,14 @@ static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
                           struct mem_cgroup *memcg, int swappiness)
 {
        unsigned long nr_to_scan, evictable;
+       struct pglist_data *pgdat = lruvec_pgdat(lruvec);
+
+       /* Proactive reclaim: anon-only requested but anon is not reclaimable */
+       if (swappiness == SWAPPINESS_ANON_ONLY &&
+           !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
+               WARN_ON_ONCE(!sc->proactive);

Similar to the previous patch, maybe it would help to add a bit more comment here?

Thanks
Barry

reviewer Reviewed-by on v2 1/4
#7 lore raw

Re: [PATCH v2 1/4] mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Ridong Chen
Quoted 46 lines · Ridong Chen

From: Ridong Chen <[email protected]>

As Qi mentioned [1], when swappiness=max (SWAPPINESS_ANON_ONLY) is set, the reclaim logic is expected to reclaim anonymous pages exclusively. However, due to the current ordering of checks in get_scan_count(), file pages may still be evicted if can_reclaim_anon_pages() returns false, which contradicts the semantics of SWAPPINESS_ANON_ONLY.

Reproducer in a cgroup holding 64M of file cache, with no swap configured:

Before (file cache is wrongly evicted):

     # cat memory.stat
     anon 196608
     file 67178496
     pgscan_proactive 0
     # echo "64M swappiness=max" > memory.reclaim
     # cat memory.stat
     anon 208896
     file 4096                 <- page cache evicted
     pgsteal_proactive 16400
     pgscan_proactive 16400

After (file cache is left intact):

     # cat memory.stat
     anon 200704
     file 67178496
     pgscan_proactive 0
     # echo "64M swappiness=max" > memory.reclaim
     -bash: echo: write error: Resource temporarily unavailable
     # cat memory.stat
     anon 208896
     file 67178496             <- page cache untouched
     pgsteal_proactive 0
     pgscan_proactive 0

Fix this by bailing out early when SWAPPINESS_ANON_ONLY is set and no anonymous pages are reclaimable, before falling back to file reclaim.

[1] https://lore.kernel.org/cgroups/[email protected]/

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
Quoted 3 lines
---
  mm/vmscan.c | 18 +++++++++++-------
  1 file changed, 11 insertions(+), 7 deletions(-)

Thanks for the fix!

Quoted 37 lines
mm/vmscan.c +11 -7
index 35c3bb15ae96..d6b383d96a0c 100644
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
  	enum scan_balance scan_balance;
  	enum lru_list lru;
 
+	/* Proactive reclaim initiated by userspace for anonymous memory only */
+	if (swappiness == SWAPPINESS_ANON_ONLY) {
+		WARN_ON_ONCE(!sc->proactive);
+		if (!can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
+			memset(nr, 0, sizeof(*nr) * NR_LRU_LISTS);
+			return;
+		}
+		scan_balance = SCAN_ANON;
+		goto out;
+	}
+
  	/* If we have no swap space, do not bother scanning anon folios. */
  	if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
  		scan_balance = SCAN_FILE;
@@ -2519,13 +2530,6 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
  		goto out;
  	}
 
-	/* Proactive reclaim initiated by userspace for anonymous memory only */
-	if (swappiness == SWAPPINESS_ANON_ONLY) {
-		WARN_ON_ONCE(!sc->proactive);
-		scan_balance = SCAN_ANON;
-		goto out;
-	}
-
  	/*
  	 * Do not apply any pressure balancing cleverness when the
  	 * system is close to OOM, scan both anon and file equally
reviewer Reviewed-by on v2 2/4
#9 lore raw

Re: [PATCH v2 2/4] mm: vmscan: propagate real error code from per-node proactive reclaim

In reply to Ridong Chen
Quoted 25 lines · Ridong Chen

From: Ridong Chen <[email protected]>

It has been observed that per-node proactive reclaim always returns -EAGAIN when any error occurs. As discussed in the mailing list [1], the interface should distinguish between cases where no reclaimable memory is left and where another entity is concurrently using the same interface. Propagate the real error code, consistent with how memcg proactive reclaim handles errors.

[1] https://lore.kernel.org/all/20250717235604.2atyx2aobwowpge3@offworld/T/#m3514718be82a31b05726a49da9b61fbfc69a589e

Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")
---
  mm/vmscan.c | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
mm/vmscan.c +0 -0
index d6b383d96a0c..cfe4556c96d7 100644

mm/vmscan.c static ssize_t reclaim_store(struct device *dev, · line 8023

Quoted 8 lines
mm/vmscan.c +1 -1
@@ -8017,7 +8017,7 @@ static ssize_t reclaim_store(struct device *dev,
  	int ret, nid = dev->id;
 
  	ret = user_proactive_reclaim((char *)buf, NULL, NODE_DATA(nid));
-	return ret ? -EAGAIN : count;
+	return ret ? ret : count;
  }
 
  static DEVICE_ATTR_WO(reclaim);

Nice catch!

Thanks,
Qi

reviewer Reviewed-by on v2 3/4
#13 lore raw

Re: [PATCH v2 3/4] mm: vmscan: drop unused gfp_mask parameter from __node_reclaim()

In reply to Ridong Chen
Quoted 9 lines · Ridong Chen

From: Ridong Chen <[email protected]>

Commit 57972c78e678 ("mm/vmscan: make __node_reclaim() more generic") moved the scan_control construction out to the callers and passed the struct in by pointer. After that change every use of the gfp mask inside __node_reclaim() goes through sc->gfp_mask, leaving the gfp_mask parameter unused. Just remove the dead parameter and update the callers accordingly. No functional change.

Quoted 8 lines
---
  mm/vmscan.c | 9 ++++-----
  1 file changed, 4 insertions(+), 5 deletions(-)

Thanks,
Qi

reviewer Acked-by
#16 lore raw

Re: [PATCH v2 4/4] mm/mglru: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Barry Song
Quoted 4 lines · Barry Song
Quoted 3 lines · Ridong Chen

From: Ridong Chen <[email protected]>

Quoted 8 lines
Quoted 5 lines

The previous patch fixes this issue for the traditional LRU, which also exists in MGLRU [1]. Fix this by checking whether swappiness is SWAPPINESS_ANON_ONLY in get_swappiness() first, and returning 0 from get_nr_to_scan() when swappiness is SWAPPINESS_ANON_ONLY and anon pages cannot be reclaimed, to avoid useless work.

Is this intended to avoid unnecessary work, or to prevent file folios from being reclaimed incorrectly?

Based on the test results below, it seems primarily intended to prevent file pages from being mistakenly reclaimed. Also, bailing out early in the SWAPPINESS_ANON_ONLY + !can_reclaim_anon_pages case is to avoid unnecessary work.

Hi Ridong, perhaps the commit message could be clearer. ;)

Quoted 41 lines
Quoted 41 lines

The test result:
Before fix:

   # cat /sys/kernel/mm/lru_gen/enabled
   0x0007
   # cat memory.stat
   anon 204800
   file 67108864
   ...
   pgsteal_proactive 0
   pgscan_proactive 0
   # echo "64M swappiness=max" > memory.reclaim
   # cat memory.stat
   anon 208896
   file 0
   ...
   pgsteal_proactive 16384
   pgscan_proactive 16384

After fix:

   # cat memory.stat
   anon 188416
   file 67215360
   kernel 1970176
   ...
   pgsteal_proactive 0
   pgscan_proactive 0
   # echo "64M swappiness=max" > memory.reclaim
   -bash: echo: write error: Resource temporarily unavailable
   # cat memory.stat
   anon 204800
   file 67215360
   ...
   pgsteal_proactive 0
   pgscan_proactive 0
Quoted 8 lines
Quoted 8 lines

[1] https://sashiko.dev/#/patchset/[email protected]

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
---
  mm/vmscan.c | 14 +++++++++++++-
  1 file changed, 13 insertions(+), 1 deletion(-)

Overall, this looks good to me.

Thanks,
Qi

#3 lore raw

Re: [PATCH v2 1/4] mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Barry Song
Quoted 63 lines · Barry Song
Quoted 39 lines · Ridong Chen

From: Ridong Chen <[email protected]>

As Qi mentioned [1], when swappiness=max (SWAPPINESS_ANON_ONLY) is set, the reclaim logic is expected to reclaim anonymous pages exclusively. However, due to the current ordering of checks in get_scan_count(), file pages may still be evicted if can_reclaim_anon_pages() returns false, which contradicts the semantics of SWAPPINESS_ANON_ONLY.

Reproducer in a cgroup holding 64M of file cache, with no swap configured:

Before (file cache is wrongly evicted):

     # cat memory.stat
     anon 196608
     file 67178496
     pgscan_proactive 0
     # echo "64M swappiness=max" > memory.reclaim
     # cat memory.stat
     anon 208896
     file 4096                 <- page cache evicted
     pgsteal_proactive 16400
     pgscan_proactive 16400

After (file cache is left intact):

     # cat memory.stat
     anon 200704
     file 67178496
     pgscan_proactive 0
     # echo "64M swappiness=max" > memory.reclaim
     -bash: echo: write error: Resource temporarily unavailable
     # cat memory.stat
     anon 208896
     file 67178496             <- page cache untouched
     pgsteal_proactive 0
     pgscan_proactive 0

Fix this by bailing out early when SWAPPINESS_ANON_ONLY is set and no anonymous pages are reclaimable, before falling back to file reclaim.

Quoted 8 lines

[1] https://lore.kernel.org/cgroups/[email protected]/

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")
Quoted 12 lines
---
  mm/vmscan.c | 18 +++++++++++-------
  1 file changed, 11 insertions(+), 7 deletions(-)
mm/vmscan.c +0 -0
index 35c3bb15ae96..d6b383d96a0c 100644
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
         enum scan_balance scan_balance;
         enum lru_list lru;
 
Quoted 7 lines

mm/vmscan.c static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc, · line 2506

Quoted 3 lines
mm/vmscan.c +3 -0
+       /* Proactive reclaim initiated by userspace for anonymous memory only */
+       if (swappiness == SWAPPINESS_ANON_ONLY) {
+               WARN_ON_ONCE(!sc->proactive);

I feel it's a bit odd that the WARN_ON_ONCE() is placed here. Maybe adding a short comment would make the intent clearer. For example:
"SWAPPINESS_ANON_ONLY is only allowed for proactive reclaim."

Hi Barry,

I didn't change the original logic in this patch. I agree that adding a comment would help clarify the intent.

I'll include that in the next version.

Quoted 3 lines

I'm not sure whether it would be better to issue this warning earlier, for example in try_to_free_mem_cgroup_pages(). That said, I don't feel strongly about this.

Since patch 4 will address the MGLRU issue and could also include this warning, I think that triggering the warning earlier makes sense and would be more general. Would it be acceptable to add a separate patch (e.g., patch 5) for moving the warning? That way, each patch stays focused on a single logical change.

Signature · 2 lines
Best regards
Ridong
#17 lore raw

Re: [PATCH v2 4/4] mm/mglru: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Qi Zheng
Quoted 16 lines · Qi Zheng
Quoted 4 lines · Barry Song
Quoted 3 lines · Ridong Chen

From: Ridong Chen <[email protected]>

Quoted 8 lines
Quoted 5 lines

The previous patch fixes this issue for the traditional LRU, which also exists in MGLRU [1]. Fix this by checking whether swappiness is SWAPPINESS_ANON_ONLY in get_swappiness() first, and returning 0 from get_nr_to_scan() when swappiness is SWAPPINESS_ANON_ONLY and anon pages cannot be reclaimed, to avoid useless work.

Is this intended to avoid unnecessary work, or to prevent file folios from being reclaimed incorrectly?

Quoted 7 lines

Based on the test results below, it seems primarily intended to prevent file pages from being mistakenly reclaimed. Also, bailing out early in the SWAPPINESS_ANON_ONLY + !can_reclaim_anon_pages case is to avoid unnecessary work.

Hi Ridong, perhaps the commit message could be clearer. ;)

Thanks for the feedback. Let me clarify:

When SWAPPINESS_ANON_ONLY is set, get_swappiness() returns the swappiness value, and get_type_to_scan() can always return LRU_GEN_ANON. So if anonymous pages cannot be reclaimed, we bail out early to avoid unnecessary work. In that case, get_nr_to_scan() just returns 0.

If we do not return early in get_nr_to_scan() under the SWAPPINESS_ANON_ONLY + !can_reclaim_anon_pages condition, then in isolate_folios(), it may fall back to scanning file pages, but this only happens when scanned = 0, which is not the usual case.

```
shrink_one
try_to_shrink_lruvec
  get_swappiness // return SWAPPINESS_ANON_ONLY if swappiness=max
  get_nr_to_scan // return 0 if anon can't be reclaimed, skip
  evict_folios
    isolate_folios // !scanned falling back to anyther type if !scanned
```

I'll update the commit message to make this clearer in the next version.

Quoted 58 lines
Quoted 41 lines
Quoted 41 lines

The test result:
Before fix:

   # cat /sys/kernel/mm/lru_gen/enabled
   0x0007
   # cat memory.stat
   anon 204800
   file 67108864
   ...
   pgsteal_proactive 0
   pgscan_proactive 0
   # echo "64M swappiness=max" > memory.reclaim
   # cat memory.stat
   anon 208896
   file 0
   ...
   pgsteal_proactive 16384
   pgscan_proactive 16384

After fix:

   # cat memory.stat
   anon 188416
   file 67215360
   kernel 1970176
   ...
   pgsteal_proactive 0
   pgscan_proactive 0
   # echo "64M swappiness=max" > memory.reclaim
   -bash: echo: write error: Resource temporarily unavailable
   # cat memory.stat
   anon 204800
   file 67215360
   ...
   pgsteal_proactive 0
   pgscan_proactive 0
Quoted 8 lines
Quoted 8 lines

[1] https://sashiko.dev/#/patchset/20260717113300.214717-1- [email protected]

Fixes 68a1436bde00 ("mm: add swappiness=max arg to memory.reclaim for only anon reclaim")

---
  mm/vmscan.c | 14 +++++++++++++-

  1 file changed, 13 insertions(+), 1 deletion(-)

Overall, this looks good to me.

Thanks,
Qi

Signature · 2 lines
Best regards
Ridong
reviewer Reviewed-by on v2 2/4
#10 lore raw

Re: [PATCH v2 2/4] mm: vmscan: propagate real error code from per-node proactive reclaim

In reply to Ridong Chen
Quoted 10 lines · Ridong Chen

From: Ridong Chen <[email protected]>

It has been observed that per-node proactive reclaim always returns -EAGAIN when any error occurs. As discussed in the mailing list [1], the interface should distinguish between cases where no reclaimable memory is left and where another entity is concurrently using the same interface. Propagate the real error code, consistent with how memcg proactive reclaim handles errors.

Quoted 7 lines

[1] https://lore.kernel.org/all/20250717235604.2atyx2aobwowpge3@offworld/T/#m3514718be82a31b05726a49da9b61fbfc69a589e

Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")
Quoted 3 lines
---
 mm/vmscan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
reviewer
#4 lore raw

Re: [PATCH v2 1/4] mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

In reply to Ridong Chen

On Mon, Jul 20, 2026 at 12:43 PM Ridong Chen <[email protected]> wrote:
[...]

Quoted 25 lines
Quoted 6 lines
Quoted 6 lines
mm/vmscan.c +0 -0
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
         enum scan_balance scan_balance;
         enum lru_list lru;
 
Quoted 7 lines

mm/vmscan.c static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc, · line 2506

Quoted 3 lines
mm/vmscan.c +3 -0
+       /* Proactive reclaim initiated by userspace for anonymous memory only */
+       if (swappiness == SWAPPINESS_ANON_ONLY) {
+               WARN_ON_ONCE(!sc->proactive);

I feel it's a bit odd that the WARN_ON_ONCE() is placed here. Maybe adding a short comment would make the intent clearer. For example:
"SWAPPINESS_ANON_ONLY is only allowed for proactive reclaim."

Hi Barry,

I didn't change the original logic in this patch. I agree that adding a comment would help clarify the intent.

I'll include that in the next version.

Quoted 3 lines

I'm not sure whether it would be better to issue this warning earlier, for example in try_to_free_mem_cgroup_pages(). That said, I don't feel strongly about this.

Quoted 5 lines

Since patch 4 will address the MGLRU issue and could also include this warning, I think that triggering the warning earlier makes sense and would be more general. Would it be acceptable to add a separate patch (e.g., patch 5) for moving the warning? That way, each patch stays focused on a single logical change.

I'm not quite sure where the best place would be to move it earlier. We already have two places that perform proactive reclaim:
try_to_free_mem_cgroup_pages() and __node_reclaim(). There are also other reclaim paths that are not proactive. Since they all eventually call shrink_node(), would it make more sense to handle it there?

If we can't find a suitable common path, I guess simply adding some comments to the current patches would be good enough.

Thanks
Barry

#5 lore raw

Re: [PATCH v2 1/4] mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

Quoted 34 lines · Barry Song

On Mon, Jul 20, 2026 at 12:43 PM Ridong Chen <[email protected]> wrote:
[...]

Quoted 25 lines
Quoted 6 lines
Quoted 6 lines
mm/vmscan.c +0 -0
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
          enum scan_balance scan_balance;
          enum lru_list lru;
 
Quoted 7 lines

mm/vmscan.c static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc, · line 2506

Quoted 3 lines
mm/vmscan.c +3 -0
+       /* Proactive reclaim initiated by userspace for anonymous memory only */
+       if (swappiness == SWAPPINESS_ANON_ONLY) {
+               WARN_ON_ONCE(!sc->proactive);

I feel it's a bit odd that the WARN_ON_ONCE() is placed here. Maybe adding a short comment would make the intent clearer. For example:
"SWAPPINESS_ANON_ONLY is only allowed for proactive reclaim."

Hi Barry,

I didn't change the original logic in this patch. I agree that adding a comment would help clarify the intent.

I'll include that in the next version.

Quoted 3 lines

I'm not sure whether it would be better to issue this warning earlier, for example in try_to_free_mem_cgroup_pages(). That said, I don't feel strongly about this.

Quoted 5 lines

Since patch 4 will address the MGLRU issue and could also include this warning, I think that triggering the warning earlier makes sense and would be more general. Would it be acceptable to add a separate patch (e.g., patch 5) for moving the warning? That way, each patch stays focused on a single logical change.

Quoted 9 lines

I'm not quite sure where the best place would be to move it earlier. We already have two places that perform proactive reclaim:
try_to_free_mem_cgroup_pages() and __node_reclaim(). There are also other reclaim paths that are not proactive. Since they all eventually call shrink_node(), would it make more sense to handle it there?

If we can't find a suitable common path, I guess simply adding some comments to the current patches would be good enough.

Eh, shrink_node handles MGLRU global reclaim via a special branch, so we'd have to add the warning at the beginning of shrink_node, but that feels a bit jarring. Let's just go with adding some comments instead.

Signature · 2 lines
Best regards
Ridong
reviewer
#6 lore raw

Re: [PATCH v2 1/4] mm/vmscan: fix anon-only reclaim evicting file pages when swappiness=max

Quoted 44 lines · Ridong Chen
Quoted 40 lines · Barry Song

On Mon, Jul 20, 2026 at 12:43 PM Ridong Chen <[email protected]> wrote:
[...]

Quoted 25 lines
Quoted 6 lines
Quoted 6 lines
mm/vmscan.c +0 -0
@@ -2501,6 +2501,17 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
          enum scan_balance scan_balance;
          enum lru_list lru;
 
Quoted 7 lines

mm/vmscan.c static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc, · line 2506

Quoted 3 lines
mm/vmscan.c +3 -0
+       /* Proactive reclaim initiated by userspace for anonymous memory only */
+       if (swappiness == SWAPPINESS_ANON_ONLY) {
+               WARN_ON_ONCE(!sc->proactive);

I feel it's a bit odd that the WARN_ON_ONCE() is placed here. Maybe adding a short comment would make the intent clearer. For example:
"SWAPPINESS_ANON_ONLY is only allowed for proactive reclaim."

Hi Barry,

I didn't change the original logic in this patch. I agree that adding a comment would help clarify the intent.

I'll include that in the next version.

Quoted 3 lines

I'm not sure whether it would be better to issue this warning earlier, for example in try_to_free_mem_cgroup_pages(). That said, I don't feel strongly about this.

Quoted 5 lines

Since patch 4 will address the MGLRU issue and could also include this warning, I think that triggering the warning earlier makes sense and would be more general. Would it be acceptable to add a separate patch (e.g., patch 5) for moving the warning? That way, each patch stays focused on a single logical change.

I'm not quite sure where the best place would be to move it earlier. We already have two places that perform proactive reclaim:
try_to_free_mem_cgroup_pages() and __node_reclaim(). There are also other reclaim paths that are not proactive. Since they all eventually call shrink_node(), would it make more sense to handle it there?

Quoted 6 lines
Quoted 3 lines

If we can't find a suitable common path, I guess simply adding some comments to the current patches would be good enough.

Eh, shrink_node handles MGLRU global reclaim via a special branch, so we'd have to add the warning at the beginning of shrink_node, but that feels a bit jarring. Let's just go with adding some comments instead.

Yep. I also failed to find the best place to add the WARN_ON earlier in a common path shared by both MGLRU and the active/inactive LRU. Maybe just adding some more comments would help clarify the intent :-)

Best Regards
Barry