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.
Unified · Split
| @@ -3915,6 +3915,32 @@ done: · 3914 unmodified lines | |||
| 3915 | * working set protection | 3915 | * working set protection |
| 3916 | ******************************************************************************/ | 3916 | ******************************************************************************/ |
| 3917 | 3917 | ||
| 3918 | static 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 | |||
| 3918 | static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc) | 3944 | static 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 are | 4015 | * 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 | ||
| 4914 | static 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 | |||
| 4940 | static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *sc) | 4934 | static 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; |