Re: [PATCH v4] sched/stats: Fix run_delay over-count for migrated sched_delayed tasks
From: albin_yang
Date: Sun Oct 25 2026 - 09:07:57 EST
Hi Peter,
Following up on the proxy exec discussion from the v3 thread
(https://lore.kernel.org/all/20260922082432.2987855-1-albin_yang@xxxxxxx/):
I have now reproduced the proxy case.
A userspace mutex is futex based and never sets blocked_on, so I used a small
test module exposing a kernel mutex, with the owner pinned to a known CPU.
While the donor is blocked, the module reports:
donor: blocked-in-module
pid=10364 cpu_before=0 cpu_now=1 on_rq=1 state=2
is_blocked=1 blocked_on=1
i.e. the donor stayed on the runqueue (on_rq=1) as a real proxy donor
(is_blocked=1, blocked_on=1) and was moved to the owner's CPU while blocked
(cpu_before=0, cpu_now=1).
Measured on a 2 CPU VM, 5 iterations each, hold 200 ms (run_delay is field 2
of /proc/<tid>/schedstat):
config block run_delay delta migrated
------------------------------ ------- --------------- --------
cross-cpu, unfixed 169.9 ms 167.1 ms 5/5
cross-cpu, v4 (!sched_delayed) 169.9 ms 167.1 ms 5/5
cross-cpu, + !task_is_blocked() 169.8 ms 0.01 ms 5/5
same-cpu (control) 167.3 ms 0.00 ms 0/5
So the same ~170 ms wait is charged ~167 ms or ~0 depending only on where the
owner happens to run, and v4 alone does not change it -- the two are
independent bugs.
The gate does not disable normal accounting either: with the donor runnable
and contending for the CPU, run_delay still grows by ~39 ms over an 80 ms
window (5/5 iterations, in both the cross-cpu and the same-cpu runs). So it
suppresses only the fake enqueue, not real waits.
On your original question: t->is_blocked still cannot be the gate. In
ttwu_runnable() the real wakeup enqueue sits inside the if (p->is_blocked)
branch, while is_blocked is cleared afterwards in ttwu_do_wakeup(), so
!is_blocked would suppress the very re-arm this patch relies on.
task_is_blocked() does work, but only as an additional condition: with
!sched_delayed && !task_is_blocked() the proxy case drops from 167.1 ms to
0.01 ms, while the delayed dequeue fix is unaffected, since a delayed sleeper
has no blocked_on.
Given the proxy migration only landed in v7.1 while the delayed dequeue bug
exists from v6.12, coupling them would either fail to build
(v6.12.y..v6.16.y, no proxy exec at all) or add a dependency on code that
cannot trigger (v6.17.y..v7.0.y, proxy exec present but no blocked-waiter
migration), so I'll send the proxy one as a separate patch on top of v4.
Thanks,
Wei Yang