Skip to content

Commit 515e399

Browse files
committed
sched_ext: Fix deadlock between scx_root_disable() and concurrent forks
scx_root_disable() enters SCX_DISABLING before it grabs scx_enable_mutex to clear __scx_switched_all and scx_switching_all. task_should_scx() short-circuits on DISABLING, so forks in that window land on fair while next_active_class() still skips fair - the new tasks stall. This can deadlock the disable path itself: scx_alloc_and_add_sched() runs under scx_enable_mutex and creates a helper kthread; if that new kthread is one of the stalled fair tasks, the mutex holder waits forever and scx_root_disable() can never make progress. Only sub-sched support exposes this, since sub-sched enables are the only path where scx_alloc_and_add_sched() can race the root's disable. Move the DISABLING check after @scx_switching_all. @scx_switching_all serves as a proxy for __scx_switched_all, so while it's set, forks keep going to scx. Once cleared, DISABLING applies normally. v2: Reword in-source comment and description. (Andrea) Fixes: 337ec00 ("sched_ext: Implement cgroup sub-sched enabling and disabling") Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
1 parent 6ae315d commit 515e399

1 file changed

Lines changed: 21 additions & 1 deletion

File tree

kernel/sched/ext.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4946,10 +4946,30 @@ static const struct kset_uevent_ops scx_uevent_ops = {
49464946
*/
49474947
bool task_should_scx(int policy)
49484948
{
4949-
if (!scx_enabled() || unlikely(scx_enable_state() == SCX_DISABLING))
4949+
/* if disabled, nothing should be on it */
4950+
if (!scx_enabled())
49504951
return false;
4952+
4953+
/* scx is taking over all SCHED_OTHER and SCHED_EXT tasks */
49514954
if (READ_ONCE(scx_switching_all))
49524955
return true;
4956+
4957+
/*
4958+
* scx is tearing down - keep new SCHED_EXT tasks out.
4959+
*
4960+
* Must come after scx_switching_all test, which serves as a proxy
4961+
* for __scx_switched_all. While __scx_switched_all is set, we must
4962+
* return true via the branch above: a fork routed to fair would
4963+
* stall because next_active_class() skips fair.
4964+
*
4965+
* This can develop into a deadlock - scx holds scx_enable_mutex across
4966+
* kthread_create() in scx_alloc_and_add_sched(); if the new kthread is
4967+
* the stalled task, the disable path can never grab the mutex to clear
4968+
* scx_switching_all.
4969+
*/
4970+
if (unlikely(scx_enable_state() == SCX_DISABLING))
4971+
return false;
4972+
49534973
return policy == SCHED_EXT;
49544974
}
49554975

0 commit comments

Comments
 (0)