Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
From: Tim Chen
Date: Fri Sep 04 2026 - 16:18:30 EST
On Fri, 2026-09-04 at 22:10 +0800, Hui Su wrote:
[...]
>
> Hi Tim, Chen Yu,
>
> Thanks for pointing out this separate issue and for the prototype.
>
> I tested the deadline-based calculation from the prototype with the
> same proxy-execution and core-scheduling reproducer. The reproducer has
> a FAIR donor proxy-executing a task on an SMT CPU while the sibling is
> force-idled.
>
> The relevant ordering in the current code is:
>
> update_curr()
> -> vruntime += delta
> -> update_deadline()
> ...
> task_tick_core()
>
> When update_deadline() advances the deadline before
> __entity_slice_used() is called, the prototype observes the newly
> advanced deadline. Since the new deadline is based on the current
> vruntime plus a new virtual slice, deadline - vruntime is reset to
> approximately vslice. Consequently, the reconstructed vused becomes
> small even though the donor has consumed scheduling service since it was
> selected.
Yes, you have a good point. The code I proposed just look at whether
we have consumed our allotment in the current slice.
What we should have looked at is whether the donor's total run time has
exceeded its quota when doing core scheduling. And we may happen to
hit __entity_slice_used() at the front of the slice after advancing
the deadline and __entity_slice_used()
returns false instead of true, even though I have consumed more than
my fair share when looking at longer time period across multiple slices.
This breaks the lone task case that Chen Yu has raised as it could
just keep running as __entity_slice_used() returns false.
>
> In the CONFIG_HZ=1000, nice-0 run, instrumentation showed:
>
> existing runtime delta == 0
> vslice == 2100000
> reconstructed vused in the tens or hundreds of thousands
> used == 0
>
> For comparison, I tested a selection-time vruntime snapshot:
>
> set_next_entity():
> core_prev_vruntime = se->vruntime;
>
> __entity_slice_used():
> vused = se->vruntime - se->core_prev_vruntime;
>
> I then repeated the test with CONFIG_HZ=1000, 250 and 100, and also
> with a nice -10 FAIR donor at HZ=250 and HZ=100.
>
> For the counts below I only included ticks where rq->donor != rq->curr,
> core force-idle was active, rq->cfs.h_nr_queued == 1, and the donor's
> existing runtime delta == 0.
>
> The observed behavior was consistent across these runs:
>
> configuration deadline prototype vruntime snapshot
> HZ=1000, nice 0 10/10 used=0 8/8 used=1
> HZ=250, nice 0 10/10 used=0 6/6 used=1
> HZ=100, nice 0 14/14 used=0 16/16 used=1
> HZ=250, nice -10 30/30 used=0 6/6 used=1
> HZ=100, nice -10 15/15 used=0 17/17 used=1
>
> The number of ticks in each window is timing-dependent and can change
> when a successful slice check triggers rescheduling. The comparison
> above is based on the per-tick result, rather than on equal window
> lengths.
>
> I also compared the existing runtime-based predicate with the snapshot
> predicate on the non-proxy path. In a CONFIG_HZ=1000 run, the decisions
> matched for all 83 observed force-idle ticks with rq->donor == rq->curr.
> For all 177 matching proxy ticks in the same run, the existing predicate
> was false while the snapshot predicate was true.
>
> In these runs, the snapshot version tracked the donor's vruntime
> progress across deadline rollovers and triggered the force-idle
> reschedule. It returned used == 1 for every matching tick observed in
> the proxy windows listed above. This also matches the previous
> sum_exec_runtime - prev_sum_exec_runtime semantics more closely: the
> measurement starts when the scheduling context is selected and is not
> tied to the current EEVDF request after a deadline rollover.
>
> These tests suggest that reconstructing the consumed service from the
> current deadline may lose the original selection-time semantics across
> a deadline rollover. The virtual-time direction still looks
> appropriate, while the consumed service appears to need a
> selection-time baseline rather than being reconstructed from a
> deadline that update_deadline() may already have advanced.
The accumulated run time of the donor since it was picked for running
should be used for selection time baseline.
So maybe a patch like the following instead.
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..bf105f436808 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -590,6 +590,10 @@ struct sched_entity {
u64 sum_exec_runtime;
u64 prev_sum_exec_runtime;
u64 vruntime;
+#ifdef CONFIG_SCHED_CORE
+ /* vruntime at the last pick, for the force-idle slice check: */
+ u64 core_slice_vruntime;
+#endif
/* Approximated virtual lag: */
s64 vlag;
/* 'Protected' deadline, to give out minimum quantums: */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index f78275192036..dde1f45051e3 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4580,6 +4580,9 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p)
p->se.prev_sum_exec_runtime = 0;
p->se.nr_migrations = 0;
p->se.vruntime = 0;
+#ifdef CONFIG_SCHED_CORE
+ p->se.core_slice_vruntime = 0;
+#endif
p->se.vlag = 0;
p->se.rel_deadline = 0;
INIT_LIST_HEAD(&p->se.group_node);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..ee06ee62e8dc 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6502,6 +6502,9 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
}
se->prev_sum_exec_runtime = se->sum_exec_runtime;
+#ifdef CONFIG_SCHED_CORE
+ se->core_slice_vruntime = se->vruntime;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14748,10 +14751,21 @@ static void rq_offline_fair(struct rq *rq)
static inline bool
__entity_slice_used(struct sched_entity *se, int min_nr_tasks)
{
- u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
- u64 slice = se->slice;
+ u64 vused, vslice;
+
+ /*
+ * @se is the scheduling context (rq->donor), which under proxy
+ * execution may not be the running task; its sum_exec_runtime is then
+ * not advanced. Use vruntime instead -- update_curr() advances it with
+ * the proxy runtime -- measured from a baseline taken at pick time in
+ * set_next_entity(). Being pick-based rather than per-slice, it stays
+ * correct when the tick period exceeds the slice, and matches the old
+ * rtime/slice test in the non-proxy case (same weight scaling).
+ */
+ vused = se->vruntime - se->core_slice_vruntime;
+ vslice = calc_delta_fair(se->slice, se);
- return (rtime * min_nr_tasks > slice);
+ return (vused * min_nr_tasks > vslice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
>
> I am keeping this as a separate patch from the execution-context tick
> series. I will continue validating the snapshot approach with
> fair-group scheduling before posting it.
Tim
>
> Thanks,
> Hui
>