Re: [PATCH RFC v2] sched/proxy: Defer donor commit until after proxy resolution
From: Xukai Wang
Date: Wed Aug 12 2026 - 01:55:14 EST
On 2026/8/12 04:41, John Stultz wrote:
> On Sun, Jul 12, 2026 at 11:22 PM Xukai Wang <kingxukai@xxxxxxxxxxxx> wrote:
>> pick_next_task() currently commits the selected task into the scheduling
>> class state before proxy execution has resolved whether that task should
>> really become the committed donor.
>>
>> With proxy execution enabled, the task returned by pick_next_task() may
>> be blocked. __schedule() then calls find_proxy_task() to resolve the
>> proxy chain. If find_proxy_task() returns NULL, __schedule() retries
>> through pick_again, but the picked donor has already gone through
>> put_prev_set_next_task(). In that case the class current state was
>> speculatively switched to a donor which is not the final scheduling
>> decision.
>>
>> Make pick_next_task() return only the selected candidate task. Move the
>> put_prev_set_next_task() call into __schedule(), next to rq_set_donor(),
>> so the scheduling class commit happens after proxy-chain resolution.
> Hey Xukai!
> Thanks for sending this out, and my apologies for being a little
> slow to respond.
>
> It might help if your commit message focused in to explain a bit more
> about *why* these changes are useful.
>
> ie: Why is the call to set_next_task on the donor problematic if we
> pick a different task to run?
>
> Is this just trying to optimize out the potentially repeated
> put_prev/set_next calls needed if we have to pick_again repeatedly? Or
> are there other benefits?
>
> What is the impact of this change? Is it measurable?
>
> These would be good things to answer at the top of the commit message.
>
> thanks
> -john
Hi John,
Thanks for taking a look. That's a good point, and I should have made
the motivation clearer in the commit message.
The main issue I was trying to address is that pick_next_task() currently
couples picking a task with committing that pick through
put_prev_set_next_task().
That works naturally when the task returned by pick_next_task() is the
final scheduling decision. With proxy execution, however, the picked
task may be a blocked donor and still needs to go through
find_proxy_task(). If proxy resolution returns NULL, __schedule() goes
back to pick_again, and the next pick may be a different task. In that
case, the put_prev_task()/set_next_task() work done for the previous pick
is followed by another put_prev_set_next_task() for the new pick, even
though the previous pick was ultimately abandoned.
The change is therefore mainly intended to avoid that unnecessary
put_prev/set_next work by separating candidate selection from the point
where the donor is actually committed.
I did some temporary instrumentation to measure how frequently this
happens and posted the results in the v1 cover letter and follow-up
discussion, but I should have carried them into the v2 commit message as
well. Sorry about that.
For example, in one 60s proxy-mutex stress run:
spec_commit_blocked 5407
spec_commit_then_null 3224
spec_commit_then_idle 1660
spec_commit_then_success 523
For the 3224 NULL cases, the following retry picked:
null_retry_same_donor 0
null_retry_diff_donor 2258
null_retry_to_idle 966
The temporary counters mean:
- spec_commit_blocked:
pick_next_task() selected a blocked donor, and in
the baseline code that donor had already gone through
put_prev_set_next_task() before proxy-chain resolution.
- spec_commit_then_null:
the blocked donor had already been committed, but
find_proxy_task() returned NULL and __schedule() retried.
- spec_commit_then_idle:
the blocked donor had already been committed, but
find_proxy_task() returned rq->idle.
- spec_commit_then_success:
the blocked donor had already been committed,
and find_proxy_task() successfully found a task to run.
- null_retry_same_donor:
after find_proxy_task() returned NULL, the next pick selected the same
donor again.
- null_retry_diff_donor:
after find_proxy_task() returned NULL, the next pick selected a
different non-idle donor.
- null_retry_to_idle:
after find_proxy_task() returned NULL, the next pick selected idle.
So in that run, none of the NULL retries selected the same donor again:
2258 switched to a different non-idle donor and 966 switched to idle.
Those are cases where the put_prev/set_next work done for the previous
pick is followed by another scheduling transition after proxy resolution
fails.
At this point I only have the frequency measurements above, not a
measurement of how much performance is gained by eliminating those
calls. Prateek also mentioned in the v1 discussion that he would look at
the performance side using schedstats.
I'll put this motivation and the frequency results near the top of the
v3 commit message so the purpose and the scope of the measurements are
clear.
--
Thanks,
Xukai