mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Tejun Heo <tj@kernel.org>
To: sched-ext@lists.linux.dev
Cc: David Vernet <void@manifault.com>,
	Andrea Righi <arighi@nvidia.com>,
	Changwoo Min <changwoo@igalia.com>,
	Emil Tsalapatis <emil@etsalapatis.com>,
	David Dai <david.dai@linux.dev>,
	linux-kernel@vger.kernel.org, Tejun Heo <tj@kernel.org>,
	Sashiko <sashiko-bot@kernel.org>
Subject: [PATCH 06/17] sched_ext: Fix rescue served time disagreeing with the slice under proxy execution
Date: Fri, 23 Oct 2026 07:55:57 -1000	[thread overview]
Message-ID: <20261023175608.4120910-7-tj@kernel.org> (raw)
In-Reply-To: <20261023175608.4120910-1-tj@kernel.org>

Rescue measures served time as the rescuee's sum_exec_runtime minus a
snapshot taken at admission. Under proxy execution that measure follows the
wrong task. update_se() credits sum_exec_runtime to rq->curr, the mutex
owner that executes, while update_curr_scx() drains the slice of rq->donor
and charges its rescue. A rescuee can be either, so its served time and its
slice disagree in both directions.

As the owner running for another donor, the rescuee accrues served time that
drained no slice and charged no budget, and its rescue ends early. As the
donor, its rescue runs long:

 1. the rescuee blocks on a mutex and the owner runs in its place
 2. the owner's run drains the rescuee's slice and the budget, but not the
    served time
 3. the slice runs out with the rescue still open, and scx_rescue_keep()
    restores a remainder that the owner's run didn't reduce
 4. the owner keeps running on the rescue until it releases the mutex, past
    the admitted slice and overdrawing the budget

Count served time in scx_rescue_charge() from the delta that drains the
slice. Charging the owner's run to the donor's rescue matches proxy
execution, which drains the donor's slice and accounts the cgroup time to
the donor for the owner's run.

scx_rescue_keep() also ignored the return of scx_set_task_slice(). The call
refuses the restore on a rescuee that escalation marked SCX_TASK_PROTECTED,
and the rescuee was kept with a zero slice and its protection set.

That refusal is now unreachable. Escalation sets the slice to the unserved
remainder before it protects the task. From then on every slice write a
scheduler can reach is refused. Only a sub-scheduler's task is rescued, and
sub-schedulers are cid-form, which rejects direct slice writes.

The slice then moves only by what update_curr_scx() drains, and that drain
charges the rescue by the same amount, so the slice runs out exactly when
the charge ends the rescue. WARN and end the rescue if the restore is
refused anyway.

Fixes: 5fd501744b10 ("sched_ext: Add bandwidth-limited rescue execution for stranded tasks")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
---
 kernel/sched/ext/sub.c | 25 ++++++++++++++++---------
 kernel/sched/sched.h   |  2 +-
 2 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c
index d3db1277118e..d3f89ec4e4fa 100644
--- a/kernel/sched/ext/sub.c
+++ b/kernel/sched/ext/sub.c
@@ -401,9 +401,7 @@ void scx_init_root_caps(struct scx_sched *sch)
 /* unserved remainder of @rq's rescuee's admitted slice, 0 once fully served */
 static s64 scx_rescue_slice_remaining(struct rq *rq)
 {
-	s64 served = rq->scx.rescue.curr->se.sum_exec_runtime - rq->scx.rescue.exec_snap;
-
-	return max(rq->scx.rescue.slice - served, 0);
+	return max(rq->scx.rescue.slice - rq->scx.rescue.served, 0);
 }
 
 /*
@@ -438,6 +436,14 @@ void scx_rescue_charge(struct rq *rq, s64 delta_exec)
 
 	lockdep_assert_rq_held(rq);
 
+	/*
+	 * Count @delta_exec as served before the clamp below, which bounds only
+	 * the budget charge. update_curr_scx() took the same delta off the
+	 * slice, so a slice set to the unserved remainder, as escalation does,
+	 * reaches zero when the remainder does, see scx_rescue_keep().
+	 */
+	rq->scx.rescue.served += delta_exec;
+
 	/*
 	 * A rescue slice is bounded by one quantum and tick-driven expiry can
 	 * overshoot by up to a tick. Clamp to avoid wild over-charges on VMs.
@@ -498,7 +504,9 @@ bool scx_rescue_keep(struct rq *rq, struct task_struct *p)
 	    scx_bypassing(scx_task_sched(p), cpu_of(rq)))
 		return false;
 
-	scx_set_task_slice(p, remaining);
+	/* never called on a protected rescuee, so the write can't be refused */
+	if (WARN_ON_ONCE(!scx_set_task_slice(p, remaining)))
+		return false;
 	return true;
 }
 
@@ -581,10 +589,9 @@ static void scx_rescue_timer_arm(struct rq *rq)
  * @slice: CPU time to grant
  *
  * The schedulers keep their normal control over @p and may preempt or reslice
- * it. @slice is measured on served CPU time against the snapshot taken here, so
- * neither shortens the rescue, see scx_rescue_charge() and scx_rescue_keep().
- * Prolonged denial escalates into protected execution, see
- * scx_rescue_timerfn().
+ * it. @slice is measured on served CPU time, so neither shortens the rescue,
+ * see scx_rescue_charge() and scx_rescue_keep(). Prolonged denial escalates
+ * into protected execution, see scx_rescue_timerfn().
  */
 static void scx_rescue_admit(struct rq *rq, struct task_struct *p, s64 slice)
 {
@@ -593,7 +600,7 @@ static void scx_rescue_admit(struct rq *rq, struct task_struct *p, s64 slice)
 
 	rq->scx.rescue.curr = p;
 	rq->scx.rescue.slice = slice;
-	rq->scx.rescue.exec_snap = p->se.sum_exec_runtime;
+	rq->scx.rescue.served = 0;
 	scx_set_task_slice(p, slice);
 	scx_rescue_timer_arm(rq);
 }
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 37d4808d7f19..6fed367feda4 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -807,7 +807,7 @@ struct scx_rq_rescue {
 	u64			clock;			/* last budget accrual timestamp */
 	struct task_struct	*curr;			/* task being rescued, one at a time */
 	s64			slice;			/* curr's admitted slice */
-	u64			exec_snap;		/* sum_exec_runtime at admission */
+	s64			served;			/* curr's served time */
 	struct timer_list	timer;			/* paces admission and escalation */
 	u64			kill_at;		/* last ejection, init before any */
 };
-- 
2.56.0


  parent reply	other threads:[~2026-10-23 17:56 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23 17:55 [PATCHSET v3 sched_ext/for-7.4] sched_ext: Fix bypassed sub-scheduler tasks stalling when the host lacks caps Tejun Heo
2026-10-23 17:55 ` [PATCH 01/17] sched_ext: Pack scx_ent_flags' multi-bit fields at the top Tejun Heo
2026-10-23 17:55 ` [PATCH 02/17] sched_ext: Clear holding_cpu where the claim is confirmed Tejun Heo
2026-10-23 17:55 ` [PATCH 03/17] sched_ext: Fix bypassed tasks stranded by the reenqueue limit Tejun Heo
2026-10-23 17:55 ` [PATCH 04/17] sched_ext: Fix bypassed tasks looping on a host without SCX_CAP_ENQ Tejun Heo
2026-10-23 17:55 ` [PATCH 05/17] sched_ext: Move refill_task_slice_dfl() below apply_slice_vtime() Tejun Heo
2026-10-23 17:55 ` Tejun Heo [this message]
2026-10-23 17:55 ` [PATCH 07/17] sched_ext: Restore a rescuee's slice after ops.stopping() Tejun Heo
2026-10-23 17:55 ` [PATCH 08/17] sched_ext: Fix a cpu idling with a runnable rescuee Tejun Heo
2026-10-23 17:56 ` [PATCH 09/17] sched_ext: Mark the rescue grant as a kernel slice with SCX_TASK_KERNEL_SLICE Tejun Heo
2026-10-23 17:56 ` [PATCH 10/17] sched_ext: Refuse extending a kernel slice Tejun Heo
2026-10-23 17:56 ` [PATCH 11/17] sched_ext: Fix a cpu idling after put_prev_task_scx() queues its task on the local DSQ Tejun Heo
2026-10-23 17:56 ` [PATCH 12/17] sched_ext: Expire prev's slice in dispatch_one() under bypass Tejun Heo
2026-10-23 17:56 ` [PATCH 13/17] sched_ext: Fix bypassed tasks stalling when the host lacks SCX_CAP_BASE Tejun Heo
2026-10-23 17:56 ` [PATCH 14/17] sched_ext: Fix the bypass load balancer stranding tasks on cpus where " Tejun Heo
2026-10-23 17:56 ` [PATCH 15/17] sched_ext: Factor out bypass_lb_move() and insert with the donor DSQ unlocked Tejun Heo
2026-10-23 17:56 ` [PATCH 16/17] sched_ext: Let the bypass load balancer move tasks homed on other cpus Tejun Heo
2026-10-23 17:56 ` [PATCH 17/17] sched_ext: Fix queued bypassed tasks stalling when the host loses SCX_CAP_BASE Tejun Heo
2026-10-23 20:06 ` [PATCH 18/17] sched_ext: Fix use-after-free of a NONE rescuee leaving sched_ext Tejun Heo
2026-10-23 20:06 ` [PATCH 19/17] sched_ext: Fix the stall watchdog blaming a bypassing owner for a stuck task Tejun Heo
2026-10-23 20:10 ` [PATCH 20/17] sched_ext: Fix a lone IMMED task waiting behind a kept prev 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=20261023175608.4120910-7-tj@kernel.org \
    --to=tj@kernel.org \
    --cc=arighi@nvidia.com \
    --cc=changwoo@igalia.com \
    --cc=david.dai@linux.dev \
    --cc=emil@etsalapatis.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=sashiko-bot@kernel.org \
    --cc=sched-ext@lists.linux.dev \
    --cc=void@manifault.com \
    /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

all inboxes | Powered by JetHome®