[PATCH] sched_ext: Fix deadlock with PSI trigger creation
Matt Fleming <[email protected]>
| Newsgroups | dev.linux.lists.sched-ext,org.kernel.vger.cgroups,org.kernel.vger.linux-kernel,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
From: Matt Fleming <[email protected]> scx_root_enable_workfn() currently takes scx_fork_rwsem for writing before acquiring cgroup_mutex. Since commit a5b98009f16d ("sched/psi: fix race between file release and pressure write"), pressure_write() holds cgroup_mutex across psi_trigger_create(), which may call kthread_create() for the psimon kthread. kthreadd's fork then enters scx_pre_fork() and waits for the read side of scx_fork_rwsem. This results in a deadlock. The enable worker holds scx_fork_rwsem and waits for cgroup_mutex, while the PSI writer holds cgroup_mutex and waits for psimon creation to complete. Any concurrent fork blocks on scx_pre_fork() behind the enable worker. The hung-task detector captured all three sides of the deadlock: scx_enable_help: __mutex_lock scx_enable_workfn kthread_worker_fn systemd: wait_for_completion_killable __kthread_create_on_node kthread_create_on_node psi_trigger_create pressure_write kernfs_fop_write_iter python3: percpu_rwsem_wait __percpu_down_read scx_pre_fork sched_fork copy_process kernel_clone It also identified systemd as the likely owner of the mutex on which scx_enable_help was blocked. We reproduced this on a 128-CPU AMD EPYC 7713 by enabling scx_lavd concurrently with writes to cgroup PSI trigger files. Unrelated tasks piled up in scx_pre_fork() and process creation on the box stopped. Fix the inversion by acquiring cgroup_mutex before scx_fork_rwsem in scx_root_enable_workfn() and releasing them in reverse order, while preserving the existing exclusion around cgroup and task initialisation. Fixes: a5b98009f16d ("sched/psi: fix race between file release and pressure write") Cc: [email protected] Signed-off-by: Matt Fleming <[email protected]> --- kernel/sched/ext/ext.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 691d53fe0f64..ba89eafe7964 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7193,7 +7193,10 @@ static void scx_root_enable_workfn(struct kthread_work *work) /* * Lock out forks, cgroup on/offlining and moves before opening the * floodgate so that they don't wander into the operations prematurely. + * cgroup_mutex must nest outside scx_fork_rwsem because cgroup file + * operations may create kthreads while holding cgroup_mutex. */ + scx_cgroup_lock(); percpu_down_write(&scx_fork_rwsem); WARN_ON_ONCE(scx_init_task_enabled); @@ -7216,7 +7219,6 @@ static void scx_root_enable_workfn(struct kthread_work *work) * while tasks are being initialized so that scx_cgroup_can_attach() * never sees uninitialized tasks. */ - scx_cgroup_lock(); set_cgroup_sched(sch_cgroup(sch), sch); ret = scx_cgroup_init(sch); if (ret) @@ -7283,8 +7285,8 @@ static void scx_root_enable_workfn(struct kthread_work *work) put_task_struct(p); } scx_task_iter_stop(&sti); - scx_cgroup_unlock(); percpu_up_write(&scx_fork_rwsem); + scx_cgroup_unlock(); /* * All tasks are READY. It's safe to turn on scx_enabled() and switch @@ -7369,8 +7371,8 @@ static void scx_root_enable_workfn(struct kthread_work *work) return; err_disable_unlock_all: - scx_cgroup_unlock(); percpu_up_write(&scx_fork_rwsem); + scx_cgroup_unlock(); /* we'll soon enter disable path, keep bypass on */ err_disable: mutex_unlock(&scx_enable_mutex); -- 2.43.0