Skip to contentSkip to conversation

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

PATCH Superseded Ridong 5 participants 15 messages Jul 17 – Jul 18

Subsystems (3)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Ridong

author
#0 lore raw

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

From: Ridong Chen <[email protected]>

Two 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().

Ridong Chen (3):
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/vmscan.c | 29 ++++++++++++++++-------------
 1 file changed, 16 insertions(+), 13 deletions(-)
Signature · 1 line
2.34.1
author
#1 lore raw

PATCH 1/3 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 4357a44ee876..098adc599720 100644
@@ -2492,6 +2492,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;
@@ -2510,13 +2521,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.34.1
author
#5 lore raw

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

In reply to Ridong

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 098adc599720..45fbbefe0906 100644
@@ -8008,7 +8008,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.34.1
author
#9 lore raw

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

In reply to Ridong

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 45fbbefe0906..551c47a6cf6f 100644
@@ -7729,7 +7729,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)
 {
@@ -7821,7 +7821,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)
@@ -7834,7 +7834,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)
 {
@@ -7940,8 +7940,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.34.1
maintainer Acked-by on v1 1/3
#2 lore raw

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

In reply to Ridong
Quoted 35 lines · Ridong

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
Quoted 8 lines

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")
maintainer Acked-by on v1 2/3
#6 lore raw

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

In reply to Ridong
Quoted 9 lines · Ridong

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 4 lines

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

Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")
maintainer Acked-by on v1 3/3
#10 lore raw

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

In reply to Ridong
Quoted 10 lines · Ridong

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.

Reviewed-by on v1 1/3
#3 lore raw

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

In reply to Ridong
Quoted 39 lines · Ridong

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
Quoted 8 lines

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")
Reviewed-by on v1 2/3
#7 lore raw

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

In reply to Ridong
Quoted 13 lines · Ridong

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 4 lines

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

Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")
Quoted 9 lines

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

1 file changed, 1 insertion(+), 1 deletion(-)
mm/vmscan.c +0 -0
index 098adc599720..45fbbefe0906 100644

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

Quoted 5 lines
mm/vmscan.c +1 -1
@@ -8008,7 +8008,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;

I usually use "? :" to omit ret. Just to be clear, I'm only sharing this as a personal preference. I'm not suggesting that you actually need to change it.

Quoted 6 lines
mm/vmscan.c +0 -0
 }
 
 static DEVICE_ATTR_WO(reclaim);
Signature · 1 line
2.34.1
Reviewed-by on v1 3/3
#11 lore raw

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

In reply to Ridong
Quoted 14 lines · Ridong

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.

reviewer Acked-by on v1 1/3
#4 lore raw

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

In reply to Ridong
Quoted 35 lines · Ridong

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
Quoted 8 lines

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")
reviewer Acked-by on v1 2/3
#8 lore raw

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

In reply to Ridong
Quoted 9 lines · Ridong

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 4 lines

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

Fixes b980077899ea ("mm: introduce per-node proactive reclaim interface")
reviewer Acked-by on v1 3/3
#12 lore raw

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

In reply to Ridong
Quoted 10 lines · Ridong

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.

maintainer
#13 lore raw
In reply to Ridong
Quoted 9 lines · On Fri, 17 Jul 2026 19:32:57 +0800 Ridong <[email protected]> wrote:

From: Ridong Chen <[email protected]>

Two 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.

Quoted 5 lines

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().

Thanks. Sashiko want to know whether MGLRU should also be fixed:

	https://sashiko.dev/#/patchset/[email protected]
#14 lore raw
Quoted 11 lines · Andrew Morton
Quoted 9 lines · On Fri, 17 Jul 2026 19:32:57 +0800 Ridong <[email protected]> wrote:

From: Ridong Chen <[email protected]>

Two 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.

Quoted 9 lines
Quoted 5 lines

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().

Thanks. Sashiko want to know whether MGLRU should also be fixed:

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

Yes, it should be fixed. I will add a patch to fix it.

Signature · 2 lines
Best regards,
Ridong