Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
wq_update_unbound_numa();
kthread_create_on_node();
wake_up_process(); //wakeup kthreadd
flush_work();
wait_for_completion();
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Signed-off-by: Prateek Sood <redacted>
---
kernel/cgroup/cpuset.c | 32 +++++++++++++++++++-------------
1 file changed, 19 insertions(+), 13 deletions(-)
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc.,
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
From: Peter Zijlstra <peterz@infradead.org> Date: 2017-09-07 17:45:21
On Thu, Sep 07, 2017 at 07:26:23PM +0530, Prateek Sood wrote:
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
wq_update_unbound_numa();
kthread_create_on_node();
wake_up_process(); //wakeup kthreadd
TJ, I'm puzzled, why would we need to spawn new threads to update NUMA
affinity when taking a CPU out? That doesn't make sense to me, we can
either shrink the affinity of an existing thread or completely kill of a
thread if the mask becomes empty. But why spawn a new thread?
So this will eventually do our:
complete() to make A go.
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
So the whole thing looks like:
A B C D
L(hotplug)
L(threadgroup)
L(cpuset)
L(threadgroup)
WFC(c)
L(cpuset)
L(hotplug)
C(c)
Yes, inverting cpuset and hotplug would break that chain, but I'm still
wondering why workqueue needs to spawn threads on CPU down.
From: Peter Zijlstra <peterz@infradead.org> Date: 2017-09-07 17:51:19
On Thu, Sep 07, 2017 at 07:26:23PM +0530, Prateek Sood wrote:
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
You've forgotten to mention your solution to the deadlock, namely
inverting cpuset_mutex and cpu_hotplug_lock.
@@ -816,16 +816,15 @@ static int generate_sched_domains(cpumask_var_t **domains,*'cpus'isremoved,thencallthisroutinetorebuildthe*scheduler'sdynamicscheddomains.*-*Callwithcpuset_mutexheld.Takesget_online_cpus().*/-staticvoidrebuild_sched_domains_locked(void)+staticvoidrebuild_sched_domains_cpuslocked(void){structsched_domain_attr*attr;cpumask_var_t*doms;intndoms;+lockdep_assert_cpus_held();lockdep_assert_held(&cpuset_mutex);-get_online_cpus();/**WehaveracedwithCPUhotplug.Don'tdoanythingtoavoid
@@ -833,27 +832,27 @@ static void rebuild_sched_domains_locked(void)*Anyways,hotplugworkitemwillrebuildscheddomains.*/if(!cpumask_equal(top_cpuset.effective_cpus,cpu_active_mask))-gotoout;+return;/* Generate domain masks and attrs */ndoms=generate_sched_domains(&doms,&attr);/* Have scheduler rebuild the domains */partition_sched_domains(ndoms,doms,attr);-out:-put_online_cpus();}#else /* !CONFIG_SMP */-staticvoidrebuild_sched_domains_locked(void)+staticvoidrebuild_sched_domains_cpuslocked(void){}#endif /* CONFIG_SMP */voidrebuild_sched_domains(void){+get_online_cpus();mutex_lock(&cpuset_mutex);-rebuild_sched_domains_locked();+rebuild_sched_domains_cpuslocked();mutex_unlock(&cpuset_mutex);+put_online_cpus();}
But if you invert these locks, the need for cpuset_hotplug_workfn() goes
away, at least for the CPU part, and we can make in synchronous again.
Yay!!
Also, I think new code should use cpus_read_lock() instead of
get_online_cpus().
On Thu, Sep 07, 2017 at 07:26:23PM +0530, Prateek Sood wrote:
quoted
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
quoted
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
wq_update_unbound_numa();
kthread_create_on_node();
wake_up_process(); //wakeup kthreadd
TJ, I'm puzzled, why would we need to spawn new threads to update NUMA
affinity when taking a CPU out? That doesn't make sense to me, we can
either shrink the affinity of an existing thread or completely kill of a
thread if the mask becomes empty. But why spawn a new thread?
quoted
flush_work();
wait_for_completion();
Yes, inverting cpuset and hotplug would break that chain, but I'm still
wondering why workqueue needs to spawn threads on CPU down.
Thanks for the comments Peter
You rightly mentioned that a new thread will not be spawn
while updating NUMA affinity when taking a CPU out.
While a CPU is made offline, attempt is made to unbind per-cpu
worker for CPU going down. This is done by queuing unbind work
to system_highpri_wq. It results in an attempt to create one
bounded worker thread as there is none.
wait_for_completion() in flush_work() waits for unbinding to
finish for CPU going down.
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
queue_work_on(system_highpri_wq);
__queue_work();
insert_work();
wake_up_worker(); //pool->nr_running = 0
flush_work();
wait_for_completion();
worker_thread();
need_more_worker(); // returns true
manage_workers();
maybe_create_worker();
create_worker();
kthread_create_on_node();
wake_up_process(kthreadd_task);
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
On Thu, Sep 07, 2017 at 07:26:23PM +0530, Prateek Sood wrote:
quoted
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
You've forgotten to mention your solution to the deadlock, namely
inverting cpuset_mutex and cpu_hotplug_lock.
@@ -816,16 +816,15 @@ static int generate_sched_domains(cpumask_var_t **domains,*'cpus'isremoved,thencallthisroutinetorebuildthe*scheduler'sdynamicscheddomains.*-*Callwithcpuset_mutexheld.Takesget_online_cpus().*/-staticvoidrebuild_sched_domains_locked(void)+staticvoidrebuild_sched_domains_cpuslocked(void){structsched_domain_attr*attr;cpumask_var_t*doms;intndoms;+lockdep_assert_cpus_held();lockdep_assert_held(&cpuset_mutex);-get_online_cpus();/**WehaveracedwithCPUhotplug.Don'tdoanythingtoavoid
@@ -833,27 +832,27 @@ static void rebuild_sched_domains_locked(void)*Anyways,hotplugworkitemwillrebuildscheddomains.*/if(!cpumask_equal(top_cpuset.effective_cpus,cpu_active_mask))-gotoout;+return;/* Generate domain masks and attrs */ndoms=generate_sched_domains(&doms,&attr);/* Have scheduler rebuild the domains */partition_sched_domains(ndoms,doms,attr);-out:-put_online_cpus();}#else /* !CONFIG_SMP */-staticvoidrebuild_sched_domains_locked(void)+staticvoidrebuild_sched_domains_cpuslocked(void){}#endif /* CONFIG_SMP */voidrebuild_sched_domains(void){+get_online_cpus();mutex_lock(&cpuset_mutex);-rebuild_sched_domains_locked();+rebuild_sched_domains_cpuslocked();mutex_unlock(&cpuset_mutex);+put_online_cpus();}
But if you invert these locks, the need for cpuset_hotplug_workfn() goes
away, at least for the CPU part, and we can make in synchronous again.
Yay!!
Also, I think new code should use cpus_read_lock() instead of
get_online_cpus().
Thanks for the review comments Peter.
For patch related to circular deadlock, I will send an updated version.
The callback making a call to cpuset_hotplug_workfn()in hotplug path are
[CPUHP_AP_ACTIVE] = {
.name = "sched:active",
.startup.single = sched_cpu_activate,
.teardown.single = sched_cpu_deactivate,
},
if we make cpuset_hotplug_workfn() synchronous, deadlock might happen:
_cpu_down()
cpus_write_lock() //held
cpuhp_kick_ap_work()
cpuhp_kick_ap()
__cpuhp_kick_ap()
wake_up_process() //cpuhp_thread_fun
wait_for_ap_thread() //wait for complete from cpuhp_thread_fun()
cpuhp_thread_fun()
cpuhp_invoke_callback()
sched_cpu_deactivate()
cpuset_cpu_inactive()
cpuset_update_active_cpus()
cpuset_hotplug_work()
rebuild_sched_domains()
cpus_read_lock() //waiting as acquired in _cpu_down()
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
From: Peter Zijlstra <peterz@infradead.org> Date: 2017-10-11 09:48:42
On Mon, Oct 09, 2017 at 06:57:46PM +0530, Prateek Sood wrote:
On 09/07/2017 11:21 PM, Peter Zijlstra wrote:
quoted
But if you invert these locks, the need for cpuset_hotplug_workfn() goes
away, at least for the CPU part, and we can make in synchronous again.
Yay!!
The callback making a call to cpuset_hotplug_workfn()in hotplug path are
[CPUHP_AP_ACTIVE] = {
.name = "sched:active",
.startup.single = sched_cpu_activate,
.teardown.single = sched_cpu_deactivate,
},
if we make cpuset_hotplug_workfn() synchronous, deadlock might happen:
_cpu_down()
cpus_write_lock() //held
cpuhp_kick_ap_work()
cpuhp_kick_ap()
__cpuhp_kick_ap()
wake_up_process() //cpuhp_thread_fun
wait_for_ap_thread() //wait for complete from cpuhp_thread_fun()
cpuhp_thread_fun()
cpuhp_invoke_callback()
sched_cpu_deactivate()
cpuset_cpu_inactive()
cpuset_update_active_cpus()
cpuset_hotplug_work()
rebuild_sched_domains()
cpus_read_lock() //waiting as acquired in _cpu_down()
Well, duh, don't use rebuild_sched_domains() 'obviously' :-) use
rebuild_sched_domains_cpuslocked() instead and it works just fine.
After applying your patch, the below boots and survives a hotplug.
---
include/linux/cpuset.h | 6 ------
kernel/cgroup/cpuset.c | 30 +++++++++---------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 9 insertions(+), 30 deletions(-)
@@ -2362,25 +2360,15 @@ static void cpuset_hotplug_workfn(struct}/* rebuild sched domains if cpus_allowed has changed */-if(cpus_updated||force_rebuild){-force_rebuild=false;+if(cpus_updated)rebuild_sched_domains();-}}voidcpuset_update_active_cpus(void){-/*-*We'reinsidecpuhotplugcriticalregionwhichusuallynests-*insidecgroupsynchronization.Bounceactualhotplugprocessing-*toaworkitemtoavoidreverselockingorder.-*/-schedule_work(&cpuset_hotplug_work);-}--voidcpuset_wait_for_hotplug(void)-{-flush_work(&cpuset_hotplug_work);+mutex_lock(&cpuset_mutex);+rebuild_sched_domains_cpuslocked();+mutex_unlock(&cpuset_mutex);}/*---a/kernel/power/process.c+++b/kernel/power/process.c
@@ -203,8 +203,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */---a/kernel/sched/core.c+++b/kernel/sched/core.c
On Mon, Oct 09, 2017 at 06:57:46PM +0530, Prateek Sood wrote:
quoted
On 09/07/2017 11:21 PM, Peter Zijlstra wrote:
quoted
quoted
But if you invert these locks, the need for cpuset_hotplug_workfn() goes
away, at least for the CPU part, and we can make in synchronous again.
Yay!!
quoted
The callback making a call to cpuset_hotplug_workfn()in hotplug path are
[CPUHP_AP_ACTIVE] = {
.name = "sched:active",
.startup.single = sched_cpu_activate,
.teardown.single = sched_cpu_deactivate,
},
if we make cpuset_hotplug_workfn() synchronous, deadlock might happen:
_cpu_down()
cpus_write_lock() //held
cpuhp_kick_ap_work()
cpuhp_kick_ap()
__cpuhp_kick_ap()
wake_up_process() //cpuhp_thread_fun
wait_for_ap_thread() //wait for complete from cpuhp_thread_fun()
cpuhp_thread_fun()
cpuhp_invoke_callback()
sched_cpu_deactivate()
cpuset_cpu_inactive()
cpuset_update_active_cpus()
cpuset_hotplug_work()
rebuild_sched_domains()
cpus_read_lock() //waiting as acquired in _cpu_down()
Well, duh, don't use rebuild_sched_domains() 'obviously' :-) use
rebuild_sched_domains_cpuslocked() instead and it works just fine.
After applying your patch, the below boots and survives a hotplug.
---
include/linux/cpuset.h | 6 ------
kernel/cgroup/cpuset.c | 30 +++++++++---------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 9 insertions(+), 30 deletions(-)
@@ -2362,25 +2360,15 @@ static void cpuset_hotplug_workfn(struct}/* rebuild sched domains if cpus_allowed has changed */-if(cpus_updated||force_rebuild){-force_rebuild=false;+if(cpus_updated)rebuild_sched_domains();-}}voidcpuset_update_active_cpus(void){-/*-*We'reinsidecpuhotplugcriticalregionwhichusuallynests-*insidecgroupsynchronization.Bounceactualhotplugprocessing-*toaworkitemtoavoidreverselockingorder.-*/-schedule_work(&cpuset_hotplug_work);-}--voidcpuset_wait_for_hotplug(void)-{-flush_work(&cpuset_hotplug_work);+mutex_lock(&cpuset_mutex);+rebuild_sched_domains_cpuslocked();+mutex_unlock(&cpuset_mutex);}/*---a/kernel/power/process.c+++b/kernel/power/process.c
@@ -203,8 +203,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */---a/kernel/sched/core.c+++b/kernel/sched/core.c
Thanks Peter for sharing the patch and test results.
quoted hunk
void cpuset_update_active_cpus(void) {- /*- * We're inside cpu hotplug critical region which usually nests- * inside cgroup synchronization. Bounce actual hotplug processing- * to a work item to avoid reverse locking order.- */- schedule_work(&cpuset_hotplug_work);-}--void cpuset_wait_for_hotplug(void)-{- flush_work(&cpuset_hotplug_work);+ mutex_lock(&cpuset_mutex);+ rebuild_sched_domains_cpuslocked();+ mutex_unlock(&cpuset_mutex); }
In the above patch rebuild_sched_domains_cpuslocked() has been
used directly. Earlier cpuset_hotplug_update_tasks() was also
called from cpuset_hotplug_workfn(). So migration of tasks
related to cgroup which has empty cpuset would not happen
during cpu hotplug.
Could you please help in understanding more on this.
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
In the above patch rebuild_sched_domains_cpuslocked() has been
used directly. Earlier cpuset_hotplug_update_tasks() was also
called from cpuset_hotplug_workfn(). So migration of tasks
related to cgroup which has empty cpuset would not happen
during cpu hotplug.
Could you please help in understanding more on this.
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
wq_update_unbound_numa();
kthread_create_on_node();
wake_up_process(); //wakeup kthreadd
flush_work();
wait_for_completion();
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock.
Signed-off-by: Prateek Sood <redacted>
---
include/linux/cpuset.h | 6 -----
kernel/cgroup/cpuset.c | 70 ++++++++++++++++++++++++++------------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 36 insertions(+), 43 deletions(-)
@@ -2356,25 +2354,29 @@ static void cpuset_hotplug_workfn(struct work_struct *work)}/* rebuild sched domains if cpus_allowed has changed */-if(cpus_updated||force_rebuild){-force_rebuild=false;-rebuild_sched_domains();+if(cpus_updated){+if(use_cpu_hp_lock)+rebuild_sched_domains();+else{+/* When called during cpu hotplug cpu_hotplug_lock+*isheldbythecallingthread,not+*notcpuhp_thread_fun+*/+mutex_lock(&cpuset_mutex);+rebuild_sched_domains_cpuslocked();+mutex_unlock(&cpuset_mutex);+}}}-voidcpuset_update_active_cpus(void)+staticvoidcpuset_hotplug_workfn(structwork_struct*work){-/*-*We'reinsidecpuhotplugcriticalregionwhichusuallynests-*insidecgroupsynchronization.Bounceactualhotplugprocessing-*toaworkitemtoavoidreverselockingorder.-*/-schedule_work(&cpuset_hotplug_work);+cpuset_hotplug(true);}-voidcpuset_wait_for_hotplug(void)+voidcpuset_update_active_cpus(void){-flush_work(&cpuset_hotplug_work);+cpuset_hotplug(false);}/*
@@ -203,8 +203,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc.,
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
From: Waiman Long <longman@redhat.com> Date: 2017-10-26 14:05:16
On 10/26/2017 07:52 AM, Prateek Sood wrote:
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
wq_update_unbound_numa();
kthread_create_on_node();
wake_up_process(); //wakeup kthreadd
flush_work();
wait_for_completion();
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock.
General comments:
Please add a version number of your patch. I have seen multiple versions
of this patch and have lost track how many are there as there is no
version number information. In addition, there are changes beyond just
swapping the lock order and they are not documented in this change log.
I would like to see you discuss about those additional changes here as well.
I saw a lot of instances where cpus_read_lock() and mutex_lock() come
together. Maybe some new lock/unlock helper functions may help.
quoted hunk
@@ -2356,25 +2354,29 @@ static void cpuset_hotplug_workfn(struct work_struct *work) } /* rebuild sched domains if cpus_allowed has changed */- if (cpus_updated || force_rebuild) {- force_rebuild = false;- rebuild_sched_domains();+ if (cpus_updated) {+ if (use_cpu_hp_lock)+ rebuild_sched_domains();+ else {+ /* When called during cpu hotplug cpu_hotplug_lock+ * is held by the calling thread, not+ * not cpuhp_thread_fun+ */
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
wq_update_unbound_numa();
kthread_create_on_node();
wake_up_process(); //wakeup kthreadd
flush_work();
wait_for_completion();
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock.
General comments:
Please add a version number of your patch. I have seen multiple versions
of this patch and have lost track how many are there as there is no
version number information. In addition, there are changes beyond just
swapping the lock order and they are not documented in this change log.
I would like to see you discuss about those additional changes here as well.
Thanks for the comments Longman. I will introduce patch versioning and update
commit text to document extra changes.
Explaintaion for extra changes in this patch:
After inverting the locking sequence of cpu_hotplug_lock and cpuset_mutex,
cpuset_hotplug_workfn() related functionality can be done synchronously from
the context doing cpu hotplug. Extra changes in this patch intend to remove
queuing of cpuset_hotplug_workfn() as a work item for cpu hotplug path. For
memory hotplug it still gets queued as a work item.
This suggestion came in from Peter.
Peter could you please elaborate if I have missed anything.
I saw a lot of instances where cpus_read_lock() and mutex_lock() come
together. Maybe some new lock/unlock helper functions may help.
Ok, I will introduce a single wrapper for locking and unlocking
of both locks
quoted
@@ -2356,25 +2354,29 @@ static void cpuset_hotplug_workfn(struct work_struct *work) } /* rebuild sched domains if cpus_allowed has changed */- if (cpus_updated || force_rebuild) {- force_rebuild = false;- rebuild_sched_domains();+ if (cpus_updated) {+ if (use_cpu_hp_lock)+ rebuild_sched_domains();+ else {+ /* When called during cpu hotplug cpu_hotplug_lock+ * is held by the calling thread, not+ * not cpuhp_thread_fun+ */
??? The comment is not clear.
Following is the scenario that is described by the comment
Process A
_cpu_down()
cpus_write_lock() //cpu_hotplug_lock held
cpuhp_kick_ap_work()
cpuhp_kick_ap()
wake_up_process() // wake up cpuhp_thread_fun
wait_for_ap_thread() //wait for hotplug thread to signal completion
cpuhp_thread_fun()
cpuhp_invoke_callback()
sched_cpu_deactivate()
cpuset_cpu_inactive()
cpuset_update_active_cpus()
cpuset_hotplug(false) \\ do not use cpu_hotplug_lock from _cpu_down() path
I will update the comment in next version of patch to elaborate more.
Cheers,
Longman
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
queue_work_on(); // unbind_work on system_highpri_wq
__queue_work();
insert_work();
wake_up_worker();
flush_work();
wait_for_completion();
worker_thread();
manage_workers();
create_worker();
kthread_create_on_node();
wake_up_process(kthreadd_task);
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock. After inverting the locking sequence of cpu_hotplug_lock
and cpuset_mutex, cpuset_hotplug_workfn() related functionality can be
done synchronously from the context doing cpu hotplug. For memory hotplug
it still gets queued as a work item.
Signed-off-by: Prateek Sood <redacted>
---
include/linux/cpuset.h | 6 ----
kernel/cgroup/cpuset.c | 94 +++++++++++++++++++++++++++-----------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 50 insertions(+), 53 deletions(-)
@@ -203,8 +203,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc.,
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
queue_work_on(); // unbind_work on system_highpri_wq
__queue_work();
insert_work();
wake_up_worker();
flush_work();
wait_for_completion();
worker_thread();
manage_workers();
create_worker();
kthread_create_on_node();
wake_up_process(kthreadd_task);
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock. After inverting the locking sequence of cpu_hotplug_lock
and cpuset_mutex, cpuset_hotplug_workfn() related functionality can be
done synchronously from the context doing cpu hotplug. For memory hotplug
it still gets queued as a work item.
Signed-off-by: Prateek Sood <redacted>
---
include/linux/cpuset.h | 6 ----
kernel/cgroup/cpuset.c | 94 +++++++++++++++++++++++++++-----------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 50 insertions(+), 53 deletions(-)
@@ -203,8 +203,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */
Hi Folks,
Are there any more feedbacks/suggestions to improve this patch?
Thanks
PS
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
queue_work_on(); // unbind_work on system_highpri_wq
__queue_work();
insert_work();
wake_up_worker();
flush_work();
wait_for_completion();
worker_thread();
manage_workers();
create_worker();
kthread_create_on_node();
wake_up_process(kthreadd_task);
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock. After inverting the locking sequence of cpu_hotplug_lock
and cpuset_mutex, cpuset_hotplug_workfn() related functionality can be
done synchronously from the context doing cpu hotplug. For memory hotplug
it still gets queued as a work item.
Signed-off-by: Prateek Sood <redacted>
---
include/linux/cpuset.h | 6 ----
kernel/cgroup/cpuset.c | 94 +++++++++++++++++++++++++++-----------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 50 insertions(+), 53 deletions(-)
@@ -203,8 +203,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */
Any improvement/suggestion for this patch?
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
From: Peter Zijlstra <peterz@infradead.org> Date: 2017-11-15 10:37:57
On Wed, Nov 15, 2017 at 03:56:26PM +0530, Prateek Sood wrote:
Any improvement/suggestion for this patch?
I would have done 2 patches, the first one solving the locking issue,
the second removing the then redundant async rebuild stuff.
Other than that this looks OK I suppose, but I was expecting this to go
through the cgroup tree, TJ?
This patch does following
1- Remove circular dependency deadlock by inverting order of
cpu_hotplug_lock and cpuset_mutex.
2- Make cpuset_hotplug_workfn() synchronous for cpu hotplug path.
For memory hotplug path it still gets queued as a work item.
Prateek Sood (2):
cgroup/cpuset: remove circular dependency deadlock
cpuset: Make cpuset hotplug synchronous
include/linux/cpuset.h | 6 ----
kernel/cgroup/cpuset.c | 94 +++++++++++++++++++++++++++-----------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 50 insertions(+), 53 deletions(-)
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc.,
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
Remove circular dependency deadlock in a scenario where hotplug of CPU is
being done while there is updation in cgroup and cpuset triggered from
userspace.
Process A => kthreadd => Process B => Process C => Process A
Process A
cpu_subsys_offline();
cpu_down();
_cpu_down();
percpu_down_write(&cpu_hotplug_lock); //held
cpuhp_invoke_callback();
workqueue_offline_cpu();
queue_work_on(); // unbind_work on system_highpri_wq
__queue_work();
insert_work();
wake_up_worker();
flush_work();
wait_for_completion();
worker_thread();
manage_workers();
create_worker();
kthread_create_on_node();
wake_up_process(kthreadd_task);
kthreadd
kthreadd();
kernel_thread();
do_fork();
copy_process();
percpu_down_read(&cgroup_threadgroup_rwsem);
__rwsem_down_read_failed_common(); //waiting
Process B
kernfs_fop_write();
cgroup_file_write();
cgroup_procs_write();
percpu_down_write(&cgroup_threadgroup_rwsem); //held
cgroup_attach_task();
cgroup_migrate();
cgroup_migrate_execute();
cpuset_can_attach();
mutex_lock(&cpuset_mutex); //waiting
Process C
kernfs_fop_write();
cgroup_file_write();
cpuset_write_resmask();
mutex_lock(&cpuset_mutex); //held
update_cpumask();
update_cpumasks_hier();
rebuild_sched_domains_locked();
get_online_cpus();
percpu_down_read(&cpu_hotplug_lock); //waiting
Eliminating deadlock by reversing the locking order for cpuset_mutex and
cpu_hotplug_lock.
Signed-off-by: Prateek Sood <redacted>
---
kernel/cgroup/cpuset.c | 53 ++++++++++++++++++++++++++++----------------------
1 file changed, 30 insertions(+), 23 deletions(-)
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc.,
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
Convert cpuset_hotplug_workfn() into synchronous call for cpu hotplug
path. For memory hotplug path it still gets queued as a work item.
Since cpuset_hotplug_workfn() can be made synchronous for cpu hotplug
path, it is not required to wait for cpuset hotplug while thawing
processes.
Signed-off-by: Prateek Sood <redacted>
---
include/linux/cpuset.h | 6 ------
kernel/cgroup/cpuset.c | 41 ++++++++++++++++++++---------------------
kernel/power/process.c | 2 --
kernel/sched/core.c | 1 -
4 files changed, 20 insertions(+), 30 deletions(-)
@@ -204,8 +204,6 @@ void thaw_processes(void)__usermodehelper_set_disable_depth(UMH_FREEZING);thaw_workqueues();-cpuset_wait_for_hotplug();-read_lock(&tasklist_lock);for_each_process_thread(g,p){/* No other threads should have PF_SUSPEND_TASK set */
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc.,
is a member of Code Aurora Forum, a Linux Foundation Collaborative Project.
On Wed, Nov 15, 2017 at 11:37:42AM +0100, Peter Zijlstra wrote:
On Wed, Nov 15, 2017 at 03:56:26PM +0530, Prateek Sood wrote:
quoted
Any improvement/suggestion for this patch?
I would have done 2 patches, the first one solving the locking issue,
the second removing the then redundant async rebuild stuff.
Other than that this looks OK I suppose, but I was expecting this to go
through the cgroup tree, TJ?
On Wed, Nov 15, 2017 at 11:37:42AM +0100, Peter Zijlstra wrote:
quoted
On Wed, Nov 15, 2017 at 03:56:26PM +0530, Prateek Sood wrote:
quoted
Any improvement/suggestion for this patch?
I would have done 2 patches, the first one solving the locking issue,
the second removing the then redundant async rebuild stuff.
Other than that this looks OK I suppose, but I was expecting this to go
through the cgroup tree, TJ?
Will pick them up after -rc1.
Thanks.
I have made two patches as suggested by Peter and sent for review.
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation
Center, Inc., is a member of Code Aurora Forum, a Linux Foundation
Collaborative Project
On Wed, Nov 15, 2017 at 07:50:13PM +0530, Prateek Sood wrote:
This patch does following
1- Remove circular dependency deadlock by inverting order of
cpu_hotplug_lock and cpuset_mutex.
2- Make cpuset_hotplug_workfn() synchronous for cpu hotplug path.
For memory hotplug path it still gets queued as a work item.
Prateek Sood (2):
cgroup/cpuset: remove circular dependency deadlock
cpuset: Make cpuset hotplug synchronous
Applied to cgroup/for-4.15-fixes.
Thanks.
--
tejun