* SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
@ 2026-04-22 13:21 Kuba Piecuch
2026-04-22 16:50 ` Tejun Heo
0 siblings, 1 reply; 8+ messages in thread
From: Kuba Piecuch @ 2026-04-22 13:21 UTC (permalink / raw)
To: Tejun Heo, Andrea Righi, Changwoo Min, David Vernet
Cc: linux-kernel, sched-ext
Hi folks,
I recently saw that scx_qmap got rid of the sched_switch tracepoint hook,
claiming that SCX_OPS_ALWAYS_ENQ_IMMED is sufficient to keep tasks from
lingering on local DSQs.
This prompted me to think about some possible edge cases, and I think we
can end up with lingering tasks on the local DSQ in the following scenario:
Initial conditions: rq->curr == rq->idle &&
rq->next_class == &idle_sched_class
1. We enter schedule() for whatever reason, e.g. BPF scheduler kick from
another CPU.
2. In __pick_next_task(), all sched classes above SCX fail to pick a task.
We still have rq->next_class == &idle_sched_class.
3. We enter do_pick_task_scx(). rq_modified_begin() does nothing because
sched_class_above(rq->next_class, &ext_sched_class) is false.
4. ops.dispatch() dispatches two tasks. The first one goes to the local DSQ,
and the second one goes to a remote CPU's local DSQ. The first task is
dispatched without interference.
5. During dispatch of the second task, while the local CPU's rq lock is dropped
during insertion into the remote CPU's local DSQ, an RT task wakes up on the
local CPU. Since rq->next_class is still idle, wakeup_preempt() calls
wakeup_preempt_idle() which calls resched_curr(rq). This effectively does
nothing since need_resched is cleared in __schedule() after pick.
rq->next_class is set to &rt_sched_class.
6. At the end of balance_one(), we don't trigger a reenqueue because the local
DSQ has only one task.
7. do_pick_task_scx() notices rq_modified_above(rq, &ext_sched_class) and
returns RETRY_TASK.
8. The RT task ends up being picked and runs. SCX is not notified of the switch
because we're switching from the idle task to an RT task.
If my understanding is correct and I didn't miss anything important, then
at no point does SCX reenqueue the first task, even though it should.
This particular scenario may not apply to scx_qmap, but I think it proves that
it's possible to have dispatched tasks lingering on the local DSQ even with
SCX_OPS_ALWAYS_ENQ_IMMED.
I was thinking we could fix this by adding a nr_immed check right before
returning RETRY_TASK:
diff --git i/kernel/sched/ext.c w/kernel/sched/ext.c
index d66fea57ee69..480627fdc203 100644
--- i/kernel/sched/ext.c
+++ w/kernel/sched/ext.c
@@ -3079,8 +3079,11 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
* If @force_scx is true, always try to pick a SCHED_EXT task,
* regardless of any higher-priority sched classes activity.
*/
- if (!force_scx && rq_modified_above(rq, &ext_sched_class))
+ if (!force_scx && rq_modified_above(rq, &ext_sched_class)) {
+ if (rq->scx.nr_immed)
+ schedule_reenq_local(rq, 0);
return RETRY_TASK;
+ }
keep_prev = rq->scx.flags & SCX_RQ_BAL_KEEP;
if (unlikely(keep_prev &&
...but I think this only fixes the case where the RT task wakes up on the CPU
that is doing the dispatch. The other case is one where the RT task wakes up
on the remote CPU (the one the second task was dispatched to) after insertion
of the second task, assuming the remote CPU is initially idle.
To fix both cases, one potential solution that comes to mind is bumping
rq->next_class to &ext_sched_class when inserting a task into rq->scx.local_dsq.
Perhaps we should call wakeup_preempt() in dispatch_to_local_dsq()?
Let me know what you think!
Thanks,
Kuba
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-22 13:21 SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs Kuba Piecuch
@ 2026-04-22 16:50 ` Tejun Heo
2026-04-23 9:48 ` Kuba Piecuch
0 siblings, 1 reply; 8+ messages in thread
From: Tejun Heo @ 2026-04-22 16:50 UTC (permalink / raw)
To: Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
Hello, Kuba.
On Wed, Apr 22, 2026 at 01:21:27PM +0000, Kuba Piecuch wrote:
> diff --git i/kernel/sched/ext.c w/kernel/sched/ext.c
> index d66fea57ee69..480627fdc203 100644
> --- i/kernel/sched/ext.c
> +++ w/kernel/sched/ext.c
> @@ -3079,8 +3079,11 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
> * If @force_scx is true, always try to pick a SCHED_EXT task,
> * regardless of any higher-priority sched classes activity.
> */
> - if (!force_scx && rq_modified_above(rq, &ext_sched_class))
> + if (!force_scx && rq_modified_above(rq, &ext_sched_class)) {
> + if (rq->scx.nr_immed)
> + schedule_reenq_local(rq, 0);
> return RETRY_TASK;
> + }
>
> keep_prev = rq->scx.flags & SCX_RQ_BAL_KEEP;
> if (unlikely(keep_prev &&
Ah, good catch.
> ...but I think this only fixes the case where the RT task wakes up on the CPU
> that is doing the dispatch. The other case is one where the RT task wakes up
> on the remote CPU (the one the second task was dispatched to) after insertion
> of the second task, assuming the remote CPU is initially idle.
>
> To fix both cases, one potential solution that comes to mind is bumping
> rq->next_class to &ext_sched_class when inserting a task into rq->scx.local_dsq.
> Perhaps we should call wakeup_preempt() in dispatch_to_local_dsq()?
I think what's missing is wakeup_preempt() call in
move_remote_task_to_local_dsq(). This is SCX's version of move_queued_task()
and we're missing wakeup_preempt() call after activate_task().
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-22 16:50 ` Tejun Heo
@ 2026-04-23 9:48 ` Kuba Piecuch
2026-04-23 16:53 ` Tejun Heo
0 siblings, 1 reply; 8+ messages in thread
From: Kuba Piecuch @ 2026-04-23 9:48 UTC (permalink / raw)
To: Tejun Heo, Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
Hi Tejun,
On Wed Apr 22, 2026 at 4:50 PM UTC, Tejun Heo wrote:
> Hello, Kuba.
>
> On Wed, Apr 22, 2026 at 01:21:27PM +0000, Kuba Piecuch wrote:
>> diff --git i/kernel/sched/ext.c w/kernel/sched/ext.c
>> index d66fea57ee69..480627fdc203 100644
>> --- i/kernel/sched/ext.c
>> +++ w/kernel/sched/ext.c
>> @@ -3079,8 +3079,11 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
>> * If @force_scx is true, always try to pick a SCHED_EXT task,
>> * regardless of any higher-priority sched classes activity.
>> */
>> - if (!force_scx && rq_modified_above(rq, &ext_sched_class))
>> + if (!force_scx && rq_modified_above(rq, &ext_sched_class)) {
>> + if (rq->scx.nr_immed)
>> + schedule_reenq_local(rq, 0);
>> return RETRY_TASK;
>> + }
>>
>> keep_prev = rq->scx.flags & SCX_RQ_BAL_KEEP;
>> if (unlikely(keep_prev &&
>
> Ah, good catch.
>
>> ...but I think this only fixes the case where the RT task wakes up on the CPU
>> that is doing the dispatch. The other case is one where the RT task wakes up
>> on the remote CPU (the one the second task was dispatched to) after insertion
>> of the second task, assuming the remote CPU is initially idle.
>>
>> To fix both cases, one potential solution that comes to mind is bumping
>> rq->next_class to &ext_sched_class when inserting a task into rq->scx.local_dsq.
>> Perhaps we should call wakeup_preempt() in dispatch_to_local_dsq()?
>
> I think what's missing is wakeup_preempt() call in
> move_remote_task_to_local_dsq(). This is SCX's version of move_queued_task()
> and we're missing wakeup_preempt() call after activate_task().
I'm not sure if we should limit ourselves to just remote tasks.
If we call wakeup_preempt(rq, p, flags) when adding @p to @rq's local DSQ
regardless of whether @p/@rq is remote, then I think that should cover all
cases and the patch above wouldn't be needed.
Perhaps local_dsq_post_enq() would be a good place to invoke wakeup_preempt()?
Thanks,
Kuba
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-23 9:48 ` Kuba Piecuch
@ 2026-04-23 16:53 ` Tejun Heo
2026-04-23 19:12 ` Kuba Piecuch
0 siblings, 1 reply; 8+ messages in thread
From: Tejun Heo @ 2026-04-23 16:53 UTC (permalink / raw)
To: Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
Hello,
On Thu, Apr 23, 2026 at 09:48:11AM +0000, Kuba Piecuch wrote:
> I'm not sure if we should limit ourselves to just remote tasks.
> If we call wakeup_preempt(rq, p, flags) when adding @p to @rq's local DSQ
> regardless of whether @p/@rq is remote, then I think that should cover all
> cases and the patch above wouldn't be needed.
Sorry, I wasn't clear. I meant adding wakeup_preempt() in
move_remote_task_to_local_dsq() *in addition to* your nr_immed/RETRY_TASK
patch. That combination covers both cases:
- Local (your stepwise scenario): the task stays on the dispatching CPU, no
migration happens, so there's no insertion-side hook to arm. Your nr_immed
check in do_pick_task_scx() catches it on the RETRY_TASK path.
- Remote: move_remote_task_to_local_dsq() is SCX's analog of
move_queued_task() and is missing the wakeup_preempt() call that
move_queued_task() does after activate_task(). Once added,
dst_rq->next_class is bumped to &ext_sched_class, and any subsequent
higher-class wakeup on dst_rq routes through wakeup_preempt_scx() which
already has the nr_immed reenqueue.
Adding wakeup_preempt() to local_dsq_post_enq() for every insertion would
work too, but I'd rather keep it in sync with how core sched handles
move_queued_task() and only add it where it's actually needed.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-23 16:53 ` Tejun Heo
@ 2026-04-23 19:12 ` Kuba Piecuch
2026-04-23 19:29 ` Tejun Heo
0 siblings, 1 reply; 8+ messages in thread
From: Kuba Piecuch @ 2026-04-23 19:12 UTC (permalink / raw)
To: Tejun Heo, Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
On Thu Apr 23, 2026 at 4:53 PM UTC, Tejun Heo wrote:
> Hello,
>
> On Thu, Apr 23, 2026 at 09:48:11AM +0000, Kuba Piecuch wrote:
>> I'm not sure if we should limit ourselves to just remote tasks.
>> If we call wakeup_preempt(rq, p, flags) when adding @p to @rq's local DSQ
>> regardless of whether @p/@rq is remote, then I think that should cover all
>> cases and the patch above wouldn't be needed.
>
> Sorry, I wasn't clear. I meant adding wakeup_preempt() in
> move_remote_task_to_local_dsq() *in addition to* your nr_immed/RETRY_TASK
> patch. That combination covers both cases:
>
> - Local (your stepwise scenario): the task stays on the dispatching CPU, no
> migration happens, so there's no insertion-side hook to arm. Your nr_immed
> check in do_pick_task_scx() catches it on the RETRY_TASK path.
>
> - Remote: move_remote_task_to_local_dsq() is SCX's analog of
> move_queued_task() and is missing the wakeup_preempt() call that
> move_queued_task() does after activate_task(). Once added,
> dst_rq->next_class is bumped to &ext_sched_class, and any subsequent
> higher-class wakeup on dst_rq routes through wakeup_preempt_scx() which
> already has the nr_immed reenqueue.
There's another case we need to handle: dispatching to a remote CPU's
local DSQ, but without migrating the task. In dispatch_to_local_dsq()
this corresponds to rq != src_rq && src_rq == dst_rq. The check for the local
case on the remote CPU doesn't cover it because an RT task can swoop in and
prevent pick_task_scx() from running (the assumption is that the remote CPU
is idle).
The check for the remote case doesn't cover it either because we do
dispatch_enqueue() directly from dispatch_to_local_dsq().
Then there's the same case, but instead of dispatching we're moving the task
from a user DSQ. If my understanding of the code is correct, the path is:
scx_dsq_move(p, dsq_id);
raw_spin_rq_unlock(this_rq);
raw_spin_lock(src_rq);
dst_dsq = find_dsq_for_dispatch(dsq_id);
move_task_between_dsqs(p, src_dsq, dst_dsq);
dst_rq = container_of(dst_dsq, struct rq, scx.local_dsq);
task_unlink_from_dsq(p, src_dsq);
move_local_task_to_local_dsq(p, src_dsq, dst_rq);
I think these two cases show that we don't necessarily have to migrate a task
for wakeup_preempt() to be warranted.
So to cover all cases (that I'm aware of), we need four checks:
* wakeup_preempt() in dispatch_to_local_dsq() in the case of rq != src_rq &&
src_rq == dst_rq
* wakeup_preempt() at the end of move_remote_task_to_local_dsq()
* wakeup_preempt() somewhere on the scx_dsq_move() path, but only if we're
moving the task to the local DSQ of a remote CPU (?)
* nr_immed check before returning RETRY_TASK
>
> Adding wakeup_preempt() to local_dsq_post_enq() for every insertion would
> work too, but I'd rather keep it in sync with how core sched handles
> move_queued_task() and only add it where it's actually needed.
Does having four separate cases to handle, not all of which involve task
migration, change that calculus somewhat?
I believe all of these cases will be handled if we add wakeup_preempt()
to local_dsq_post_enq().
Thanks,
Kuba
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-23 19:12 ` Kuba Piecuch
@ 2026-04-23 19:29 ` Tejun Heo
2026-04-23 20:03 ` Kuba Piecuch
0 siblings, 1 reply; 8+ messages in thread
From: Tejun Heo @ 2026-04-23 19:29 UTC (permalink / raw)
To: Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
Hello,
On Thu, Apr 23, 2026 at 07:12:18PM +0000, Kuba Piecuch wrote:
...
> I think these two cases show that we don't necessarily have to migrate a task
> for wakeup_preempt() to be warranted.
I see.
> So to cover all cases (that I'm aware of), we need four checks:
>
> * wakeup_preempt() in dispatch_to_local_dsq() in the case of rq != src_rq &&
> src_rq == dst_rq
>
> * wakeup_preempt() at the end of move_remote_task_to_local_dsq()
>
> * wakeup_preempt() somewhere on the scx_dsq_move() path, but only if we're
> moving the task to the local DSQ of a remote CPU (?)
>
> * nr_immed check before returning RETRY_TASK
>
> > Adding wakeup_preempt() to local_dsq_post_enq() for every insertion would
> > work too, but I'd rather keep it in sync with how core sched handles
> > move_queued_task() and only add it where it's actually needed.
>
> Does having four separate cases to handle, not all of which involve task
> migration, change that calculus somewhat?
> I believe all of these cases will be handled if we add wakeup_preempt()
> to local_dsq_post_enq().
If we call wakeup_preempt() unconditionally, we'd be calling
wakeup_preempt_scx() on every dispatch, which isn't too appealing. We can
add an ENQ flag to mark these sites specifically but I'm not sure whether
that's necessarily better. The invovled code paths are inherently subtle and
finicky, so might as well just add the calls where they're necessary. What
do you think?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-23 19:29 ` Tejun Heo
@ 2026-04-23 20:03 ` Kuba Piecuch
2026-04-23 21:57 ` Tejun Heo
0 siblings, 1 reply; 8+ messages in thread
From: Kuba Piecuch @ 2026-04-23 20:03 UTC (permalink / raw)
To: Tejun Heo, Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
On Thu Apr 23, 2026 at 7:29 PM UTC, Tejun Heo wrote:
> Hello,
>
> On Thu, Apr 23, 2026 at 07:12:18PM +0000, Kuba Piecuch wrote:
> ...
>> I think these two cases show that we don't necessarily have to migrate a task
>> for wakeup_preempt() to be warranted.
>
> I see.
>
>> So to cover all cases (that I'm aware of), we need four checks:
>>
>> * wakeup_preempt() in dispatch_to_local_dsq() in the case of rq != src_rq &&
>> src_rq == dst_rq
>>
>> * wakeup_preempt() at the end of move_remote_task_to_local_dsq()
>>
>> * wakeup_preempt() somewhere on the scx_dsq_move() path, but only if we're
>> moving the task to the local DSQ of a remote CPU (?)
>>
>> * nr_immed check before returning RETRY_TASK
>>
>> > Adding wakeup_preempt() to local_dsq_post_enq() for every insertion would
>> > work too, but I'd rather keep it in sync with how core sched handles
>> > move_queued_task() and only add it where it's actually needed.
>>
>> Does having four separate cases to handle, not all of which involve task
>> migration, change that calculus somewhat?
>> I believe all of these cases will be handled if we add wakeup_preempt()
>> to local_dsq_post_enq().
>
> If we call wakeup_preempt() unconditionally, we'd be calling
> wakeup_preempt_scx() on every dispatch, which isn't too appealing. We can
> add an ENQ flag to mark these sites specifically but I'm not sure whether
> that's necessarily better. The invovled code paths are inherently subtle and
> finicky, so might as well just add the calls where they're necessary. What
> do you think?
I don't like the idea of maintaining the separate checks, precisely because
of how subtle and finnicky they are. I worry that it will be difficult to keep
all cases covered as the codebase evolves.
As an optimization, we could skip wakeup_preempt() in local_dsq_post_enq() if
rq->next_class == &ext_sched_class. That should make the performance impact
negligible in the common case, WDYT?
Thanks,
Kuba
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs
2026-04-23 20:03 ` Kuba Piecuch
@ 2026-04-23 21:57 ` Tejun Heo
0 siblings, 0 replies; 8+ messages in thread
From: Tejun Heo @ 2026-04-23 21:57 UTC (permalink / raw)
To: Kuba Piecuch
Cc: Andrea Righi, Changwoo Min, David Vernet, linux-kernel, sched-ext
Hello,
On Thu, Apr 23, 2026 at 08:03:51PM +0000, Kuba Piecuch wrote:
...
> I don't like the idea of maintaining the separate checks, precisely because
> of how subtle and finnicky they are. I worry that it will be difficult to keep
> all cases covered as the codebase evolves.
>
> As an optimization, we could skip wakeup_preempt() in local_dsq_post_enq() if
> rq->next_class == &ext_sched_class. That should make the performance impact
> negligible in the common case, WDYT?
Yeah, let's do that. Please send a patch and can you also cc PeterZ?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-04-23 21:57 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-22 13:21 SCX_ENQ_IMMED potentially leaving dispatched tasks lingering on local DSQs Kuba Piecuch
2026-04-22 16:50 ` Tejun Heo
2026-04-23 9:48 ` Kuba Piecuch
2026-04-23 16:53 ` Tejun Heo
2026-04-23 19:12 ` Kuba Piecuch
2026-04-23 19:29 ` Tejun Heo
2026-04-23 20:03 ` Kuba Piecuch
2026-04-23 21:57 ` Tejun Heo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox