From: Andrea Righi <arighi@nvidia.com>
To: sashiko-reviews@lists.linux.dev
Cc: sched-ext@lists.linux.dev
Subject: Re: [PATCH v2 sched_ext/for-7.3-fixes] sched_ext: Don't leave balance callbacks queued across a pick retry
Date: Sat, 17 Oct 2026 18:17:39 +0200 [thread overview]
Message-ID: <atOfoxKON9YYfup3@gpd4> (raw)
In-Reply-To: <sashiko-outbox-175614@kernel.org>
On Sat, Oct 17, 2026 at 03:40:58PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] sched_ext: balance callbacks queued before core-sched pick is final
> --
>
> commit 859b8e499322d8a16b0b359431e360cdd5ae7494
> Author: Andrea Righi <arighi@nvidia.com>
>
> sched_ext: Don't leave balance callbacks queued across a pick retry
>
> This commit moves the queueing of rq->scx.deferred_bal_cb and
> rq->scx.kick_sync_bal_cb to the exit path of do_pick_task_scx() so that
> balance callbacks are only queued when do_pick_task_scx() returns a final
> task rather than RETRY_TASK or a NULL dl_server pick.
>
> > diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> > index 0403c46342813..e19961164cb01 100644
> > --- a/kernel/sched/ext/ext.c
> > +++ b/kernel/sched/ext/ext.c
>
> [ ... ]
>
> > @@ -3342,16 +3331,8 @@ static enum scx_dsp_verdict dispatch_core_pick(struct rq *rq, struct rq_flags *r
> >
> > verdict = dispatch_one(rq, prev);
> >
> > - if (cpu_of(rq) == smp_processor_id()) {
> > - maybe_queue_balance_callback(rq);
> > -
> > - /* see dispatch_pick() */
> > - if (unlikely(rq->scx.kick_sync_pending)) {
> > - rq->scx.kick_sync_pending = false;
> > - queue_balance_callback(rq, &rq->scx.kick_sync_bal_cb,
> > - kick_sync_wait_bal_cb);
> > - }
> > - } else if (unlikely(rq->scx.flags & SCX_RQ_BAL_CB_PENDING)) {
> > + if (cpu_of(rq) != smp_processor_id() &&
> > + unlikely(rq->scx.flags & SCX_RQ_BAL_CB_PENDING)) {
> > /*
> > * Balance callbacks must run in the context that queued them,
> > * so they can't be queued on another CPU's rq. Run the deferred
> > @@ -3394,8 +3375,10 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
> > else
> > verdict = dispatch_pick(rq, rf, prev);
> >
> > - if (verdict == SCX_DSP_RETRY)
> > - return RETRY_TASK;
> > + if (verdict == SCX_DSP_RETRY) {
> > + p = RETRY_TASK;
> > + goto out;
> > + }
>
> [ ... ]
>
> > @@ -3437,6 +3422,38 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
> > }
> > }
> >
> > +out:
> > + if (cpu_of(rq) != smp_processor_id())
> > + return p;
> > +
> > + /*
> > + * Balance callbacks must be consumed before the rq lock is released, so
> > + * queue them only when the pick is final. On RETRY_TASK, or after a
> > + * NULL pick from the dl_server, the core continues picking and can
> > + * release the rq lock (e.g., prev_balance() or another dispatch). In
> > + * that case, run the deferred work from irq_work, which doesn't depend
> > + * on the current rq lock section, and leave the kick sync wait pending
> > + * for the next pick.
> > + */
> > + if (p != RETRY_TASK && (p || !force_scx)) {
> > + maybe_queue_balance_callback(rq);
> > +
> > + /*
> > + * Defer to a balance callback which can drop rq lock and enable
> > + * IRQs. Waiting directly in the pick path would deadlock against
> > + * CPUs sending us IPIs (e.g., TLB flushes) while we wait for
> > + * them.
> > + */
> > + if (unlikely(rq->scx.kick_sync_pending)) {
> > + rq->scx.kick_sync_pending = false;
> > + queue_balance_callback(rq, &rq->scx.kick_sync_bal_cb,
> > + kick_sync_wait_bal_cb);
> > + }
> > + } else if (rq->scx.flags & SCX_RQ_BAL_CB_PENDING) {
> > + rq->scx.flags &= ~SCX_RQ_BAL_CB_PENDING;
> > + schedule_deferred(rq);
> > + }
> > +
> > return p;
> > }
>
> [Severity: High]
> Under CONFIG_SCHED_CORE, can a non-RETRY_TASK return from
> do_pick_task_scx() on the local rq still be followed by further picks that
> release the shared core lock before the core-wide pick is final?
>
> In kernel/sched/core.c:pick_next_task(), a single shared core lock is held
> across all SMT siblings, and pick_task() can be called multiple times after
> the local rq pick succeeds:
>
> 1. In the !need_sync fastpath, pick_task(rq, rf) runs on the local rq:
>
> kernel/sched/core.c:pick_next_task() {
> if (!need_sync) {
> next = pick_task(rq, rf);
> ...
> if (!next->core_cookie) {
> ...
> goto out_set_next;
> }
> }
> }
>
> If do_pick_task_scx() returns a cookied SCX task (next->core_cookie != 0),
> the out: block sees p != RETRY_TASK && (p || !force_scx) and queues
> rq->scx.deferred_bal_cb and/or rq->scx.kick_sync_bal_cb on
> rq->balance_callback. Because next->core_cookie is non-zero,
> pick_next_task() then falls through into the for_each_cpu_wrap(i, smt_mask,
> cpu) loop to re-pick across all siblings.
>
> 2. In the for_each_cpu_wrap(i, smt_mask, cpu) loop in pick_next_task(),
> iteration starts at i = cpu (where cpu_of(rq_i) == smp_processor_id()):
>
> kernel/sched/core.c:pick_next_task() {
> for_each_cpu_wrap(i, smt_mask, cpu) {
> rq_i = cpu_rq(i);
> ...
> p = pick_task(rq_i, rf);
> if (unlikely(p == RETRY_TASK)) {
> ...
> goto restart;
> }
> }
> }
>
> Once do_pick_task_scx() on the local rq succeeds and queues the balance
> callbacks on rq->balance_callback, the loop proceeds to call
> pick_task(rq_i, rf) on the remaining SMT siblings (i != cpu). When
> dispatch_core_pick(rq_i, rf, prev) runs for a sibling rq_i,
> dispatch_one(rq_i, prev) or run_deferred(rq_i) can drop the shared core
> lock and return SCX_DSP_RETRY, causing pick_next_task() to jump to restart
> with rq->balance_callback already queued on the local rq. Any CPU acquiring
> the core lock during that window will trigger the warning in
> assert_balance_callbacks_empty() via rq_pin_lock() or flush the callback
> early.
I'm planning to fix this with a follow-up fix, it was also pointed out by Tejun
in his review of v1.
>
> 3. Also in dispatch_core_pick(), if an SMT sibling already has an in-flight
> dispatch on the local rq:
>
> kernel/sched/ext/ext.c:dispatch_core_pick() {
> /* another dispatch is in flight on @rq, let that handle it */
> if (rq->scx.flags & SCX_RQ_IN_DISPATCH)
> return SCX_DSP_NONE;
> }
>
> Previously, returning SCX_DSP_NONE here bypassed callback queueing in
> dispatch_core_pick(). With the callback handling moved to the out: label in
> do_pick_task_scx(), won't this early return now fall through to out: and
> consume SCX_RQ_BAL_CB_PENDING and rq->scx.kick_sync_pending while the
> sibling's dispatch_one() on rq is still in flight?
Hm... yeah this looks legit for SCX_RQ_BAL_CB_PENDING, I'll fix this in v3.
-Andrea
next prev parent reply other threads:[~2026-10-17 16:17 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-17 15:22 [PATCH v2 sched_ext/for-7.3-fixes] sched_ext: Don't leave balance callbacks queued across a pick retry Andrea Righi
2026-10-17 15:40 ` sashiko-bot
2026-10-17 16:17 ` Andrea Righi [this message]
2026-10-17 18:44 ` Tejun Heo
2026-10-17 19:11 ` 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=atOfoxKON9YYfup3@gpd4 \
--to=arighi@nvidia.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=sched-ext@lists.linux.dev \
/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