Sched_ext development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Tejun Heo" <tj@kernel.org>, sched-ext@lists.linux.dev
Subject: Re: [PATCH sched_ext/for-7.4-fixes] sched_ext: Fix stale pick when a wakeup resets the donor during dispatch
Date: Fri, 30 Oct 2026 14:49:29 +0000	[thread overview]
Message-ID: <sashiko-outbox-191122@kernel.org> (raw)
In-Reply-To: <20261030142338.3453484-1-tj@kernel.org>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] sched_ext: stale prev and donor use in dispatch_one() after rq lock drop
- [Medium] sched: stale rq->dl_server across RETRY_TASK in __pick_next_task()
--

commit 2376377a1091277fdc742a85c71efe512084aeba
Author: Tejun Heo <tj@kernel.org>

sched_ext: Fix stale pick when a wakeup resets the donor during dispatch

When proxy execution is enabled, a concurrent wakeup while dispatch_one()
releases the runqueue lock can reset rq->donor to rq->curr and migrate the
previous donor to another CPU. This commit checks whether prev != rq->donor
in dispatch_pick() after dispatch_one() returns and restarts the pick with
SCX_DSP_RETRY.

> diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> index 904627ef909cb..c3f2f631dfafb 100644
> --- a/kernel/sched/ext/ext.c
> +++ b/kernel/sched/ext/ext.c
> @@ -3919,6 +3919,16 @@ static enum scx_dsp_verdict dispatch_pick(struct rq *rq, struct rq_flags *rf,
>  	verdict = dispatch_one(rq, prev);
>  	rq_repin_lock(rq, rf);
>  
> +	/*
> +	 * With proxy execution, @prev is the donor and need not be the running
> +	 * task. A wakeup while dispatch_one() has the rq lock released can
> +	 * reset the donor and move @prev to another CPU. The verdict was
> +	 * computed for @prev and doesn't apply once the donor changed. Restart
> +	 * the pick.
> +	 */
> +	if (unlikely(prev != rq->donor))
> +		return SCX_DSP_RETRY;

[Severity: High]
Because dispatch_pick() checks prev != rq->donor only after dispatch_one()
has completed, can a donor change during an internal rq->__lock drop inside
dispatch_one() cause dispatch_one() and scx_dispatch_sched() to operate on a
stale prev task and a mismatched rq->donor before returning?

In dispatch_one(), scx_process_sync_ecaps() -> __scx_process_sync_ecaps() ->
scx_flush_dispatch_buf() can drop and reacquire rq->__lock:

kernel/sched/ext/ext.c:dispatch_one() {
	scx_process_sync_ecaps(rq, prev);
	...
	if (prev->sched_class == &ext_sched_class) {
		update_curr_scx(rq);
		...
		if (scx_bypassing(scx_task_sched(prev), cpu))
			expire_task_slice(prev);
}

If another CPU wakes the blocked donor prev during that window,
proxy_needs_return() resets rq->donor to rq->curr and try_to_wake_up() can
migrate and enqueue prev on a remote CPU. When scx_process_sync_ecaps()
returns:

1. dispatch_one() tests prev->sched_class == &ext_sched_class on the old
   donor and calls update_curr_scx(rq), which reads donor = rq->donor (now
   rq->curr). If rq->curr is not in ext_sched_class, update_curr_scx() runs
   apply_task_slice_oob() and update_curr_common() on the non-scx rq->curr
   and charges its execution delta to rq->ext_server.

2. scx_task_sched(prev) is called without holding prev->pi_lock or
   task_rq(prev)->__lock, triggering the rcu_dereference_protected() check,
   and expire_task_slice(prev) -> set_task_slice_keep_oob(prev, 0) trips
   lockdep_assert_rq_held(task_rq(p)) while zeroing prev->scx.slice on a
   remote runqueue.

Similarly, if rq->__lock is dropped there or inside scx_dispatch_sched()
(via scx_consume_global_dsq(), scx_consume_dispatch_q(), or
scx_flush_dispatch_buf()):

kernel/sched/ext/inlines.h:scx_dispatch_sched() {
		SCX_CALL_OP(sch, dispatch, rq, scx_cpu_arg(cpu),
			    prev_on_sch ? prev : NULL);
}

kernel/sched/ext/ext.c:dispatch_one() {
	if (prev->scx.flags & SCX_TASK_QUEUED) {
		struct scx_sched *prev_sch = scx_task_sched(prev);
		...
	}
	...
has_tasks:
	if (unlikely((rq->scx.local_dsq.nr > 1 || verdict == SCX_DSP_PREV) &&
		     rq->scx.nr_immed))
		scx_schedule_reenq_local(rq, 0);
}

Wouldn't scx_dispatch_sched() pass the migrated prev pointer to ops.dispatch,
dispatch_one() call scx_task_sched(prev) unlocked when prev is queued on its
new CPU, and the has_tasks path schedule a spurious local DSQ re-enqueue for
a lone SCX_ENQ_IMMED task if a stale SCX_DSP_PREV verdict was reached before
dispatch_pick() turns it into SCX_DSP_RETRY?

[Severity: Medium]
Also, when dispatch_pick() returns SCX_DSP_RETRY during a deadline server
pick (do_pick_task_scx() called from ext_server_pick_task() with
sched_core_enabled(rq) false), could returning RETRY_TASK leave a stale
rq->dl_server pointer across the restart in __pick_next_task()?

In __pick_task_dl(), a RETRY_TASK return from server_pick_task() is non-NULL,
so rq->dl_server is set before returning RETRY_TASK:

kernel/sched/deadline.c:__pick_task_dl() {
	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 __pick_next_task(), jumping to restart bypasses rq->dl_server = NULL:

kernel/sched/core.c:__pick_next_task() {
	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 retry pass ends up picking a non-server task (for instance, if the
migrated donor was the last scx task so ext_server_pick_task() returns NULL
and stops ext_server, or a higher-priority task became runnable while the rq
lock was dropped), wouldn't put_prev_set_next_task() ->
__put_prev_set_next_dl_server() assign the stale &rq->ext_server pointer to
next->dl_server on that task?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261030142338.3453484-1-tj@kernel.org?part=1

  reply	other threads:[~2026-10-30 14:49 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-30 14:23 [PATCH sched_ext/for-7.4-fixes] sched_ext: Fix stale pick when a wakeup resets the donor during dispatch Tejun Heo
2026-10-30 14:49 ` sashiko-bot [this message]
2026-10-30 17:00 ` Andrea Righi

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-191122@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