History

mm/mglru: fix ineffective protection calculation

mem_cgroup_calculate_protection() is not stateless and should only be used as part of a top-down tree traversal. shrink_one() traverses the per-node memcg LRU instead of the root_mem_cgroup tree, and therefore it should not call mem_cgroup_calculate_protection(). The existing misuse in shrink_one() can cause ineffective protection of sub-trees that are grandchildren of root_mem_cgroup. Fix it by reusing lru_gen_age_node(), which already traverses the root_mem_cgroup tree, to calculate the protection. Previously lru_gen_age_node() opportunistically skips the first pass, i.e., when scan_control->priority is DEF_PRIORITY. On the second pass, lruvec_is_sizable() uses appropriate scan_control->priority, set by set_initial_priority() from lru_gen_shrink_node(), to decide whether a memcg is too small to reclaim from. Now lru_gen_age_node() unconditionally traverses the root_mem_cgroup tree. So it should call set_initial_priority() upfront, to make sure lruvec_is_sizable() uses appropriate scan_control->priority on the first pass. Otherwise, lruvec_is_reclaimable() can return false negatives and result in premature OOM kills when min_ttl_ms is used.

Yu Zhao committed 2 years ago

Commit 30d77b71 parent b749cb01 file · +38 −44Browse files

Unified · Split

