mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded
@ 2026-10-17  5:37 Tejun Heo
  2026-10-17  5:37 ` [PATCH 1/2] sched_ext: Fix the running task keeping its protection into bypass Tejun Heo
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Tejun Heo @ 2026-10-17  5:37 UTC (permalink / raw)
  To: sched-ext
  Cc: David Vernet, Andrea Righi, Changwoo Min, Emil Tsalapatis,
	David Dai, linux-kernel, Tejun Heo

Hello,

scx_bypass() handles the running task inside its walk over the runnable
list, which never contains the running task, so the protection strip at
bypass entry never runs. Separately, a bypassing sub-scheduler's tasks are
run by its nearest ancestor that isn't bypassing, but whether such a task
may stay on its cpu is still checked against the caps of its own scheduler.
A bypassed task that the bypass load balancer moved to a cpu where its
scheduler holds no caps can't stay there once its slice runs out, and the
cpu can go idle with the task queued on the ancestor's bypass DSQ.

Patch 1 handles the running task outside the walk. Patch 2 checks a bypassed
task against the caps of the scheduler running it and, in the block patch 1
adds, evicts the running task when bypass lifts if its own scheduler lacks
the caps to keep it.

Verified on bare metal by attaching and detaching a sub-scheduler repeatedly
under load. A debug knob that marks the running task protected at each
bypass entry finds every such task stripped with patch 1. Without it, they
all keep their protection and the bypass tick can't cut their slices. In a
VM, running tasks whose scheduler holds no caps on their cpu leave it within
about 50us of the bypass lift with patch 2, instead of running out their
bypass slice. The sched_ext selftests pass.

Based on sched_ext/for-7.3-fixes (43e1ef14a472).

This patchset contains the following 2 patches.

 0001 sched_ext: Fix the running task keeping its protection into bypass
 0002 sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on

The patchset is also available in the following git branch:

 git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git bypass-stall-fixes

diffstat follows. Thanks.

 kernel/sched/ext/ext.c      | 32 +++++++++++++++++++-------------
 kernel/sched/ext/internal.h | 17 +++++++++++++++++
 kernel/sched/ext/sub.c      |  7 +++++--
 kernel/sched/ext/sub.h      |  6 +++++-
 4 files changed, 46 insertions(+), 16 deletions(-)

--
tejun

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] sched_ext: Fix the running task keeping its protection into bypass
  2026-10-17  5:37 [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Tejun Heo
@ 2026-10-17  5:37 ` Tejun Heo
  2026-10-17  5:37 ` [PATCH 2/2] sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on Tejun Heo
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Tejun Heo @ 2026-10-17  5:37 UTC (permalink / raw)
  To: sched-ext
  Cc: David Vernet, Andrea Righi, Changwoo Min, Emil Tsalapatis,
	David Dai, linux-kernel, Tejun Heo

Protection ends when the scheduler enters bypass, so that bypass scheduling
can cut every task's slice. scx_bypass() clears it for the queued tasks by
cycling them, and strips the running task's in the same walk over
rq->scx.runnable_list. The running task is never on that list.
set_next_task_scx() takes it off and put_prev_task_scx() puts it back, so
the strip never runs. A protected running task then keeps its slice into
bypass, where the tick's attempts to cut it are refused.

Strip the running task's protection outside the walk.

Fixes: 9cfc6ab34a31 ("sched_ext: Add SCX_TASK_PROTECTED")
Signed-off-by: Tejun Heo <tj@kernel.org>
---
 kernel/sched/ext/ext.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 2c213153bdab..d6689bf8e769 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -6318,6 +6318,16 @@ void scx_bypass(struct scx_sched *sch, bool bypass)
 			continue;
 		}
 
+		/*
+		 * Bypass trumps protection. Cycling the queued tasks below
+		 * clears theirs. The current task isn't on the runnable list,
+		 * so strip its protection here.
+		 */
+		p = rq->curr;
+		if (bypass && p->sched_class == &ext_sched_class &&
+		    scx_is_descendant(scx_task_sched(p), sch))
+			scx_task_slice_ended(rq, p);
+
 		/*
 		 * The use of list_for_each_entry_safe_reverse() is required
 		 * because each task is going to be removed from and added back
@@ -6330,13 +6340,6 @@ void scx_bypass(struct scx_sched *sch, bool bypass)
 			if (!scx_is_descendant(scx_task_sched(p), sch))
 				continue;
 
-			/*
-			 * Bypass trumps protection. Cycling clears for queued
-			 * tasks but current task needs explicit stripping.
-			 */
-			if (bypass && task_current(rq, p))
-				scx_task_slice_ended(rq, p);
-
 			/* cycling deq/enq is enough, see the function comment */
 			scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
 				/* nothing */ ;
-- 
2.56.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 2/2] sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on
  2026-10-17  5:37 [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Tejun Heo
  2026-10-17  5:37 ` [PATCH 1/2] sched_ext: Fix the running task keeping its protection into bypass Tejun Heo
