[PATCH] sched_ext: Fix deadlock with PSI trigger creation
From: Matt Fleming
Date: Fri Jul 10 2026 - 06:08:43 EST
From: Matt Fleming <mfleming@xxxxxxxxxxxxxx>
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: stable@xxxxxxxxxxxxxxx
Signed-off-by: Matt Fleming <mfleming@xxxxxxxxxxxxxx>
---
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