The Linux Kernel Mailing List
 help / color / mirror / Atom feed
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

  reply	other threads:[~2026-08-07  9:17 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