@ 2026-10-17  5:37 ` Tejun Heo
  2026-10-17 10:12 ` [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Andrea Righi
  2026-10-17 15:34 ` Tejun Heo
  3 siblings, 0 replies; 6+ messages in thread
From: Tejun Heo @ 2026-10-17  5:37 UTC (permalink / raw)
  To: sched-ext
  Cc: David Vernet, Andrea Righi, Changwoo Min, Emil Tsalapatis,
	David Dai, linux-kernel, Tejun Heo

A bypassing sub-scheduler's tasks are run by its nearest ancestor that isn't
bypassing. The ancestor puts them on the local DSQ under its own caps.
Whether such a task may stay on the cpu and whether a cap revoke reenqueues
it are still checked against the caps of the task's own scheduler.

The bypass load balancer moves tasks between cpus without looking at caps,
so a bypassed task can run on a cpu where its own scheduler holds none. Once
its slice runs out, it can't stay. It goes to the ancestor's bypass DSQ
without a kick, and the cpu can go idle with it queued until something else
reschedules the cpu.

Make both checks against the caps of the scheduler running the task. When
bypass lifts, the running task keeps its slice without a cap check, so evict
it then if it can't stay on the cpu.

Fixes: 75a8c8202c91 ("sched_ext: Add reject DSQ for cap-rejected dispatches")
Fixes: 46a85ae6fe5b ("sched_ext: Tie cpu occupancy to SCX_CAP_BASE through the task slice")
Signed-off-by: Tejun Heo <tj@kernel.org>
---
 kernel/sched/ext/ext.c      | 21 ++++++++++++---------
 kernel/sched/ext/internal.h | 17 +++++++++++++++++
 kernel/sched/ext/sub.c      |  7 +++++--
 kernel/sched/ext/sub.h      |  6 +++++-
 4 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index d6689bf8e769..6b472506944e 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -322,7 +322,6 @@ static const struct sched_class *scx_setscheduler_class(struct task_struct *p)
 
 static struct scx_dispatch_q *bypass_enq_target_dsq(struct scx_sched *sch, s32 cpu)
 {
-#ifdef CONFIG_EXT_SUB_SCHED
 	/*
 	 * If @sch is a sub-sched which is bypassing, its tasks should go into
 	 * the bypass DSQs of the nearest ancestor which is not bypassing. The
@@ -334,11 +333,7 @@ static struct scx_dispatch_q *bypass_enq_target_dsq(struct scx_sched *sch, s32 c
 	 * are re-enqueued after scx_bypassing() is turned on, guaranteeing that
 	 * all tasks are transferred to the right DSQs.
 	 */
-	while (scx_parent(sch) && scx_bypassing(sch, cpu))
-		sch = scx_parent(sch);
-#endif	/* CONFIG_EXT_SUB_SCHED */
-
-	return scx_bypass_dsq(sch, cpu);
+	return scx_bypass_dsq(scx_exec_sched(sch, cpu), cpu);
 }
 
 /**
@@ -6322,11 +6317,19 @@ void scx_bypass(struct scx_sched *sch, bool bypass)
 		 * Bypass trumps protection. Cycling the queued tasks below
 		 * clears theirs. The current task isn't on the runnable list,
 		 * so strip its protection here.
+		 *
+		 * When bypass lifts, the current task keeps running on its
+		 * slice without a cap check. Evict it if it can't stay on the
+		 * cpu.
 		 */
 		p = rq->curr;
-		if (bypass && p->sched_class == &ext_sched_class &&
-		    scx_is_descendant(scx_task_sched(p), sch))
-			scx_task_slice_ended(rq, p);
+		if (p->sched_class == &ext_sched_class &&
+		    scx_is_descendant(scx_task_sched(p), sch)) {
+			if (bypass)
+				scx_task_slice_ended(rq, p);
+			else if (!scx_task_can_stay_on_cpu(rq, p))
+				scx_set_task_slice(p, 0);
+		}
 
 		/*
 		 * The use of list_for_each_entry_safe_reverse() is required
diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h
index 352547aedbb7..03f03204d158 100644
--- a/kernel/sched/ext/internal.h
+++ b/kernel/sched/ext/internal.h
@@ -2539,4 +2539,21 @@ static inline struct scx_sched *scx_parent(struct scx_sched *sch) { return NULL;
 
 #endif	/* CONFIG_EXT_SUB_SCHED */
 
+/**
+ * scx_exec_sched - Find the sched running @sch's tasks on @cpu
+ * @sch: sched owning the tasks
+ * @cpu: cpu of interest
+ *
+ * A bypassing sched's tasks are run by its nearest ancestor that isn't
+ * bypassing. Return @sch if it isn't bypassing on @cpu, otherwise that
+ * ancestor, or the root if every ancestor is bypassing. Hold @cpu's rq lock for
+ * a stable answer.
+ */
+static inline struct scx_sched *scx_exec_sched(struct scx_sched *sch, s32 cpu)
+{
+	while (scx_parent(sch) && scx_bypassing(sch, cpu))
+		sch = scx_parent(sch);
+	return sch;
+}
+
 #endif /* _KERNEL_SCHED_EXT_INTERNAL_H */
diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c
index fee75fcdb59d..90430493bb72 100644
--- a/kernel/sched/ext/sub.c
+++ b/kernel/sched/ext/sub.c
@@ -755,18 +755,21 @@ struct scx_dispatch_q *scx_resolve_local_dsq(struct scx_sched *sch, struct rq *r
 /* @p lost the caps needed to stay on @rq's local DSQ? Record reason if so. */
 bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p)
 {
+	s32 cpu = cpu_of(rq);
 	u64 missing;
 
 	/* migration-disabled tasks and the rescuee are admitted capless */
 	if (is_migration_disabled(p) || p == scx_rescuee(rq))
 		return false;
 
-	missing = scx_missing_caps(scx_task_sched(p), cpu_of(rq), scx_caps_for_task(p));
+	/* check caps against the sched running a bypassed task */
+	missing = scx_missing_caps(scx_exec_sched(scx_task_sched(p), cpu), cpu,
+				   scx_caps_for_task(p));
 	if (likely(!missing))
 		return false;
 
 	p->scx.reenq_reason_caps = missing;
-	p->scx.reenq_reason_cid = __scx_cpu_to_cid(cpu_of(rq));
+	p->scx.reenq_reason_cid = __scx_cpu_to_cid(cpu);
 	return true;
 }
 
diff --git a/kernel/sched/ext/sub.h b/kernel/sched/ext/sub.h
index f7bcdfda8dd8..0ac7b8e59205 100644
--- a/kernel/sched/ext/sub.h
+++ b/kernel/sched/ext/sub.h
@@ -199,6 +199,8 @@ static inline u64 scx_caps_implied(u64 cap)
 /* may @p keep running on @rq's cpu? requires baseline cpu access */
 static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p)
 {
+	s32 cpu = cpu_of(rq);
+
 	if (!scx_has_subs())
 		return true;
 
@@ -206,7 +208,9 @@ static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p
 	if (unlikely(is_migration_disabled(p)))
 		return true;
 
-	return likely(!scx_missing_caps(scx_task_sched(p), cpu_of(rq), SCX_CAP_BASE));
+	/* check caps against the sched running a bypassed task */
+	return likely(!scx_missing_caps(scx_exec_sched(scx_task_sched(p), cpu), cpu,
+					SCX_CAP_BASE));
 }
 
 /* the task admitted for rescue on @rq, NULL if none */
-- 
2.56.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded
  2026-10-17  5:37 [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Tejun Heo
  2026-10-17  5:37 ` [PATCH 1/2] sched_ext: Fix the running task keeping its protection into bypass Tejun Heo
  2026-10-17  5:37 ` [PATCH 2/2] sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on Tejun Heo
@ 2026-10-17 10:12 ` Andrea Righi
  2026-10-17 15:34   ` Tejun Heo
  2026-10-17 15:34 ` Tejun Heo
  3 siblings, 1 reply; 6+ messages in thread
From: Andrea Righi @ 2026-10-17 10:12 UTC (permalink / raw)
  To: Tejun Heo
  Cc: sched-ext, David Vernet, Changwoo Min, Emil Tsalapatis,
	David Dai, linux-kernel

Hi Tejun,

On Fri, Oct 16, 2026 at 07:37:15PM -1000, Tejun Heo wrote:
> Hello,
> 
> scx_bypass() handles the running task inside its walk over the runnable
> list, which never contains the running task, so the protection strip at
> bypass entry never runs. Separately, a bypassing sub-scheduler's tasks are
> run by its nearest ancestor that isn't bypassing, but whether such a task
> may stay on its cpu is still checked against the caps of its own scheduler.
> A bypassed task that the bypass load balancer moved to a cpu where its
> scheduler holds no caps can't stay there once its slice runs out, and the
> cpu can go idle with the task queued on the ancestor's bypass DSQ.
> 
> Patch 1 handles the running task outside the walk. Patch 2 checks a bypassed
> task against the caps of the scheduler running it and, in the block patch 1
> adds, evicts the running task when bypass lifts if its own scheduler lacks
> the caps to keep it.
> 
> Verified on bare metal by attaching and detaching a sub-scheduler repeatedly
> under load. A debug knob that marks the running task protected at each
> bypass entry finds every such task stripped with patch 1. Without it, they
> all keep their protection and the bypass tick can't cut their slices. In a
> VM, running tasks whose scheduler holds no caps on their cpu leave it within
> about 50us of the bypass lift with patch 2, instead of running out their
> bypass slice. The sched_ext selftests pass.

A few notes:

 - when merging for-7.3-fixes into for-7.4, with proxy exec the slice and the
   protection belong to the donor, so I think the resolution should use rq->donor
   instead of rq->curr, both for the strip on bypass and for the eviction on
   unbypass. With rq->curr, a blocked SCX donor would keep its protection into
   bypass and we'd act on the proxy owner instead.

 - not a regression and probably transient (I haven't verified it):
   bypass_lb_cpu() doesn't look at caps, when the nearest non-bypassing ancestor
   running a bypassed sched's tasks is itself a sub-sched with caps only on some
   CPUs, could a bypassed task still be moved to a CPU it can't stay on, until
   bypass lifts?

For the series:

Reviewed-by: Andrea Righi <arighi@nvidia.com>

Thanks,
-Andrea

> 
> Based on sched_ext/for-7.3-fixes (43e1ef14a472).
> 
> This patchset contains the following 2 patches.
> 
>  0001 sched_ext: Fix the running task keeping its protection into bypass
>  0002 sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on
> 
> The patchset is also available in the following git branch:
> 
>  git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git bypass-stall-fixes
> 
> diffstat follows. Thanks.
> 
>  kernel/sched/ext/ext.c      | 32 +++++++++++++++++++-------------
>  kernel/sched/ext/internal.h | 17 +++++++++++++++++
>  kernel/sched/ext/sub.c      |  7 +++++--
>  kernel/sched/ext/sub.h      |  6 +++++-
>  4 files changed, 46 insertions(+), 16 deletions(-)
> 
> --
> tejun

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded
  2026-10-17  5:37 [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Tejun Heo
                   ` (2 preceding siblings ...)
  2026-10-17 10:12 ` [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Andrea Righi
@ 2026-10-17 15:34 ` Tejun Heo
  3 siblings, 0 replies; 6+ messages in thread
From: Tejun Heo @ 2026-10-17 15:34 UTC (permalink / raw)
  To: sched-ext
  Cc: David Vernet, Andrea Righi, Changwoo Min, Emil Tsalapatis,
	David Dai, linux-kernel

>  0001 sched_ext: Fix the running task keeping its protection into bypass
>  0002 sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on

Applied 1-2 to sched_ext/for-7.3-fixes.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded
  2026-10-17 10:12 ` [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Andrea Righi
@ 2026-10-17 15:34   ` Tejun Heo
  0 siblings, 0 replies; 6+ messages in thread
From: Tejun Heo @ 2026-10-17 15:34 UTC (permalink / raw)
  To: Andrea Righi
  Cc: sched-ext, David Vernet, Changwoo Min, Emil Tsalapatis,
	David Dai, linux-kernel

Hello, Andrea.

On Sat, Oct 17, 2026 at 12:12:44PM +0200, Andrea Righi wrote:
>  - when merging for-7.3-fixes into for-7.4, with proxy exec the slice and the
>    protection belong to the donor, so I think the resolution should use rq->donor
>    instead of rq->curr, both for the strip on bypass and for the eviction on
>    unbypass. With rq->curr, a blocked SCX donor would keep its protection into
>    bypass and we'd act on the proxy owner instead.

Yes, the merge into for-7.4 will use rq->donor for both.

>  - not a regression and probably transient (I haven't verified it):
>    bypass_lb_cpu() doesn't look at caps, when the nearest non-bypassing ancestor
>    running a bypassed sched's tasks is itself a sub-sched with caps only on some
>    CPUs, could a bypassed task still be moved to a CPU it can't stay on, until
>    bypass lifts?

Yeah, that's already being worked on in a separate series.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-17 15:34 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-17  5:37 [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Tejun Heo
2026-10-17  5:37 ` [PATCH 1/2] sched_ext: Fix the running task keeping its protection into bypass Tejun Heo
2026-10-17  5:37 ` [PATCH 2/2] sched_ext: Fix a bypassed task stalling on a cpu its scheduler has no caps on Tejun Heo
2026-10-17 10:12 ` [PATCHSET sched_ext/for-7.3-fixes] sched_ext: Fix bypass leaving the running task protected and bypassed tasks stranded Andrea Righi
2026-10-17 15:34   ` Tejun Heo
2026-10-17 15:34 ` Tejun Heo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®