Re: [PATCH RFC v2] sched/proxy: Defer donor commit until after proxy resolution
From: Xukai Wang
Date: Mon Aug 10 2026 - 06:56:37 EST
Hi Prateek, John,
A follow-up on this RFC v2.
On 2026/7/13 14:21, Xukai Wang 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.
>
> For proxy execution, keep the picked donor separate from the actual task
> to run:
>
> donor = task selected by pick_next_task()
> next = task returned by find_proxy_task(), possibly the proxy owner
>
> Only commit the donor after proxy-chain resolution succeeds:
>
> put_prev_set_next_task(rq, rq->donor, donor);
> rq_set_donor(rq, donor);
>
> If find_proxy_task() returns NULL, the candidate donor has not been
> committed and __schedule() can retry without first undoing a speculative
> set_next() on that donor.
>
> A proxy candidate is not necessarily the committed rq->donor anymore.
> Update proxy_deactivate() accordingly. If the task being blocked is
> still the committed donor, proxy_resched_idle() is needed to drop
> rq/class current references before block_task() clears ->on_rq. If
> it is only an uncommitted proxy candidate, it is still queued and can
> be blocked directly.
>
> proxy_migrate_task() still switches the rq to idle before dropping the
> rq lock. Migrating a task found in the proxy chain abandons the current
> proxy pick attempt, and switching the committed donor to idle leaves
> rq->donor and the class current state in a defined state while the chain
> is modified and the task is attached elsewhere.
>
> Move zap_balance_callbacks() into proxy_resched_idle(), next to the idle
> commit. The outer NULL/idle proxy-resolution paths no longer zap
> unconditionally: if no put/set was done, no callbacks should have been
> generated there; if the path went through proxy_resched_idle(),
> callbacks are cleared at the point that performed the idle commit.
While revisiting the patch, I noticed one detail that needs to be handled
when deferring put_prev_set_next_task(): rq->dl_server.
On the v2 base, before this change, put_prev_set_next_task() is called
inside pick_next_task(), so rq->dl_server has already been consumed and
cleared by the time __schedule() calls find_proxy_task(). In other words,
find_proxy_task() is entered with rq->dl_server == NULL.
With the commit deferred by this RFC, rq->dl_server can still contain the
state from the candidate pick when find_proxy_task() is called. I think
the original entry state can be preserved by saving and clearing it
around proxy resolution, roughly:
|next = pick_next_task(rq, &rf);|
|donor = next;|
|rq->next_class = donor->sched_class;|
||
|if (sched_proxy_exec()) {|
| donor->blocked_donor = NULL;|
||
| if (unlikely(donor->is_blocked)) {|
| struct sched_dl_entity *donor_dl_server = rq->dl_server;|
| rq->dl_server = NULL;|
||
| next = find_proxy_task(rq, donor, &rf);|
| if (!next)|
| goto pick_again;|
| if (next == rq->idle)|
| goto keep_resched;|
||
| rq->dl_server = donor_dl_server;|
| }|
|...|
|}|
||
|put_prev_set_next_task(rq, rq->donor, donor);|
|rq_set_donor(rq, donor);|
|
|
This keeps rq->dl_server == NULL on entry to find_proxy_task(), as it was
before this RFC, while restoring it for the final donor commit when proxy
resolution succeeds.
Before sending a v3 with this addressed, I wanted to check whether the
overall direction still looks reasonable, particularly whether
find_proxy_task() has any dependency on rq->donor already being updated
to the picked donor before proxy resolution.
--
Best regards,
Xukai