From 260fbcb92bbeacfcd050410fdc2d24ab15044400 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 28 Oct 2025 20:19:16 -1000 Subject: [PATCH] cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free() Currently, cgroup_task_exit() adds thread group leaders with live member threads to their css_set's dying_tasks list (so cgroup.procs iteration can still see the leader), and cgroup_task_release() later removes them with list_del_init(&task->cg_list). An upcoming patch will defer the dying_tasks list addition, moving it from cgroup_task_exit() (called from do_exit()) to a new function called from finish_task_switch(). However, release_task() (which calls cgroup_task_release()) can run either before or after finish_task_switch(), creating a race where cgroup_task_release() might try to remove the task from dying_tasks before or while it's being added. Move the list_del_init() from cgroup_task_release() to cgroup_task_free() to fix this race. cgroup_task_free() runs from __put_task_struct(), which is always after both paths, making the cleanup safe. Cc: Dan Schatzberg Cc: Peter Zijlstra Signed-off-by: Tejun Heo --- kernel/cgroup/cgroup.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 826b7fd2f85d6..b3c27900c5d29 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -7019,6 +7019,11 @@ void cgroup_task_release(struct task_struct *task) do_each_subsys_mask(ss, ssid, have_release_callback) { ss->release(task); } while_each_subsys_mask(); +} + +void cgroup_task_free(struct task_struct *task) +{ + struct css_set *cset = task_css_set(task); if (!list_empty(&task->cg_list)) { spin_lock_irq(&css_set_lock); @@ -7026,11 +7031,7 @@ void cgroup_task_release(struct task_struct *task) list_del_init(&task->cg_list); spin_unlock_irq(&css_set_lock); } -} -void cgroup_task_free(struct task_struct *task) -{ - struct css_set *cset = task_css_set(task); put_css_set(cset); } -- 2.47.3