Re: [PATCH v25 9/9] sched: Handle blocked-waiter migration (and return migration)

From: K Prateek Nayak

Date: Wed Mar 18 2026 - 02:56:55 EST


Hello Juri,

On 3/18/2026 12:05 PM, Juri Lelli wrote:
>> + deactivate_task(rq, p, DEQUEUE_NOCLOCK);
>> + proxy_set_task_cpu(p, target_cpu);
>> +
>> + /*
>> + * We have to zap callbacks before unlocking the rq
>> + * as another CPU may jump in and call sched_balance_rq
>> + * which can trip the warning in rq_pin_lock() if we
>> + * leave callbacks set.
>> + */
>> + zap_balance_callbacks(rq);
>> + rq_unpin_lock(rq, rf);
>> + raw_spin_rq_unlock(rq);
>> +
>> + attach_one_task(target_rq, p);
>
> We release rq lock between deactivate and attach (and we don't hold
> neither wait_lock nor blocked_lock as they are out of scope at this
> point). Can't something like the following happen?
>
> - Task A: blocked on mutex M, queued on CPU 0
> - Task B: owns mutex M, running on CPU 1
>
> CPU 0 (migrating A→CPU 1) CPU 1 (B finishes critical section)
> ------------------------- ------------------------------------
> find_proxy_task(donor=A):
> owner = B, owner_cpu = 1
> action = MIGRATE
> // guard releases wait_lock
>
> proxy_migrate_task(A, cpu=1):
> deactivate_task(rq0, A)
> → A->on_rq = 0

This sets TASK_ON_RQ_MIGRATING
before dequeuing.

block_task() is the only one
that clears task->on_rq now.

> proxy_set_task_cpu(A, 1)
> → A->cpu = 1
> raw_spin_rq_unlock(rq0)
> → RQ0 LOCK RELEASED
> // Task B running
> mutex_unlock(M):
> lock(&M->wait_lock) // ← Can grab it
> A->blocked_on = PROXY_WAKING
> unlock(&M->wait_lock)
> wake_up_q():
> try_to_wake_up(A):

CPU1 see p->on_rq (TASK_ON_RQ_MIGRATING)
and go into ttwu_runnable() and stall
at __task_rq_lock() since it sees
task_on_rq_migrating() ...

attach is done here
A->on_rq is set to
TASK_ON_RQ_QUEUED

... we come back here see
task_on_rq_queued() and simply do a
wakeup_preempt() and bail out early
from try_to_wake_up() path.

> sees A->on_rq == 0
> cpu = select_task_rq(A)
> → returns CPU 2
> set_task_cpu(A, 2)
> ttwu_queue(A, 2)
> → A enqueued on CPU 2
> → A->on_rq = 1, A->cpu = 2
>
> attach_one_task(rq1, A):
> attach_task(rq1, A):
> WARN_ON_ONCE(task_rq(A) != rq1)
> → Fires! task_rq(A) = rq2
> activate_task(rq1, A)
> → Double-enqueue! A->on_rq already = 1

Thus, we avoid that unless I'm mistaken :-)

--
Thanks and Regards,
Prateek