[RFC PATCH 1/1] sched/proxy_exec: detect cycles in proxy walks

From: Hui Su

Date: Mon Sep 14 2026 - 14:29:43 EST


Proxy execution follows blocked_on relationships to find a runnable lock
owner. A cycle in that chain can make find_proxy_task() loop indefinitely
while holding rq->lock.

Use Brent checkpoint state directly in the real owner walk. Cycle detection
reuses the owner resolution already performed by that walk and requires no
separate preflight traversal. The checkpoint, power, and span state are all
invocation-local.

Keep the existing owner == p wakeup-race handling ahead of cycle detection.
Unlike a sequence-marker approach, this adds no task_struct or runqueue
state and requires no activation-time reset.

The online walk can temporarily install a blocked_donor cycle before the
delayed Brent detection point. In the tested recovery path, the selected
task's blocked_donor was cleared before it resumed. A forced-stale control
also confirmed that mutex handoff revalidates the donor's blocked_on
relationship before consuming a backlink. Validation of this trade-off and
comparative measurements against the sequence-marker approach are included
in the cover letter.

Signed-off-by: Hui Su <sh_def@xxxxxxx>
---
kernel/sched/core.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index b998ef6b87af..debf313ed9fd 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -6914,6 +6914,9 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
__must_hold(__rq_lockp(rq))
{
struct task_struct *owner = NULL;
+ struct task_struct *cycle_checkpoint = donor;
+ unsigned int cycle_power = 1;
+ unsigned int cycle_span = 0;
bool curr_in_chain = false;
int this_cpu = cpu_of(rq);
struct task_struct *p;
@@ -6921,6 +6924,13 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)

/* Follow blocked_on chain. */
for (p = donor; p->is_blocked; p = owner) {
+ /* Keep Brent's checkpoint state local to this owner walk. */
+ if (cycle_span == cycle_power) {
+ cycle_checkpoint = p;
+ cycle_power <<= 1;
+ cycle_span = 0;
+ }
+
/* if its PROXY_WAKING, do return migration or run if current */
struct mutex *mutex = p->blocked_on;
if (!mutex) {
@@ -7035,6 +7045,15 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
*/
return proxy_resched_idle(rq);
}
+
+ cycle_span++;
+ if (owner == cycle_checkpoint) {
+ pr_warn_once("sched/pe: deadlock cycle detected, pid %d\n",
+ p->pid);
+ __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.55.0