kernel/sched/stats.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
From: Wei Yang <albinwyang@tencent.com>
With DELAY_DEQUEUE, a blocked task stays on the runqueue with
se.sched_delayed set and its sched_info.last_queued is cleared, so the sleep
is not counted into run_delay.
When such a delayed (sleeping) task is migrated across CPUs via the plain
migration paths (move_queued_task / move_queued_task_locked, the latter used
by __migrate_swap_task), activate_task(dst, 0) calls enqueue_task() without
ENQUEUE_RESTORE, re-arming last_queued to the migration timestamp while the
task is still sleeping. The later real wakeup (ENQUEUE_DELAYED) tries to
re-arm last_queued at wakeup time but is suppressed because last_queued is
already non-zero, so sched_info_arrive() folds the whole sleep duration
between migration and wakeup into run_delay.
Load balance is affected too: the sched_delayed check in can_migrate_task()
bails out only when env->migration_type != migrate_load, so it does not
block migration when the type is migrate_load - which active load balance
always uses (its lb_env leaves migration_type at 0 == migrate_load), and
which regular load balance can also use via calculate_imbalance(). Either
way the re-attach goes through attach_task() -> activate_task(rq, p,
ENQUEUE_NOCLOCK), without ENQUEUE_RESTORE.
Fix by not re-arming last_queued for a sched_delayed task in
sched_info_enqueue(). The wakeup path clears sched_delayed before reaching
sched_info_enqueue(), so it still re-arms at the real wakeup time. Plain
runnable tasks are unaffected.
Fixes: 152e11f6df29 ("sched/fair: Implement delayed dequeue")
Reported-by: MingTao Huang <mintaohuang@tencent.com>
Signed-off-by: Wei Yang <albinwyang@tencent.com>
Reviewed-by: Chen Yu <yu.c.chen@intel.com>
Reviewed-by: Kayra Cizmeci <kayracizmeci@gmail.com>
Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
---
Changes in v3:
- Collect Reviewed-by from Kayra Cizmeci and K Prateek Nayak.
- Collect Tested-by from K Prateek Nayak.
Changes in v2:
- Correct the load-balance description: sched_delayed tasks are not excluded
from active load balancing. can_migrate_task()'s sched_delayed check only
bails out when env->migration_type != migrate_load, so active load balance
(which always uses migrate_load) still migrates them.
- Collect Reviewed-by from Chen Yu.
v2: https://lore.kernel.org/all/20260920043116.1298017-1-albin_yang@163.com/
v1: https://lore.kernel.org/all/20260909133345.1572954-1-albin_yang@163.com/
kernel/sched/stats.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h
index ebe0a7765f98..dc626f99ffd9 100644
--- a/kernel/sched/stats.h
+++ b/kernel/sched/stats.h
@@ -290,7 +290,7 @@ static void sched_info_arrive(struct rq *rq, struct task_struct *t)
*/
static inline void sched_info_enqueue(struct rq *rq, struct task_struct *t)
{
- if (!t->sched_info.last_queued)
+ if (!t->sched_info.last_queued && !t->se.sched_delayed)
t->sched_info.last_queued = rq_clock(rq);
}
--
2.43.7
On Tue, Sep 22, 2026 at 04:24:32PM +0800, albin_yang@163.com wrote:
> From: Wei Yang <albinwyang@tencent.com>
>
> With DELAY_DEQUEUE, a blocked task stays on the runqueue with
> se.sched_delayed set and its sched_info.last_queued is cleared, so the sleep
> is not counted into run_delay.
>
> When such a delayed (sleeping) task is migrated across CPUs via the plain
> migration paths (move_queued_task / move_queued_task_locked, the latter used
> by __migrate_swap_task), activate_task(dst, 0) calls enqueue_task() without
> ENQUEUE_RESTORE, re-arming last_queued to the migration timestamp while the
> task is still sleeping. The later real wakeup (ENQUEUE_DELAYED) tries to
> re-arm last_queued at wakeup time but is suppressed because last_queued is
> already non-zero, so sched_info_arrive() folds the whole sleep duration
> between migration and wakeup into run_delay.
>
> Load balance is affected too: the sched_delayed check in can_migrate_task()
> bails out only when env->migration_type != migrate_load, so it does not
> block migration when the type is migrate_load - which active load balance
> always uses (its lb_env leaves migration_type at 0 == migrate_load), and
> which regular load balance can also use via calculate_imbalance(). Either
> way the re-attach goes through attach_task() -> activate_task(rq, p,
> ENQUEUE_NOCLOCK), without ENQUEUE_RESTORE.
>
> Fix by not re-arming last_queued for a sched_delayed task in
> sched_info_enqueue(). The wakeup path clears sched_delayed before reaching
> sched_info_enqueue(), so it still re-arms at the real wakeup time. Plain
> runnable tasks are unaffected.
>
> Fixes: 152e11f6df29 ("sched/fair: Implement delayed dequeue")
> Reported-by: MingTao Huang <mintaohuang@tencent.com>
> Signed-off-by: Wei Yang <albinwyang@tencent.com>
> Reviewed-by: Chen Yu <yu.c.chen@intel.com>
> Reviewed-by: Kayra Cizmeci <kayracizmeci@gmail.com>
> Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
> Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
> ---
> kernel/sched/stats.h | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h
> index ebe0a7765f98..dc626f99ffd9 100644
> --- a/kernel/sched/stats.h
> +++ b/kernel/sched/stats.h
> @@ -290,7 +290,7 @@ static void sched_info_arrive(struct rq *rq, struct task_struct *t)
> */
> static inline void sched_info_enqueue(struct rq *rq, struct task_struct *t)
> {
> - if (!t->sched_info.last_queued)
> + if (!t->sched_info.last_queued && !t->se.sched_delayed)
> t->sched_info.last_queued = rq_clock(rq);
> }
Will we not have a similar problem with proxy exec? That is, would
t->is_blocked be more appropriate?
Hi Peter, On Tue, 22 Sep 2026 12:55:22 +0200, Peter Zijlstra wrote: > Will we not have a similar problem with proxy exec? That is, would > t->is_blocked be more appropriate? Yes, there is a similar issue: proxy_migrate_task() re-attaches a blocked donor via activate_task() without ENQUEUE_RESTORE -- a fake enqueue that should not update last_queued. Thanks to Kayra for confirming this and for the task_is_blocked() idea. But t->is_blocked can't gate it. In ttwu_runnable(), the real wakeup of a delayed task does its enqueue_task(ENQUEUE_DELAYED) inside the if (p->is_blocked) branch, and is_blocked is only cleared afterwards in ttwu_do_wakeup(). So !is_blocked would suppress exactly the re-arm this patch relies on, and run_delay would never be accounted. task_is_blocked() does look usable for the proxy case: every waker clears blocked_on before waking the task (mutex handoff, ww_mutex die/wound, proxy_needs_return), so it should not be set on a real wakeup enqueue. But it cannot replace se.sched_delayed -- a delayed sleeper has no blocked_on at all -- so it would have to be an additional condition rather than a substitute. Before adding that I would like to reproduce the proxy case and confirm what the donor's run_delay should look like: whether the time it spent blocked on the mutex ought to be dropped like a delayed sleep, or accounted somehow. Please shed some light on how the proxy case could be dealt with, or should this patch only focus on the delayed case? Thanks, Wei Yang
Hi Peter, > Will we not have a similar problem with proxy exec? > That is, would t->is_blocked be more appropriate? Yeah. Only place that sets is_blocked to 1 is try_to_block_task(), which gets only called from __schedule. Also, we give should_block param. !task_is_blocked(), so if there are a pointer we return false on try_to_block_task(), so on task_is_blocked() true case, and that means proxy tasks stay in rq. With blocked 1. So yeah, we have a similar problem on proxy exec. To the second one tho, I think using task_is_blocked() would be better. Since, the current code looks to that for whether or not queue the proxy. And, on some places is_blocked is cleared after enqueu. My brain stopped working 2 hours ago, this proxy execution stuff combined with delayed fries my brain. So I might be wrong, please correct me if so. as far as I can tell reading tho. I'm sick of using tho. tho. :-) Thanks, Kayra
© 2016 - 2026 Red Hat, Inc.