mm/vmscan.c+38−44
@@ -3915,6 +3915,32 @@ done: · 3914 unmodified lines
3915 * working set protection3915 * working set protection
3916 ******************************************************************************/3916 ******************************************************************************/
3917 3917
3918static void set_initial_priority(struct pglist_data *pgdat, struct scan_control *sc)
3919{
3920 int priority;
3921 unsigned long reclaimable;
3922
3923 if (sc->priority != DEF_PRIORITY || sc->nr_to_reclaim < MIN_LRU_BATCH)
3924 return;
3925 /*
3926 * Determine the initial priority based on
3927 * (total >> priority) * reclaimed_to_scanned_ratio = nr_to_reclaim,
3928 * where reclaimed_to_scanned_ratio = inactive / total.
3929 */
3930 reclaimable = node_page_state(pgdat, NR_INACTIVE_FILE);
3931 if (can_reclaim_anon_pages(NULL, pgdat->node_id, sc))
3932 reclaimable += node_page_state(pgdat, NR_INACTIVE_ANON);
3933
3934 /* round down reclaimable and round up sc->nr_to_reclaim */
3935 priority = fls_long(reclaimable) - 1 - fls_long(sc->nr_to_reclaim - 1);
3936
3937 /*
3938 * The estimation is based on LRU pages only, so cap it to prevent
3939 * overshoots of shrinker objects by large margins.
3940 */
3941 sc->priority = clamp(priority, DEF_PRIORITY / 2, DEF_PRIORITY);
3942}
3943
3918static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)3944static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
3919{3945{
3920 int gen, type, zone;3946 int gen, type, zone;
@@ -3948,19 +3974,17 @@ static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc · 27 unmodified lines
3948 struct mem_cgroup *memcg = lruvec_memcg(lruvec);3974 struct mem_cgroup *memcg = lruvec_memcg(lruvec);
3949 DEFINE_MIN_SEQ(lruvec);3975 DEFINE_MIN_SEQ(lruvec);
3950 3976
3951 /* see the comment on lru_gen_folio */3977 if (mem_cgroup_below_min(NULL, memcg))
3952 gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
3953 birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
3954
3955 if (time_is_after_jiffies(birth + min_ttl))
3956 return false;3978 return false;
3957 3979
3958 if (!lruvec_is_sizable(lruvec, sc))3980 if (!lruvec_is_sizable(lruvec, sc))
3959 return false;3981 return false;
3960 3982
3961 mem_cgroup_calculate_protection(NULL, memcg);3983 /* see the comment on lru_gen_folio */
3984 gen = lru_gen_from_seq(min_seq[LRU_GEN_FILE]);
3985 birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
3962 3986
3963 return !mem_cgroup_below_min(NULL, memcg);3987 return time_is_before_jiffies(birth + min_ttl);
3964}3988}
3965 3989
3966/* to protect the working set of the last N jiffies */3990/* to protect the working set of the last N jiffies */
@@ -3970,23 +3994,20 @@ static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc) · 3 unmodified lines
3970{3994{
3971 struct mem_cgroup *memcg;3995 struct mem_cgroup *memcg;
3972 unsigned long min_ttl = READ_ONCE(lru_gen_min_ttl);3996 unsigned long min_ttl = READ_ONCE(lru_gen_min_ttl);
3997 bool reclaimable = !min_ttl;
3973 3998
3974 VM_WARN_ON_ONCE(!current_is_kswapd());3999 VM_WARN_ON_ONCE(!current_is_kswapd());
3975 4000
3976 /* check the order to exclude compaction-induced reclaim */4001 set_initial_priority(pgdat, sc);
3977 if (!min_ttl || sc->order || sc->priority == DEF_PRIORITY)
3978 return;
3979 4002
3980 memcg = mem_cgroup_iter(NULL, NULL, NULL);4003 memcg = mem_cgroup_iter(NULL, NULL, NULL);
3981 do {4004 do {
3982 struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);4005 struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
3983 4006
3984 if (lruvec_is_reclaimable(lruvec, sc, min_ttl)) {4007 mem_cgroup_calculate_protection(NULL, memcg);
3985 mem_cgroup_iter_break(NULL, memcg);
3986 return;
3987 }
3988 4008
3989 cond_resched();4009 if (!reclaimable)
4010 reclaimable = lruvec_is_reclaimable(lruvec, sc, min_ttl);
3990 } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));4011 } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
3991 4012
3992 /*4013 /*
@@ -3994,7 +4015,7 @@ static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc) · 1 unmodified line
3994 * younger than min_ttl. However, another possibility is all memcgs are4015 * younger than min_ttl. However, another possibility is all memcgs are
3995 * either too small or below min.4016 * either too small or below min.
3996 */4017 */
3997 if (mutex_trylock(&oom_lock)) {4018 if (!reclaimable && mutex_trylock(&oom_lock)) {
3998 struct oom_control oc = {4019 struct oom_control oc = {
3999 .gfp_mask = sc->gfp_mask,4020 .gfp_mask = sc->gfp_mask,
4000 };4021 };
@@ -4786,8 +4807,7 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) · 785 unmodified lines
4786 struct mem_cgroup *memcg = lruvec_memcg(lruvec);4807 struct mem_cgroup *memcg = lruvec_memcg(lruvec);
4787 struct pglist_data *pgdat = lruvec_pgdat(lruvec);4808 struct pglist_data *pgdat = lruvec_pgdat(lruvec);
4788 4809
4789 mem_cgroup_calculate_protection(NULL, memcg);4810 /* lru_gen_age_node() called mem_cgroup_calculate_protection() */
4790
4791 if (mem_cgroup_below_min(NULL, memcg))4811 if (mem_cgroup_below_min(NULL, memcg))
4792 return MEMCG_LRU_YOUNG;4812 return MEMCG_LRU_YOUNG;
4793 4813
@@ -4911,32 +4931,6 @@ static void lru_gen_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc · 117 unmodified lines
4911 blk_finish_plug(&plug);4931 blk_finish_plug(&plug);
4912}4932}
4913 4933
4914static void set_initial_priority(struct pglist_data *pgdat, struct scan_control *sc)
4915{
4916 int priority;
4917 unsigned long reclaimable;
4918
4919 if (sc->priority != DEF_PRIORITY || sc->nr_to_reclaim < MIN_LRU_BATCH)
4920 return;
4921 /*
4922 * Determine the initial priority based on
4923 * (total >> priority) * reclaimed_to_scanned_ratio = nr_to_reclaim,
4924 * where reclaimed_to_scanned_ratio = inactive / total.
4925 */
4926 reclaimable = node_page_state(pgdat, NR_INACTIVE_FILE);
4927 if (can_reclaim_anon_pages(NULL, pgdat->node_id, sc))
4928 reclaimable += node_page_state(pgdat, NR_INACTIVE_ANON);
4929
4930 /* round down reclaimable and round up sc->nr_to_reclaim */
4931 priority = fls_long(reclaimable) - 1 - fls_long(sc->nr_to_reclaim - 1);
4932
4933 /*
4934 * The estimation is based on LRU pages only, so cap it to prevent
4935 * overshoots of shrinker objects by large margins.
4936 */
4937 sc->priority = clamp(priority, DEF_PRIORITY / 2, DEF_PRIORITY);
4938}
4939
4940static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)4934static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc)
4941{4935{
4942 struct blk_plug plug;4936 struct blk_plug plug;