From: sashiko-bot@kernel.org
To: "Tejun Heo" <tj@kernel.org>, sched-ext@lists.linux.dev
Subject: Re: [PATCH v2 sched_ext/for-7.4-fixes] sched_ext: Fix stale pick when a wakeup resets the donor during dispatch
Date: Sat, 31 Oct 2026 00:29:44 +0000 [thread overview]
Message-ID: <sashiko-outbox-191676@kernel.org> (raw)
In-Reply-To: <20261030235728.4098872-1-tj@kernel.org>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] sched_ext: unguarded keep-prev check in scx_dispatch_sched()
- [Medium] sched_ext: premature cpu_acquire() before stale donor check in dispatch_one()
- [Medium] sched: stale rq->dl_server across RETRY_TASK in __pick_next_task()
--
commit da4492c7a1c1f5575c5658737f248cb38f6f4291
Author: Tejun Heo <tj@kernel.org>
sched_ext: Fix stale pick when a wakeup resets the donor during dispatch
This commit restarts the sched_ext pick with SCX_DSP_RETRY when a wakeup or
sched_proxy_block_task() resets rq->donor while dispatch_one() or
scx_dispatch_sched() has the rq lock dropped. It also updates
scx_dispatch_sched() to re-evaluate whether prev is still the donor and on
the target scheduler before passing prev to ops.dispatch().
> diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> index ffaab9fafca87..fe1be3d9444dd 100644
> --- a/kernel/sched/ext/ext.c
> +++ b/kernel/sched/ext/ext.c
> @@ -3407,6 +3407,12 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev
> rq->scx.cpu_released = false;
> }
>
> + /* the ecaps sync may have dropped the rq lock */
> + if (scx_dsp_prev_stale(rq, prev)) {
> + verdict = SCX_DSP_RETRY;
> + goto out;
> + }
[Severity: Medium]
Should the scx_dsp_prev_stale() check happen immediately after
scx_process_sync_ecaps(), before checking rq->scx.cpu_released and invoking
ops.cpu_acquire()?
If scx_process_sync_ecaps() drops rq->__lock and another CPU wakes the SCX
proxy donor while rq->curr belongs to a higher scheduling class,
proxy_needs_return() calls proxy_reset_donor():
proxy_needs_return()
proxy_reset_donor()
put_prev_set_next_task(rq, rq->donor, rq->curr)
put_prev_task_scx(rq, prev, rq->curr)
switch_class(rq, rq->curr)
That path invokes ops.cpu_release() and sets rq->scx.cpu_released = true, and
proxy_reset_donor() sets rq->donor = rq->curr.
When scx_process_sync_ecaps() returns to dispatch_one(), the
rq->scx.cpu_released block then sees rq->scx.cpu_released == true, calls
ops.cpu_acquire(), and clears rq->scx.cpu_released = false right before
scx_dsp_prev_stale() aborts the pick with SCX_DSP_RETRY.
On the restarted pick, rq->donor is already rq->curr in the higher class, so
put_prev_task_scx() will not run again. Wouldn't that leave
rq->scx.cpu_released false and the BPF scheduler thinking it owns the CPU
while a higher class runs?
> @@ -3449,8 +3455,19 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev
> }
>
> verdict = scx_dispatch_sched(root_sch, rq, prev, false);
> - if (verdict != SCX_DSP_NONE)
> +
> + /* scx_dispatch_sched() may have dropped the rq lock */
> + if (scx_dsp_prev_stale(rq, prev))
> + verdict = SCX_DSP_RETRY;
> +
> + switch (verdict) {
> + case SCX_DSP_RETRY:
> + goto out;
[Severity: Medium]
As reported by Andrea Righi on v1 [1], when dispatch_one() returns
SCX_DSP_RETRY during an ext_server pick, do_pick_task_scx() returns
RETRY_TASK to __pick_task_dl():
__pick_next_task()
pick_task_dl()
__pick_task_dl()
ext_server_pick_task()
do_pick_task_scx()
dispatch_pick()
dispatch_one()
In kernel/sched/deadline.c:__pick_task_dl(), RETRY_TASK is non-NULL, so
rq->dl_server is set before returning:
if (dl_server(dl_se)) {
p = dl_se->server_pick_task(dl_se, rf);
if (!p) {
dl_server_stop(dl_se);
goto again;
}
rq->dl_server = dl_se;
}
In kernel/sched/core.c:__pick_next_task(), rq->dl_server = NULL is placed
above the restart label:
rq->dl_server = NULL;
if (scx_enabled())
goto restart;
...
restart:
prev_balance(rq, rf);
for_each_active_class(class) {
p = class->pick_task(rq, rf);
if (unlikely(p == RETRY_TASK))
goto restart;
if (p) {
put_prev_set_next_task(rq, rq->donor, p);
return p;
}
}
If the retried pass picks a non-server task (for instance, if the woken donor
was the only sched_ext task and migrated away so ext_server_pick_task()
returns NULL and stops ext_server, or if a higher-priority task was woken
during the lock drop), won't put_prev_set_next_task() hand the stale
&rq->ext_server pointer in rq->dl_server to next->dl_server?
> diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h
> index 1e39d12678c4d..fb0c58927d421 100644
> --- a/kernel/sched/ext/inlines.h
> +++ b/kernel/sched/ext/inlines.h
[ ... ]
> @@ -106,6 +124,11 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,
> rq->scx.sub_dispatch_prev = prev;
> #endif
>
> + /* the dispatch so far may have dropped the rq lock */
> + prev_on_sch = !scx_dsp_prev_stale(rq, prev) &&
> + prev->sched_class == &ext_sched_class &&
> + scx_task_on_sched(sch, prev);
> +
> SCX_CALL_OP(sch, dispatch, rq, scx_cpu_arg(cpu),
> prev_on_sch ? prev : NULL);
[Severity: High]
As reported by Sashiko on v1 [2], prev_on_sch guards passing prev to
ops.dispatch(), but right after scx_flush_dispatch_buf(sch, rq),
scx_dispatch_sched() still checks prev->scx.flags and prev->scx.slice
unconditionally:
SCX_CALL_OP(sch, dispatch, rq, scx_cpu_arg(cpu),
prev_on_sch ? prev : NULL);
#ifdef CONFIG_EXT_SUB_SCHED
if (!nested)
rq->scx.sub_dispatch_prev = NULL;
#endif
scx_flush_dispatch_buf(sch, rq);
if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice)
return SCX_DSP_PREV;
if (rq->scx.local_dsq.nr)
return SCX_DSP_LOCAL;
if (scx_consume_global_dsq(sch, rq))
return SCX_DSP_LOCAL;
If rq->__lock was dropped (either before ops.dispatch() or inside
scx_flush_dispatch_buf()) and another CPU woke the donor prev and enqueued it
remotely with SCX_TASK_QUEUED and a fresh slice, or if prev does not belong
to sch, won't this check read prev's state without holding its rq lock and
return SCX_DSP_PREV prematurely?
In that case:
- Tasks just dispatched to sch's global DSQ are left unconsumed because
scx_dispatch_sched() returns before reaching scx_consume_global_dsq(sch, rq)
or the dspc->nr_tasks retry loop.
- When called from scx_bpf_sub_dispatch(), returning SCX_DSP_PREV causes
scx_bpf_sub_dispatch() (which tests verdict != SCX_DSP_NONE) to return true
to the caller even though child did not dispatch a task, including inside
one-shot ops.sub_ecaps_updated() / ops.sub_child_ecaps_updated() callbacks
in __scx_process_sync_ecaps() that already drained rq->scx.ecaps_to_sync
and will not re-run when dispatch_one() retries.
- If prev == rq->curr == rq->donor was in fair_sched_class when dispatch_one()
started via ext_server_pick_task() and another CPU switched prev to
SCHED_EXT during scx_flush_dispatch_buf(), scx_dsp_prev_stale(rq, prev) is
still false while prev now has SCX_TASK_QUEUED and a default slice, so
scx_dispatch_sched() returns SCX_DSP_PREV ahead of rq->scx.local_dsq.nr even
though ops.dispatch() was passed NULL for prev.
Should the post-flush SCX_DSP_PREV check also require prev_on_sch and
!scx_dsp_prev_stale(rq, prev)?
[1] https://lore.kernel.org/r/auTNPtiaPyO9R5NG@gpd4
[2] https://lore.kernel.org/r/20261030142338.3453484-1-tj@kernel.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030235728.4098872-1-tj@kernel.org?part=1
next prev parent reply other threads:[~2026-10-31 0:29 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 23:57 [PATCH v2 sched_ext/for-7.4-fixes] sched_ext: Fix stale pick when a wakeup resets the donor during dispatch Tejun Heo
2026-10-31 0:29 ` sashiko-bot [this message]
2026-10-31 2:01 ` Tejun Heo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-191676@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=sched-ext@lists.linux.dev \
--cc=tj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox