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 03/14] sched_ext: Fix bypassed tasks looping on a host without SCX_CAP_ENQ
Date: Thu, 22 Oct 2026 22:04:10 +0000	[thread overview]
Message-ID: <sashiko-outbox-182095@kernel.org> (raw)
In-Reply-To: <20261022213836.2259069-4-tj@kernel.org>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] sched_ext: bypassed tasks stranded on IMMED reenqueue in scx_do_enqueue_task()
- [Medium] sched_ext: IMMED bypass task not reenqueued in dispatch_one() on SCX_DSP_PREV
--

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

sched_ext: Fix bypassed tasks looping on a host without SCX_CAP_ENQ

This commit updates bypass dispatch queue consumption in scx_dispatch_sched()
to pass SCX_ENQ_IMMED so that a host scheduler holding only SCX_CAP_ENQ_IMMED
can consume bypassed tasks onto the local DSQ without rejecting them for
lacking SCX_CAP_ENQ.

> diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h
> index 8de9d8fb4b25f..1e39d12678c4d 100644
> --- a/kernel/sched/ext/inlines.h
> +++ b/kernel/sched/ext/inlines.h
> @@ -39,10 +39,18 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,
>  	if (scx_consume_global_dsq(sch, rq))
>  		return SCX_DSP_LOCAL;
>  
> +	/*
> +	 * A bypass consume puts a task on the cpu being dispatched for, to run
> +	 * right away. That is an IMMED insert and needs only
> +	 * %SCX_CAP_ENQ_IMMED, the base cap. Dispatch descends into @sch only on
> +	 * a cpu where @sch holds the base cap, so @sch holds it here. The same
> +	 * holds for the consume at the end of this function.
> +	 */
>  	if (scx_bypass_dsp_enabled(sch)) {
>  		/* if @sch is bypassing, only the bypass DSQs are active */
>  		if (scx_bypassing(sch, cpu)) {
> -			if (scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0))
> +			if (scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu),
> +						   SCX_ENQ_IMMED))
>  				return SCX_DSP_LOCAL;
>  			return SCX_DSP_NONE;
>  		}

[Severity: High]
Can consuming bypassed tasks with SCX_ENQ_IMMED here cause a bypassed task to
be stranded off all dispatch queues in scx_do_enqueue_task() when it gets
reenqueued from the local DSQ?

When scx_consume_dispatch_q() moves a bypassed task to rq->scx.local_dsq with
SCX_ENQ_IMMED, dsq_inc_nr() marks the task with SCX_TASK_IMMED. If the task
cannot run right away (for example, due to a higher-priority wakeup, a head
insert, or a parent scheduler queueing its own task ahead of
scx_bpf_sub_dispatch()), reenq_local() dequeues it from rq->scx.local_dsq and
calls scx_do_enqueue_task() with SCX_ENQ_REENQ.

In kernel/sched/ext/ext.c:scx_do_enqueue_task(), sch is resolved as
scx_task_sched(p) (the bypassing scheduler that owns p, rather than the host
scheduler), and the reenqueue limit is checked before scx_bypassing():

	if (enq_flags & SCX_ENQ_REENQ) {
		if (++p->scx.reenq_cnt > 1)
			__scx_add_event(sch, SCX_EV_REENQ_REPEAT, 1);

		if (unlikely(p->scx.reenq_cnt > SCX_REENQ_MAX_REPEAT)) {
			__scx_exit(sch, SCX_EXIT_ERROR_REENQ, 0, cpu_of(rq),
				   "%s[%d] reenqueued %u times without running",
				   p->comm, p->pid, p->scx.reenq_cnt);
			goto out;
		}
	}

	if (!scx_rq_online(rq))
		goto local;

	if (scx_bypassing(sch, cpu_of(rq))) {
		__scx_add_event(sch, SCX_EV_BYPASS_DISPATCH, 1);
		goto bypass;
	}

This appears to break in two ways:

1. If a task previously triggered SCX_EXIT_ERROR_REENQ, it enters bypass with
   p->scx.reenq_cnt already at SCX_REENQ_MAX_REPEAT + 1 because cycling the
   task in scx_bypass() via dequeue_task_scx() -> ops_dequeue() ->
   clr_task_runnable(p, false) does not reset p->scx.reenq_cnt. On the very
   first local DSQ reenqueue during bypass, p->scx.reenq_cnt increments again
   and hits the goto out path before reaching goto bypass. Because
   scx_bypass(sch, true) in scx_sub_disable() or scx_root_disable() has
   already run, nothing picks the task back up, leaving it stranded off all
   DSQs while teardown waits for forward progress.

2. Even if p->scx.reenq_cnt starts at 0, if a bypassed task is reenqueued
   more than SCX_REENQ_MAX_REPEAT times (for example, because a parent
   scheduler repeatedly queues a task before calling scx_bpf_sub_dispatch()),
   __scx_exit() is called on the already-bypassing descendant sch instead of
   the host scheduler, and goto out strands the task off all DSQs.

Should scx_do_enqueue_task() check scx_bypassing(sch, cpu_of(rq)) before the
SCX_ENQ_REENQ handling, or attribute and handle reenqueues of bypassed tasks
against the host scheduler without stranding the task?

> @@ -63,7 +71,8 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,
>  		struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
>  
>  		if (!(pcpu->bypass_host_seq++ % SCX_BYPASS_HOST_NTH) &&
> -		    scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) {
> +		    scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu),
> +					   SCX_ENQ_IMMED)) {
>  			__scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1);
>  			return SCX_DSP_LOCAL;
>  		}
> @@ -135,7 +144,7 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,
>  	 * scheduler's ops.dispatch() doesn't yield any tasks.
>  	 */
>  	if (scx_bypass_dsp_enabled(sch) &&
> -	    scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) {
> +	    scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), SCX_ENQ_IMMED)) {
>  		__scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1);
>  		return SCX_DSP_LOCAL;
>  	}

[Severity: Medium]
What happens if a child host holding only SCX_CAP_ENQ_IMMED consumes a single
bypassed task with SCX_ENQ_IMMED during scx_bpf_sub_dispatch() while
rq->scx.local_dsq is empty, and the parent scheduler's ops.dispatch() also
extends prev->scx.slice?

During dispatch_one(), SCX_RQ_IN_DISPATCH is set on rq->scx.flags, so
rq_is_open() returns true:

	if (rq->scx.flags & SCX_RQ_IN_DISPATCH)
		return true;

When the child consumes one bypassed task onto the empty rq->scx.local_dsq,
dsq_inc_nr() sees dsq->nr == 1 and rq_is_open() == true, so it increments
rq->scx.nr_immed to 1 without calling scx_schedule_reenq_local().

When control returns to the parent's scx_dispatch_sched(), extending
prev->scx.slice causes it to return SCX_DSP_PREV before checking
rq->scx.local_dsq.nr:

		if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice)
			return SCX_DSP_PREV;
		if (rq->scx.local_dsq.nr)
			return SCX_DSP_LOCAL;

Then at has_tasks in kernel/sched/ext/ext.c:dispatch_one(), the post-dispatch
check only schedules a local reenqueue when rq->scx.local_dsq.nr > 1:

has_tasks:
	if (unlikely(rq->scx.local_dsq.nr > 1 && rq->scx.nr_immed))
		scx_schedule_reenq_local(rq, 0);

Because prev is rq->donor and not on rq->scx.local_dsq, rq->scx.local_dsq.nr
is 1, so scx_schedule_reenq_local() is not called. Doesn't this leave the
single bypassed task waiting on rq->scx.local_dsq behind prev without holding
SCX_CAP_ENQ and without being reenqueued back to the bypass DSQ?

[ ... ]

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

  reply	other threads:[~2026-10-22 22:04 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 21:38 [PATCHSET sched_ext/for-7.4] sched_ext: Fix bypassed sub-scheduler tasks stalling when the host lacks caps Tejun Heo
2026-10-22 21:38 ` [PATCH 01/14] sched_ext: Pack scx_ent_flags' multi-bit fields at the top Tejun Heo
2026-10-22 21:38 ` [PATCH 02/14] sched_ext: Clear holding_cpu where the claim is confirmed Tejun Heo
2026-10-22 21:38 ` [PATCH 03/14] sched_ext: Fix bypassed tasks looping on a host without SCX_CAP_ENQ Tejun Heo
2026-10-22 22:04   ` sashiko-bot [this message]
2026-10-23  0:03     ` Tejun Heo
2026-10-22 21:38 ` [PATCH 04/14] sched_ext: Move refill_task_slice_dfl() below apply_slice_vtime() Tejun Heo
2026-10-22 21:38 ` [PATCH 05/14] sched_ext: Restore a rescuee's slice after ops.stopping() Tejun Heo
2026-10-22 21:38 ` [PATCH 06/14] sched_ext: Fix a cpu idling with a runnable rescuee Tejun Heo
2026-10-22 21:56   ` sashiko-bot
2026-10-23  0:03     ` Tejun Heo
2026-10-22 21:38 ` [PATCH 07/14] sched_ext: Mark the rescue grant as a kernel slice with SCX_TASK_KERNEL_SLICE Tejun Heo
2026-10-22 21:38 ` [PATCH 08/14] sched_ext: Refuse extending a kernel slice Tejun Heo
2026-10-22 21:38 ` [PATCH 09/14] sched_ext: Fix a cpu idling after put_prev_task_scx() queues its task on the local DSQ Tejun Heo
2026-10-22 21:38 ` [PATCH 10/14] sched_ext: Fix bypassed tasks stalling when the host lacks SCX_CAP_BASE Tejun Heo
2026-10-22 22:09   ` sashiko-bot
2026-10-23  0:03     ` Tejun Heo
2026-10-22 21:38 ` [PATCH 11/14] sched_ext: Fix the bypass load balancer stranding tasks on cpus where " Tejun Heo
2026-10-22 21:38 ` [PATCH 12/14] sched_ext: Factor out bypass_lb_move() and insert with the donor DSQ unlocked Tejun Heo
2026-10-22 21:38 ` [PATCH 13/14] sched_ext: Let the bypass load balancer move tasks homed on other cpus Tejun Heo
2026-10-22 21:38 ` [PATCH 14/14] sched_ext: Fix queued bypassed tasks stalling when the host loses SCX_CAP_BASE Tejun Heo
2026-10-22 22:04   ` sashiko-bot
2026-10-23  0:03     ` 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-182095@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