* [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans
@ 2026-10-26 12:02 Zhan Xusheng
2026-10-27 13:26 ` Tao Cui
2026-11-02 10:12 ` Michal Hocko
0 siblings, 2 replies; 4+ messages in thread
From: Zhan Xusheng @ 2026-10-26 12:02 UTC (permalink / raw)
To: akpm
Cc: hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song,
penguin-kernel, tj, mkoutny, cgroups, linux-mm, linux-kernel,
zhanxusheng, stable
From: Zhan Xusheng <zhanxusheng@xiaomi.com>
mem_cgroup_scan_tasks() walks with CSS_TASK_ITER_PROCS, so it sees only
thread group leaders, plus a dying leader that still has live threads.
Moving a process whose leader has exited, by writing its PID to
cgroup.procs, moves the live threads but leaves the leader behind, as
cgroup_migrate_add_task() skips exiting tasks.
The destination's OOM killer then cannot see the process. If it is
alone there, every attempt ends with "Out of memory and no killable
processes..." and the charge is retried indefinitely, and
memory.oom.group kills nothing; otherwise another process is killed in
its place. The source's OOM killer still finds the leader, reaches the
moved threads' mm through find_lock_task_mm(), and kills a process that
has left the memcg.
Walk all tasks and take one per thread group: the leader, or the first
live thread once the leader is exiting. Skip exiting threads, which may
belong to a group that has moved out.
Tested in a VM with memory.max=64M: in each case above the process using
the memory is now killed, and a process whose exited leader was not
moved, or whose leader is alive, is killed as before, with and without
memory.oom.group.
Fixes: f168a9a54ec3 ("mm: memcontrol: use CSS_TASK_ITER_PROCS at mem_cgroup_scan_tasks()")
Cc: stable@vger.kernel.org
Signed-off-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
---
mm/memcontrol.c | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 4d00748c8a5b..8823c549b6af 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1427,6 +1427,36 @@ static void invalidate_reclaim_iterators(struct mem_cgroup *dead_memcg)
dead_memcg);
}
+/*
+ * Take one task per thread group: the leader, or the first live thread once
+ * the leader is exiting, since migrating the group leaves an exiting leader
+ * behind. Exiting threads are skipped, as one left behind may belong to a
+ * group that has moved out.
+ */
+static bool mem_cgroup_scan_skip(struct task_struct *task)
+{
+ struct task_struct *leader = task->group_leader;
+ struct task_struct *t;
+ bool skip = true;
+
+ if (READ_ONCE(task->flags) & PF_EXITING)
+ return true;
+ if (task == leader)
+ return false;
+ if (!(READ_ONCE(leader->flags) & PF_EXITING))
+ return true;
+
+ rcu_read_lock();
+ for_each_thread(leader, t) {
+ if (!(READ_ONCE(t->flags) & PF_EXITING)) {
+ skip = t != task;
+ break;
+ }
+ }
+ rcu_read_unlock();
+ return skip;
+}
+
/**
* mem_cgroup_scan_tasks - iterate over tasks of a memory cgroup hierarchy
* @memcg: hierarchy root
@@ -1452,9 +1482,10 @@ void mem_cgroup_scan_tasks(struct mem_cgroup *memcg,
struct css_task_iter it;
struct task_struct *task;
- css_task_iter_start(&iter->css, CSS_TASK_ITER_PROCS, &it);
+ css_task_iter_start(&iter->css, 0, &it);
while (!ret && (task = css_task_iter_next(&it))) {
- ret = fn(task, arg);
+ if (!mem_cgroup_scan_skip(task))
+ ret = fn(task, arg);
/* Avoid potential softlockup warning */
cond_resched();
}
base-commit: 6b4b543fa18757297ac756a6fe6e64f506b7844a
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans
2026-10-26 12:02 [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans Zhan Xusheng
@ 2026-10-27 13:26 ` Tao Cui
2026-11-02 10:12 ` Michal Hocko
1 sibling, 0 replies; 4+ messages in thread
From: Tao Cui @ 2026-10-27 13:26 UTC (permalink / raw)
To: Zhan Xusheng, akpm
Cc: cui.tao, hannes, mhocko, roman.gushchin, shakeel.butt,
muchun.song, penguin-kernel, tj, mkoutny, cgroups, linux-mm,
linux-kernel, zhanxusheng, stable
Hi, Xusheng,
在 2026/10/26 20:02, Zhan Xusheng 写道:
> From: Zhan Xusheng <zhanxusheng@xiaomi.com>
>
> mem_cgroup_scan_tasks() walks with CSS_TASK_ITER_PROCS, so it sees only
> thread group leaders, plus a dying leader that still has live threads.
> Moving a process whose leader has exited, by writing its PID to
> cgroup.procs, moves the live threads but leaves the leader behind, as
> cgroup_migrate_add_task() skips exiting tasks.
>
> The destination's OOM killer then cannot see the process. If it is
> alone there, every attempt ends with "Out of memory and no killable
> processes..." and the charge is retried indefinitely, and
> memory.oom.group kills nothing; otherwise another process is killed in
> its place. The source's OOM killer still finds the leader, reaches the
> moved threads' mm through find_lock_task_mm(), and kills a process that
> has left the memcg.
>
> Walk all tasks and take one per thread group: the leader, or the first
> live thread once the leader is exiting. Skip exiting threads, which may
> belong to a group that has moved out.
>
> Tested in a VM with memory.max=64M: in each case above the process using
> the memory is now killed, and a process whose exited leader was not
> moved, or whose leader is alive, is killed as before, with and without
> memory.oom.group.
>
Tested on next-20261026 (x86_64 KVM guest, cgroup v2, 48M memory.max
on the destination group). Without the patch the worker thread stays
alive and the OOM scan keeps printing "Out of memory and no killable
processes..." (2887 oom, 0 oom_kill in one minute). With the patch
the worker is killed right away. A live leader and an exited leader
that was not moved away are still killed as before.
Reviewed-by: Tao Cui <cuitao@kylinos.cn>
Tested-by: Tao Cui <cuitao@kylinos.cn>
Thanks,
Tao
> Fixes: f168a9a54ec3 ("mm: memcontrol: use CSS_TASK_ITER_PROCS at mem_cgroup_scan_tasks()")
> Cc: stable@vger.kernel.org
> Signed-off-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
> ---
> mm/memcontrol.c | 35 +++++++++++++++++++++++++++++++++--
> 1 file changed, 33 insertions(+), 2 deletions(-)
>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 4d00748c8a5b..8823c549b6af 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -1427,6 +1427,36 @@ static void invalidate_reclaim_iterators(struct mem_cgroup *dead_memcg)
> dead_memcg);
> }
>
> +/*
> + * Take one task per thread group: the leader, or the first live thread once
> + * the leader is exiting, since migrating the group leaves an exiting leader
> + * behind. Exiting threads are skipped, as one left behind may belong to a
> + * group that has moved out.
> + */
> +static bool mem_cgroup_scan_skip(struct task_struct *task)
> +{
> + struct task_struct *leader = task->group_leader;
> + struct task_struct *t;
> + bool skip = true;
> +
> + if (READ_ONCE(task->flags) & PF_EXITING)
> + return true;
> + if (task == leader)
> + return false;
> + if (!(READ_ONCE(leader->flags) & PF_EXITING))
> + return true;
> +
> + rcu_read_lock();
> + for_each_thread(leader, t) {
> + if (!(READ_ONCE(t->flags) & PF_EXITING)) {
> + skip = t != task;
> + break;
> + }
> + }
> + rcu_read_unlock();
> + return skip;
> +}
> +
> /**
> * mem_cgroup_scan_tasks - iterate over tasks of a memory cgroup hierarchy
> * @memcg: hierarchy root
> @@ -1452,9 +1482,10 @@ void mem_cgroup_scan_tasks(struct mem_cgroup *memcg,
> struct css_task_iter it;
> struct task_struct *task;
>
> - css_task_iter_start(&iter->css, CSS_TASK_ITER_PROCS, &it);
> + css_task_iter_start(&iter->css, 0, &it);
> while (!ret && (task = css_task_iter_next(&it))) {
> - ret = fn(task, arg);
> + if (!mem_cgroup_scan_skip(task))
> + ret = fn(task, arg);
> /* Avoid potential softlockup warning */
> cond_resched();
> }
>
> base-commit: 6b4b543fa18757297ac756a6fe6e64f506b7844a
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans
2026-10-26 12:02 [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans Zhan Xusheng
2026-10-27 13:26 ` Tao Cui
@ 2026-11-02 10:12 ` Michal Hocko
2026-11-02 11:40 ` Zhan Xusheng
1 sibling, 1 reply; 4+ messages in thread
From: Michal Hocko @ 2026-11-02 10:12 UTC (permalink / raw)
To: Zhan Xusheng
Cc: akpm, hannes, roman.gushchin, shakeel.butt, muchun.song,
penguin-kernel, tj, mkoutny, cgroups, linux-mm, linux-kernel,
zhanxusheng, stable
On Mon 26-10-26 20:02:48, Zhan Xusheng wrote:
> From: Zhan Xusheng <zhanxusheng@xiaomi.com>
>
> mem_cgroup_scan_tasks() walks with CSS_TASK_ITER_PROCS, so it sees only
> thread group leaders, plus a dying leader that still has live threads.
> Moving a process whose leader has exited, by writing its PID to
> cgroup.procs, moves the live threads but leaves the leader behind, as
> cgroup_migrate_add_task() skips exiting tasks.
Is this a desirable/intended behavior? Shouldn't we move the dead leader
along with the rest?
> The destination's OOM killer then cannot see the process. If it is
> alone there, every attempt ends with "Out of memory and no killable
> processes..." and the charge is retried indefinitely, and
> memory.oom.group kills nothing; otherwise another process is killed in
> its place. The source's OOM killer still finds the leader, reaches the
> moved threads' mm through find_lock_task_mm(), and kills a process that
> has left the memcg.
While you patch works around this seems like something to address in the
cgroup core. Should CSS_TASK_ITER_PROCS iteration see all processes
regardless leader being alive after or prior the migration? AFAIU the
thread group belongs to the new cgroup in both cases.
>
> @@ -1452,9 +1482,10 @@ void mem_cgroup_scan_tasks(struct mem_cgroup *memcg,
> struct css_task_iter it;
> struct task_struct *task;
>
> - css_task_iter_start(&iter->css, CSS_TASK_ITER_PROCS, &it);
> + css_task_iter_start(&iter->css, 0, &it);
Shouldn't this be CSS_TASK_ITER_THREADED instead of 0?
> while (!ret && (task = css_task_iter_next(&it))) {
> - ret = fn(task, arg);
> + if (!mem_cgroup_scan_skip(task))
> + ret = fn(task, arg);
> /* Avoid potential softlockup warning */
> cond_resched();
> }
>
> base-commit: 6b4b543fa18757297ac756a6fe6e64f506b7844a
> --
> 2.43.0
--
Michal Hocko
SUSE Labs
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans
2026-11-02 10:12 ` Michal Hocko
@ 2026-11-02 11:40 ` Zhan Xusheng
0 siblings, 0 replies; 4+ messages in thread
From: Zhan Xusheng @ 2026-11-02 11:40 UTC (permalink / raw)
To: mhocko
Cc: akpm, hannes, roman.gushchin, shakeel.butt, muchun.song,
penguin-kernel, tj, mkoutny, cgroups, linux-mm, linux-kernel,
stable, zhanxusheng
Hi Michal,
This is v1. v2 [1] reworked the check, but your questions apply to
both.
On Mon, Nov 02, 2026 at 11:12:42AM +0100, Michal Hocko wrote:
> Is this a desirable/intended behavior? Shouldn't we move the dead leader
> along with the rest?
Userspace sees it too. After writing the PID of such a process to
dst/cgroup.procs:
src: cgroup.procs = 85 cgroup.threads = (empty)
dst: cgroup.procs = (empty) cgroup.threads = 86 87
Moving the leader would fix that, and memcg would then need no change,
as the PROCS walk already returns a dying leader with live threads.
The catch is that the leader has already gone through
cgroup_task_dead() and is still accounted by controllers: pids, for
one, uncharges it through task_css() when it is reaped, so the move
would also have to transfer its pids charge. Tejun, would that be
acceptable in the core?
> Should CSS_TASK_ITER_PROCS iteration see all processes
> regardless leader being alive after or prior the migration?
With the leader moved, it would. Otherwise PROCS would have to pick a
stand-in thread, which is what this patch does inside memcg.
> Shouldn't this be CSS_TASK_ITER_THREADED instead of 0?
No. Walking a memcg css goes over e_csets[memory], which already
includes the csets of threaded descendants, as memory is not a
threaded controller. THREADED also walks each cset's threaded_csets,
so those would be visited twice. It is meant for walks over
cgrp->self, like cgroup.procs. 0 is also what this function used
before f168a9a54ec3.
[1] https://lore.kernel.org/all/20261027144016.2582880-1-zhanxusheng1024@gmail.com/
Thanks,
Zhan Xusheng
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-02 11:40 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26 12:02 [PATCH] mm: memcontrol: don't miss processes whose leader has exited in OOM scans Zhan Xusheng
2026-10-27 13:26 ` Tao Cui
2026-11-02 10:12 ` Michal Hocko
2026-11-02 11:40 ` Zhan Xusheng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox