[PATCH v33 5/7] sched/proxy: Break cyclic proxy chains by deactivating blocked tasks

From: John Stultz

Date: Fri Oct 23 2026 - 18:05:52 EST


From: "zhidao su (Xiaomi)" <soolaugust@xxxxxxxxx>

find_proxy_task() follows the blocked_on/owner chain for a
blocked donor. If the chain forms a cycle, __schedule() can keep
walking the same chain while holding rq->lock.

Kernel locks are not allowed to have cycles (except ww_mutexes,
which we handle properly), so this logic isn't likely to trip
unless a bad driver has incorrect locking (which lockdep should
detect). However, eventually we want to have proxy-enabled
futexes, where we can't control userland's locking, and will
need to avoid wasting cpu time iterating around a cycle.

A PE cycle reproducer creates two tasks blocked on each other's
mutexes. Without a guard, the guest does not complete within a
90s vng timeout. With this change, loading the reproducer
reports:

sched/pe: deadlock cycle detected, pid 128

and a heartbeat kthread pinned to the same CPU continues to run:

pe_cycle_kmod: heartbeat 3 pid 129

Rescheduling idle is not enough here: the same blocked_on
relation remains, so the next scheduling pass can select the
same donor and re-enter the same cycle.

When the walk reaches the original donor again, clear the
current blocked_on relation and deactivate that blocked task. Do
the same for excessive chain depth after revalidating blocked_on
under p->blocked_lock. This removes the task from the proxy-exec
runnable-chain selection path and lets the scheduler make
progress.

Use 1024 as the depth limit to match rtmutex's default
max_lock_depth.

Signed-off-by: zhidao su (Xiaomi) <soolaugust@xxxxxxxxx>
[jstultz: Simplified slightly, dropping WARN_ON and owner == donor
check, slightly reworked commit message]
Signed-off-by: John Stultz <jstultz@xxxxxxxxxx>
---
Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
Cc: Ingo Molnar <mingo@xxxxxxxxxx>
Cc: K Prateek Nayak <kprateek.nayak@xxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Juri Lelli <juri.lelli@xxxxxxxxxx>
Cc: Vincent Guittot <vincent.guittot@xxxxxxxxxx>
Cc: Valentin Schneider <vschneid@xxxxxxxxxx>
Cc: Suleiman Souhlal <suleiman@xxxxxxxxxx>
Cc: Andrea Righi <arighi@xxxxxxxxxx>
Cc: Metin Kaya <Metin.Kaya@xxxxxxx>
Cc: Dietmar Eggemann <dietmar.eggemann@xxxxxxx>
Cc: Steven Rostedt <rostedt@xxxxxxxxxxx>
Cc: Zimuzo Ezeozue <zezeozue@xxxxxxxxxx>
Cc: Waiman Long <longman@xxxxxxxxxx>
Cc: Boqun Feng <boqun.feng@xxxxxxxxx>
Cc: "Paul E. McKenney" <paulmck@xxxxxxxxxx>
Cc: Joel Fernandes <joelagnelf@xxxxxxxxxx>
Cc: Qais Yousef <qyousef@xxxxxxxxxxx>
Cc: Xuewen Yan <xuewen.yan94@xxxxxxxxx>
Cc: kuyo chang <kuyo.chang@xxxxxxxxxxxx>
Cc: hupu <hupu.gm@xxxxxxxxx>
Cc: Zhan Xusheng <zhanxusheng@xxxxxxxxxx>
Cc: zhidao su (Xiaomi) <soolaugust@xxxxxxxxx>
Cc: Hui Su <sh_def@xxxxxxx>
Cc: Shubhang Kaushik (Ampere) <sh@xxxxxxxxxx>
Cc: kernel-team@xxxxxxxxxxx
---
kernel/sched/core.c | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 43ba76b5a3464..12a42252bf474 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -6984,6 +6984,8 @@ static void proxy_migrate_task(struct rq *rq, struct rq_flags *rf,
proxy_reacquire_rq_lock(rq, rf);
}

+#define MAX_PROXY_CHAIN_DEPTH 1024
+
/*
* Find runnable lock owner to proxy for mutex blocked donor
*
@@ -7019,6 +7021,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
int this_cpu = cpu_of(rq);
struct task_struct *p;
int owner_cpu;
+ int chain_depth = 0;

/* Follow blocked_on chain. */
for (p = donor; p->is_blocked; p = owner) {
@@ -7139,6 +7142,13 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
*/
goto resched_idle;
}
+
+ /* Limit the depth we'll proxy to avoid cyclic deadlocks */
+ if (++chain_depth > MAX_PROXY_CHAIN_DEPTH) {
+ __clear_task_blocked_on(p, NULL);
+ goto deactivate;
+ }
+
/*
* OK, now we're absolutely sure @owner is on this
* rq, therefore holding @rq->lock is sufficient to
--
2.56.0.257.gf7497c7d27-goog