From: Michal Hocko <mhocko@suse.com>
To: Audra Mitchell <audra@redhat.com>
Cc: david@kernel.org, jocolema@redhat.com, raquini@redhat.com,
Johannes Weiner <hannes@cmpxchg.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
Muchun Song <muchun.song@linux.dev>,
Andrew Morton <akpm@linux-foundation.org>,
cgroups@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH] Fix unbounded loop within try_charge_memcg
Date: Fri, 7 Aug 2026 11:17:55 +0200 [thread overview]
Message-ID: <anWiw0Zb66jyhzBU@tiehlicka> (raw)
In-Reply-To: <20260806182429.1841095-1-audra@redhat.com>
On Thu 06-08-26 14:24:28, Audra Mitchell wrote:
> Originally nr_retries was actually nr_oom_retries and we used it to track (and
> limit) the number of times we entered the mem_cgroup_oom path and then attempted
> a retry. The purpose of nr_retries counter changed with the introduction of
> 9b1306192d33 ("mm: memcontrol: retry reclaim for oom-disabled and __GFP_NOFAIL
> charges") so that the oom-disabled and __GFP_NOFAIL charges would also continue
> to retry within the desired nr_retries threshold. Later d977aa939fca
> ("mm, memcg: unify reclaim retry limits with page allocator") changed the
> nr_retries counter from 5 to 16.
>
> As the function has evolved we now have multiple paths that have a goto retry
> path and we have lost the original purpose of the nr_retries counter, allowing
> us to take a goto retry path an unbounded number of times.
>
> Fix the unbounded retries by nesting the code in a loop and decrementing the
> nr_retries counter correctly.
Are you trying to fix a theoretical problem spotted by the code review
or is there any actual problem that you are trying to fix?
>
> Signed-off-by: Audra Mitchell <audra@redhat.com>
> ---
> mm/memcontrol.c | 153 ++++++++++++++++++++++++------------------------
> 1 file changed, 76 insertions(+), 77 deletions(-)
>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 6dc4888a90f3..781bcced5848 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -2607,98 +2607,97 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,
> unsigned long pflags;
> bool allow_spinning = gfpflags_allow_spinning(gfp_mask);
>
> -retry:
> - if (consume_stock(memcg, nr_pages))
> - return 0;
> + for (; nr_retries >= 0; nr_retries--) {
>
> - if (!allow_spinning)
> - /* Avoid the refill and flush of the older stock */
> - batch = nr_pages;
> + if (consume_stock(memcg, nr_pages))
> + return 0;
>
> - reclaim_options = MEMCG_RECLAIM_MAY_SWAP;
> - if (!do_memsw_account() ||
> - page_counter_try_charge(&memcg->memsw, batch, &counter)) {
> - if (page_counter_try_charge(&memcg->memory, batch, &counter))
> - goto done_restock;
> - if (do_memsw_account())
> - page_counter_uncharge(&memcg->memsw, batch);
> - mem_over_limit = mem_cgroup_from_counter(counter, memory);
> - } else {
> - mem_over_limit = mem_cgroup_from_counter(counter, memsw);
> - reclaim_options &= ~MEMCG_RECLAIM_MAY_SWAP;
> - }
> + if (!allow_spinning)
> + /* Avoid the refill and flush of the older stock */
> + batch = nr_pages;
>
> - if (batch > nr_pages) {
> - batch = nr_pages;
> - goto retry;
> - }
> + reclaim_options = MEMCG_RECLAIM_MAY_SWAP;
> + if (!do_memsw_account() ||
> + page_counter_try_charge(&memcg->memsw, batch, &counter)) {
> + if (page_counter_try_charge(&memcg->memory, batch, &counter))
> + goto done_restock;
> + if (do_memsw_account())
> + page_counter_uncharge(&memcg->memsw, batch);
> + mem_over_limit = mem_cgroup_from_counter(counter, memory);
> + } else {
> + mem_over_limit = mem_cgroup_from_counter(counter, memsw);
> + reclaim_options &= ~MEMCG_RECLAIM_MAY_SWAP;
> + }
>
> - /*
> - * Prevent unbounded recursion when reclaim operations need to
> - * allocate memory. This might exceed the limits temporarily,
> - * but we prefer facilitating memory reclaim and getting back
> - * under the limit over triggering OOM kills in these cases.
> - */
> - if (unlikely(current->flags & PF_MEMALLOC))
> - goto force;
> + if (batch > nr_pages) {
> + batch = nr_pages;
> + continue;
> + }
>
> - if (unlikely(task_in_memcg_oom(current)))
> - goto nomem;
> + /*
> + * Prevent unbounded recursion when reclaim operations need to
> + * allocate memory. This might exceed the limits temporarily,
> + * but we prefer facilitating memory reclaim and getting back
> + * under the limit over triggering OOM kills in these cases.
> + */
> + if (unlikely(current->flags & PF_MEMALLOC))
> + goto force;
>
> - if (!gfpflags_allow_blocking(gfp_mask))
> - goto nomem;
> + if (unlikely(task_in_memcg_oom(current)))
> + goto nomem;
>
> - __memcg_memory_event(mem_over_limit, MEMCG_MAX, allow_spinning);
> - raised_max_event = true;
> + if (!gfpflags_allow_blocking(gfp_mask))
> + goto nomem;
>
> - psi_memstall_enter(&pflags);
> - nr_reclaimed = try_to_free_mem_cgroup_pages(mem_over_limit, nr_pages,
> - gfp_mask, reclaim_options, NULL);
> - psi_memstall_leave(&pflags);
> + __memcg_memory_event(mem_over_limit, MEMCG_MAX, allow_spinning);
> + raised_max_event = true;
>
> - if (mem_cgroup_margin(mem_over_limit) >= nr_pages)
> - goto retry;
> + psi_memstall_enter(&pflags);
> + nr_reclaimed = try_to_free_mem_cgroup_pages(mem_over_limit, nr_pages,
> + gfp_mask, reclaim_options, NULL);
> + psi_memstall_leave(&pflags);
>
> - if (!drained) {
> - drain_all_stock(mem_over_limit);
> - drained = true;
> - goto retry;
> - }
> + if (mem_cgroup_margin(mem_over_limit) >= nr_pages)
> + continue;
>
> - if (gfp_mask & __GFP_NORETRY)
> - goto nomem;
> - /*
> - * Even though the limit is exceeded at this point, reclaim
> - * may have been able to free some pages. Retry the charge
> - * before killing the task.
> - *
> - * Only for regular pages, though: huge pages are rather
> - * unlikely to succeed so close to the limit, and we fall back
> - * to regular pages anyway in case of failure.
> - */
> - if (nr_reclaimed && nr_pages <= (1 << PAGE_ALLOC_COSTLY_ORDER))
> - goto retry;
> + if (!drained) {
> + drain_all_stock(mem_over_limit);
> + drained = true;
> + continue;
> + }
>
> - if (nr_retries--)
> - goto retry;
> + if (gfp_mask & __GFP_NORETRY)
> + goto nomem;
> + /*
> + * Even though the limit is exceeded at this point, reclaim
> + * may have been able to free some pages. Retry the charge
> + * before killing the task.
> + *
> + * Only for regular pages, though: huge pages are rather
> + * unlikely to succeed so close to the limit, and we fall back
> + * to regular pages anyway in case of failure.
> + */
> + if (nr_reclaimed && nr_pages <= (1 << PAGE_ALLOC_COSTLY_ORDER))
> + continue;
>
> - if (gfp_mask & __GFP_RETRY_MAYFAIL)
> - goto nomem;
> + if (gfp_mask & __GFP_RETRY_MAYFAIL)
> + goto nomem;
>
> - /* Avoid endless loop for tasks bypassed by the oom killer */
> - if (passed_oom && task_is_dying())
> - goto nomem;
> + /* Avoid endless loop for tasks bypassed by the oom killer */
> + if (passed_oom && task_is_dying())
> + goto nomem;
>
> - /*
> - * keep retrying as long as the memcg oom killer is able to make
> - * a forward progress or bypass the charge if the oom killer
> - * couldn't make any progress.
> - */
> - if (mem_cgroup_oom(mem_over_limit, gfp_mask,
> - get_order(nr_pages * PAGE_SIZE))) {
> - passed_oom = true;
> - nr_retries = MAX_RECLAIM_RETRIES;
> - goto retry;
> + /*
> + * keep retrying as long as the memcg oom killer is able to make
> + * a forward progress or bypass the charge if the oom killer
> + * couldn't make any progress.
> + */
> + if (mem_cgroup_oom(mem_over_limit, gfp_mask,
> + get_order(nr_pages * PAGE_SIZE))) {
> + passed_oom = true;
> + nr_retries = MAX_RECLAIM_RETRIES;
> + continue;
> + }
> }
> nomem:
> /*
> --
> 2.52.0
>
--
Michal Hocko
SUSE Labs
next prev parent reply other threads:[~2026-08-07 9:18 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-06 18:24 [PATCH] Fix unbounded loop within try_charge_memcg Audra Mitchell
2026-08-07 9:17 ` Michal Hocko [this message]
[not found] <20260806151004.1825320-1-audra@redhat.com>
[not found] ` <anWSHc04JJBJ1VbS@tiehlicka>
2026-08-07 15:40 ` Audra Mitchell
2026-08-07 17:28 ` Shakeel Butt
2026-08-07 18:47 ` Michal Hocko
2026-08-07 19:10 ` Shakeel Butt
2026-08-07 19:13 ` Audra Mitchell
2026-08-07 20:07 ` Michal Hocko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=anWiw0Zb66jyhzBU@tiehlicka \
--to=mhocko@suse.com \
--cc=akpm@linux-foundation.org \
--cc=audra@redhat.com \
--cc=cgroups@vger.kernel.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=jocolema@redhat.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=muchun.song@linux.dev \
--cc=raquini@redhat.com \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox