cgroups: don't cache common ancestor in task counter subsys
Subsystems (2)
PATCH 1/1 cgroups: don't cache common ancestor in task counter subsys
To reproduce the bug:
# mount -t cgroup -o tasks xxx /cgroup # mkdir /cgroup/tmp # echo PID > /cgroup/tmp/cgroups.proc (PID has child threads) # echo PID > /cgroup/tasks # ecoh PID > /cgroup/tmp/cgroups.proc (oops!)
the call graph is:
for_each_thread() can_attach_task(); for_each_thead() attach_task();
but not:
for_each_thread() {
can_attach_task();
attach_task();
}So common_ancestor used in attach_task() is always the value calculated in the last can_attach_task() call.
To fix it, instead of caching, we always calculate the value every time we want to use it.
--- Documentation/cgroups/cgroups.txt | 3 ++- include/linux/cgroup.h | 2 +- kernel/cgroup.c | 7 ++++--- kernel/cgroup_task_counter.c | 24 +++++++++++++++++------- 4 files changed, 24 insertions(+), 12 deletions(-)
Documentation/cgroups/cgroups.txt +2 -1
@@ -623,7 +623,8 @@ function, so that the subsystem can implement a rollback. If not, not necessary. This will be called only about subsystems whose can_attach() operation have succeeded. -void cancel_attach_task(struct cgroup *cgrp, struct task_struct *tsk) +void cancel_attach_task(struct cgroup *cgrp, struct cgroup *oldcgrp, + struct task_struct *tsk) (cgroup_mutex held by caller) As cancel_attach, but for operations that must be cancelled once per
include/linux/cgroup.h +1 -1
@@ -475,7 +475,7 @@ struct cgroup_subsys { struct task_struct *tsk);void (*cancel_attach)(struct cgroup_subsys *ss,struct cgroup *cgrp,struct task_struct *tsk); -void (*cancel_attach_task)(struct cgroup *cgrp, +void (*cancel_attach_task)(struct cgroup *cgrp,struct cgroup *oldcgrp,struct task_struct *tsk);void (*pre_attach)(struct cgroup *cgrp); void (*attach_task)(struct cgroup *cgrp, struct cgroup *old_cgrp,
kernel/cgroup.c +4 -3
@@ -1885,7 +1885,7 @@ out:break ;if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk);if (ss->cancel_attach) ss->cancel_attach (ss, cgrp, tsk); }
@@ -2151,7 +2151,7 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) } }else if (retval == -ESRCH ) {if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk); }else {BUG_ON (1 ); }
@@ -2191,7 +2191,8 @@ out_cancel_attach: tsk =flex_array_get_ptr (group, i);if (tsk == failed_task)break ; - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, + tsk); } }
kernel/cgroup_task_counter.c +17 -7
@@ -94,11 +94,14 @@ static void task_counter_exit(struct cgroup_subsys *ss, struct cgroup *cgrp,res_counter_uncharge (cgroup_task_res_counter (old_cgrp),1 ); } -/* -* Protected amongst can_attach_task/attach_task/cancel_attach_task by -* cgroup mutex -*/ -static struct res_counter *common_ancestor; +static struct res_counter *find_common_ancestor (struct cgroup *cgrp, +struct cgroup *old_cgrp) +{ +struct res_counter *res =cgroup_task_res_counter (cgrp); +struct res_counter *old_res =cgroup_task_res_counter (old_cgrp); + +return res_counter_common_ancestor (res, old_res); +}/* * This does more than just probing the ability to attach to the dest cgroup.
@@ -112,7 +115,7 @@ static int task_counter_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk) {struct res_counter *res =cgroup_task_res_counter (cgrp); -struct res_counter *old_res =cgroup_task_res_counter (old_cgrp); +struct res_counter *common_ancestor;int err;/*
@@ -123,7 +126,7 @@ static int task_counter_can_attach_task(struct cgroup *cgrp,* in the ancestor and spuriously fail due to the temporary * charge. */ - common_ancestor =res_counter_common_ancestor (res, old_res); + common_ancestor =find_common_ancestor (cgrp, old_cgrp);/* * If cgrp is the root then res is NULL, however in this case
@@ -138,8 +141,12 @@ static int task_counter_can_attach_task(struct cgroup *cgrp,/* Uncharge the dest cgroup that we charged in task_counter_can_attach_task() */ static void task_counter_cancel_attach_task (struct cgroup *cgrp, +struct cgroup *oldcgrp,struct task_struct *tsk) { +struct res_counter *common_ancestor; + + common_ancestor =find_common_ancestor (cgrp, oldcgrp);res_counter_uncharge_until (cgroup_task_res_counter (cgrp), common_ancestor,1 ); }
@@ -155,6 +162,9 @@ static void task_counter_attach_task(struct cgroup *cgrp,struct cgroup *old_cgrp, struct task_struct *tsk) { + struct res_counter *common_ancestor; + + common_ancestor =find_common_ancestor (cgrp, old_cgrp);res_counter_uncharge_until (cgroup_task_res_counter (old_cgrp), common_ancestor,1 ); }
Signature · 1 line
1.7.3.1
Quoted 19 lines · Li Zefan
To reproduce the bug:
# mount -t cgroup -o tasks xxx /cgroup # mkdir /cgroup/tmp # echo PID > /cgroup/tmp/cgroups.proc (PID has child threads) # echo PID > /cgroup/tasks # ecoh PID > /cgroup/tmp/cgroups.proc (oops!)the call graph is:
for_each_thread() can_attach_task(); for_each_thead() attach_task();but not:
Quoted 7 lines
for_each_thread() { can_attach_task(); attach_task(); }So
common_ancestorused inattach_task()is always the value calculated in the lastcan_attach_task()call.
Ah! good catch!
Just a comment below:
Quoted 63 lines
To fix it, instead of caching, we always calculate the value every time we want to use it.
Reported-by Ben Blum <[email protected]>Signed-off-by Li Zefan <[email protected]>--- Documentation/cgroups/cgroups.txt | 3 ++- include/linux/cgroup.h | 2 +- kernel/cgroup.c | 7 ++++--- kernel/cgroup_task_counter.c | 24 +++++++++++++++++------- 4 files changed, 24 insertions(+), 12 deletions(-)Documentation/cgroups/cgroups.txt +2
-1@@ -623,7 +623,8 @@ function, so that the subsystem can implement a rollback. If not, not necessary. This will be called only about subsystems whose can_attach() operation have succeeded. -void cancel_attach_task(struct cgroup *cgrp, struct task_struct *tsk) +void cancel_attach_task(struct cgroup *cgrp, struct cgroup *oldcgrp, + struct task_struct *tsk) (cgroup_mutex held by caller) As cancel_attach, but for operations that must be cancelled once perinclude/linux/cgroup.h +1
-1@@ -475,7 +475,7 @@ struct cgroup_subsys { struct task_struct *tsk);void (*cancel_attach)(struct cgroup_subsys *ss,struct cgroup *cgrp,struct task_struct *tsk); -void (*cancel_attach_task)(struct cgroup *cgrp, +void (*cancel_attach_task)(struct cgroup *cgrp,struct cgroup *oldcgrp,struct task_struct *tsk);void (*pre_attach)(struct cgroup *cgrp); void (*attach_task)(struct cgroup *cgrp, struct cgroup *old_cgrp,kernel/cgroup.c +2
-2@@ -1885,7 +1885,7 @@ out:break ;if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk);if (ss->cancel_attach) ss->cancel_attach (ss, cgrp, tsk); }@@ -2151,7 +2151,7 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) } }else if (retval == -ESRCH ) {if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk); }else {BUG_ON (1 ); }
kernel/cgroup.c out_cancel_attach: · line 2195
Quoted 6 lines
kernel/cgroup.c +2
-1@@ -2191,7 +2191,8 @@ out_cancel_attach: tsk =flex_array_get_ptr (group, i);if (tsk == failed_task)break ; - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, + tsk);
When we rollback there, we are dealing with oldcgrp of the last thread we have treated. All threads in the rollback list don't necessary belonged to that old_cgroup.
And we can't try to retrieve these old_cgroup through task_cgroup_from_root() because the threads might have exited and thus could be assigned to the init cgroup.
I believe we need to cache these old cgroups in the flex array.
Quoted 106 lines · Frederic Weisbecker
Quoted 19 lines · Li Zefan
To reproduce the bug:
# mount -t cgroup -o tasks xxx /cgroup # mkdir /cgroup/tmp # echo PID > /cgroup/tmp/cgroups.proc (PID has child threads) # echo PID > /cgroup/tasks # ecoh PID > /cgroup/tmp/cgroups.proc (oops!)the call graph is:
for_each_thread() can_attach_task(); for_each_thead() attach_task();but not:
Quoted 7 lines
for_each_thread() { can_attach_task(); attach_task(); }So
common_ancestorused inattach_task()is always the value calculated in the lastcan_attach_task()call.Ah! good catch!
Just a comment below:
Quoted 63 lines
To fix it, instead of caching, we always calculate the value every time we want to use it.
Reported-by Ben Blum <[email protected]>Signed-off-by Li Zefan <[email protected]>--- Documentation/cgroups/cgroups.txt | 3 ++- include/linux/cgroup.h | 2 +- kernel/cgroup.c | 7 ++++--- kernel/cgroup_task_counter.c | 24 +++++++++++++++++------- 4 files changed, 24 insertions(+), 12 deletions(-)Documentation/cgroups/cgroups.txt +2
-1@@ -623,7 +623,8 @@ function, so that the subsystem can implement a rollback. If not, not necessary. This will be called only about subsystems whose can_attach() operation have succeeded. -void cancel_attach_task(struct cgroup *cgrp, struct task_struct *tsk) +void cancel_attach_task(struct cgroup *cgrp, struct cgroup *oldcgrp, + struct task_struct *tsk) (cgroup_mutex held by caller) As cancel_attach, but for operations that must be cancelled once perinclude/linux/cgroup.h +1
-1@@ -475,7 +475,7 @@ struct cgroup_subsys { struct task_struct *tsk);void (*cancel_attach)(struct cgroup_subsys *ss,struct cgroup *cgrp,struct task_struct *tsk); -void (*cancel_attach_task)(struct cgroup *cgrp, +void (*cancel_attach_task)(struct cgroup *cgrp,struct cgroup *oldcgrp,struct task_struct *tsk);void (*pre_attach)(struct cgroup *cgrp); void (*attach_task)(struct cgroup *cgrp, struct cgroup *old_cgrp,kernel/cgroup.c +2
-2@@ -1885,7 +1885,7 @@ out:break ;if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk);if (ss->cancel_attach) ss->cancel_attach (ss, cgrp, tsk); }@@ -2151,7 +2151,7 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) } }else if (retval == -ESRCH ) {if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk); }else {BUG_ON (1 ); }kernel/cgroup.c out_cancel_attach: · line 2195
Quoted 6 lines
kernel/cgroup.c +2
-1@@ -2191,7 +2191,8 @@ out_cancel_attach: tsk =flex_array_get_ptr (group, i);if (tsk == failed_task)break ; - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, + tsk);When we rollback there, we are dealing with oldcgrp of the last thread we have treated. All threads in the rollback list don't necessary belonged to that
old_cgroup.
Quoted 5 lines
And we can't try to retrieve these
old_cgroupthroughtask_cgroup_from_root()because the threads might have exited and thus could be assigned to the init cgroup.I believe we need to cache these old cgroups in the flex array.
Doing this would fulfill the "TODO" in cgroup_attach_proc for being able to pass the oldcgrp for each task in the loop around the ss->attach call, so if you do that, remove the corresponding comment. :)
-- Ben
Quoted 8 lines · Ben Blum
Quoted 7 lines · Frederic Weisbecker
When we rollback there, we are dealing with oldcgrp of the last thread we have treated. All threads in the rollback list don't necessary belonged to that
old_cgroup.And we can't try to retrieve these
old_cgroupthroughtask_cgroup_from_root()because the threads might have exited and thus could be assigned to the init cgroup.
Quoted 8 lines
Quoted 2 lines
I believe we need to cache these old cgroups in the flex array.
Doing this would fulfill the "TODO" in
cgroup_attach_procfor being able to pass the oldcgrp for each task in the loop around thess->attachcall, so if you do that, remove the corresponding comment. :)-- Ben
I don't know, looking at the code, there is a separate issue to solve there. The old cgroup passed in ->attach() is the one of a random task from the thread group. The migrations that happen right before leave oldcgrp with the old cgroup of the last thread in the group.
This doesn't seem to make any sense. As far as I checked this is only used by the cpuset subsystem that does a migration in cpuset_attach() from the old cgroup to the new.
I'm not sure about the details but that looks buggy.
Quoted 23 lines · Frederic Weisbecker
Quoted 8 lines · Ben Blum
Quoted 7 lines · Frederic Weisbecker
When we rollback there, we are dealing with oldcgrp of the last thread we have treated. All threads in the rollback list don't necessary belonged to that
old_cgroup.And we can't try to retrieve these
old_cgroupthroughtask_cgroup_from_root()because the threads might have exited and thus could be assigned to the init cgroup.Quoted 8 lines
Quoted 2 lines
I believe we need to cache these old cgroups in the flex array.
Doing this would fulfill the "TODO" in
cgroup_attach_procfor being able to pass the oldcgrp for each task in the loop around thess->attachcall, so if you do that, remove the corresponding comment. :)-- Ben
I don't know, looking at the code, there is a separate issue to solve there. The old cgroup passed in ->attach() is the one of a random task from the thread group. The migrations that happen right before leave oldcgrp with the old cgroup of the last thread in the group.
Quoted 6 lines
This doesn't seem to make any sense. As far as I checked this is only used by the cpuset subsystem that does a migration in
cpuset_attach()from the old cgroup to the new.I'm not sure about the details but that looks buggy.
I think you're right, and Tejun's patchset happens to fix this bug in cpuset_attach() - it gets the mm of the thread leader and do migration.
Quoted 12 lines
Quoted 12 lines
kernel/cgroup.c +1
-1@@ -2151,7 +2151,7 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) } }else if (retval == -ESRCH ) {if (ss->cancel_attach_task) - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, tsk); }else {BUG_ON (1 ); }@@ -2191,7 +2191,8 @@ out_cancel_attach: tsk =flex_array_get_ptr (group, i);if (tsk == failed_task)
Quoted 9 lines
kernel/cgroup.c out_cancel_attach: · line 2195
Quoted 4 lines
kernel/cgroup.c +2
-1break ; - ss->cancel_attach_task (cgrp, tsk); + ss->cancel_attach_task (cgrp, oldcgrp, + tsk);When we rollback there, we are dealing with oldcgrp of the last thread we have treated. All threads in the rollback list don't necessary belonged to that
old_cgroup.
Good point. I didn't look carefully at this case.
Quoted 5 lines
And we can't try to retrieve these
old_cgroupthroughtask_cgroup_from_root()because the threads might have exited and thus could be assigned to the init cgroup.I believe we need to cache these old cgroups in the flex array.