shrink_one() can return immediately for empty or protected memcgs, and
try_to_shrink_lruvec() can break out before reaching its
cond_resched().  A scan through many memcgs can repeatedly rotate
entries without giving other runnable tasks a scheduling opportunity.

Call cond_resched_tasks_rcu_qs() (as the existing shrink_lruvec() loop
does) after shrink_one(), while the memcg reference still pins the
cursor and before reacquiring the RCU read lock.  Also call it between
bins and nulls-marker retries, covering scans that skip every entry.

Signed-off-by: Max Kellermann <[email protected]>
---
 mm/vmscan.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 922c59eb9629..c9d53546dc10 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5194,6 +5194,9 @@ static void shrink_many(struct pglist_data *pgdat, struct 
scan_control *sc)
        gen = get_memcg_gen(READ_ONCE(pgdat->memcg_lru.seq));
        bin = first_bin = get_random_u32_below(MEMCG_NR_BINS);
 restart:
+       /* Yield between bins/retries that contain only skipped entries. */
+       cond_resched_tasks_rcu_qs();
+
        op = 0;
        memcg = NULL;
 
@@ -5224,6 +5227,14 @@ static void shrink_many(struct pglist_data *pgdat, 
struct scan_control *sc)
 
                op = shrink_one(lruvec, sc);
 
+               /*
+                * Empty or protected memcgs can bypass the
+                * rescheduling points in page reclaim.  The memcg
+                * reference pins our cursor while we yield outside
+                * the RCU read-side critical section.
+                */
+               cond_resched_tasks_rcu_qs();
+
                rcu_read_lock();
 
                if (should_abort_scan(lruvec, sc))
-- 
2.47.3


Reply via email to