[PATCH] [Question] sched/fair: Task starvation with RUN_TO_PARITY_WAKEUP under group topologies

From: Chen Jinghuang

Date: Sun Jul 12 2026 - 07:55:34 EST


Hi all,

While adapting the RUN_TO_PARITY_WAKEUP feature on top of mainline v7.2-rc2,
I encountered a severe task starvation issue under specific cgroup topologies.
Specifically, a running task completely hogs the CPU and prevents any
preemption.

Test Topology and Observed Behavior:
- CPU 0: Bound with two tasks: A1 (non-sleeping task) from cgroup A, and
B1 (frequent sleep/wake task) from cgroup B.
- CPU 20: Bound with other tasks from cgroup A (A2, A3... which sleep/wake
frequently) and task C1 (non-sleeping task) from cgroup C.
- All cgroups maintain default shares.

CPU 0 CPU 20 / Other CPUs
+----------------------------+ +----------------------------+
| cgroup A cgroup B | | cgroup A cgroup C |
| |- A1 (Run) |- B1 | | |- A2, A3... |- C1 |
| (Non-sleeping) (50us S) | | (Freq S/W) (Non-sleeping)|
| (10us W) | | |
+----------------------------+ +----------------------------+
|
v
A2/A3 frequent sleep/wake -> Frequent reweight of gse(A) on CPU 0
-> Triggers vlag clamping -> Unidirectional avruntime drift
-> gse->vruntime drops abnormally (while gse weight remains stable)
-> protect_slice() returns true -> pick_eevdf always returns curr
-> Task A1 constantly hogs the CPU

According to function_graph traces, the frequent sleep/wake cycles of
sibling tasks (A2/A3) on CPU 20 cause gse(A) on CPU 0 to undergo continuous
reweighting via update_cfs_cgroup() -> reweight_entity(). This triggers the
following chain reaction:

1. gse->vlag triggers the lag clamping logic in entity_lag().

2. Due to the clamping limit, A1->vruntime drifts unidirectionally against
avg_vruntime (i.e., gse->vruntime drops abnormally even though its weight
remains stable).

3. protect_slice() constantly returns true, forcing pick_eevdf() to always
return curr(A1) during wakeup preemption checks.

4. As a result, A1 indefinitely hogs CPU 0. Even in entity_tick(),
update_curr() fails to trigger resched_curr(). Both wakeup and periodic
preemptions end up picking curr.

Analysis:
Experimentally, either disabling RUN_TO_PARITY_WAKEUP or reverting the vlag
clamping patches completely resolves this starvation issue.

This indicates a problematic interaction between the extended runtime
introduced by RUN_TO_PARITY_WAKEUP and the vlag clamping mechanism. While
A1 runs beyond its expected share, frequent sibling-induced reweighting
pushes vlag to its limit, pulling A1->vruntime artificially closer to
avg_vruntime. However, A1->deadline and A1->vprot are still rescaled in
rescale_entity(). This mathematical asymmetry effectively ensures that
A1->vruntime < A1->vprot`always holds true. Consequently, protect_slice()
permanently returns true, trapping the scheduler into believing A1 has not
yet exhausted its promised slice, thereby completely blocking legitimate
context switches.

Discussion:
I'd love to hear your thoughts on how we can fix this issue without losing
the throughput gains of RUN_TO_PARITY_WAKEUP.

Thanks all.

Signed-off-by: Chen Jinghuang <chenjinghuang2@xxxxxxxxxx>
---
kernel/sched/fair.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index d78467ec6ee1..00869624d914 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1157,7 +1157,23 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq, bool protect)
return cfs_rq->next;
}

- if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr)))
+ if (curr && !curr->on_rq)
+ curr = NULL;
+
+ /*
+ * When an entity with positive lag wakes up, it pushes the
+ * avg_vruntime of the runqueue backwards. This may causes the
+ * current entity to be ineligible soon into its run leading to
+ * wakeup preemption.
+ *
+ * To prevent such aggressive preemption of the current running
+ * entity during task wakeups, skip the eligibility check if the
+ * slice promised to the entity since its selection has not yet
+ * elapsed.
+ */
+ if (curr &&
+ !(sched_feat(RUN_TO_PARITY_WAKEUP) && protect && protect_slice(curr)) &&
+ !entity_eligible(cfs_rq, curr))
curr = NULL;

if (curr && protect && protect_slice(curr))
--
2.34.1