[PATCH v3 1/2] ext4: fix shrinker scan budget accounting in ext4_es_scan()
From: Qiliang Yuan
Date: Wed Sep 30 2026 - 03:15:45 EST
The extents_status shrinker's count_objects() callback,
ext4_es_count(), reports the number of shrinkable extent_status
objects via a percpu counter. do_shrink_slab() reads this count
exactly once per invocation and derives a one-shot scan budget
(total_scan) from it, then repeatedly calls scan_objects() in
fixed-size batches until that budget is exhausted.
ext4_es_scan() never signals SHRINK_STOP, so do_shrink_slab() has no
way to tell "there was nothing to reclaim this pass" from "a full
batch was scanned and none of it was freeable". It keeps calling
scan_objects() until the original, one-shot total_scan budget is
drained, even when sbi->s_es_list has had nothing left to give for a
while.
Return SHRINK_STOP whenever __es_shrink() frees nothing, so
do_shrink_slab() stops calling us for the rest of this reclaim pass
instead of burning through its scan budget against the same stale
freeable count.
Tested by fallocate(2)-ing 10000 4K files (to populate the shrinker
with reclaimable unwritten extents without also exercising the
extent_status "referenced" second-chance path, which needs a
separate two-pass accounting of its own) and triggering
"echo 2 > /proc/sys/vm/drop_caches", while tracing the
ext4_es_shrink* tracepoints:
total scan_objects() calls with
calls nr_shrunk == 0
before this patch 429 189 (44%)
after this patch 238 1 (0.4%)
Fixes: 1ab6c4997e04 ("fs: convert fs shrinkers to new scan/count API")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Qiliang Yuan <odys.yuan@xxxxxxxxx>
---
fs/ext4/extents_status.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c
index 6e4a191e82191..d88af807d36e7 100644
--- a/fs/ext4/extents_status.c
+++ b/fs/ext4/extents_status.c
@@ -1783,6 +1783,16 @@ static unsigned long ext4_es_scan(struct shrinker *shrink,
ret = percpu_counter_read_positive(&sbi->s_es_stats.es_stats_shk_cnt);
trace_ext4_es_shrink_scan_exit(sbi->s_sb, nr_shrunk, ret);
+
+ /*
+ * es_stats_shk_cnt is a percpu counter, so it can stay positive
+ * after sbi->s_es_list has nothing left to give. Stop this reclaim
+ * pass once a call frees nothing, instead of burning through
+ * do_shrink_slab()'s scan budget against a stale count.
+ */
+ if (nr_shrunk == 0)
+ return SHRINK_STOP;
+
return nr_shrunk;
}
--
2.43.0