diff --git a/src/context/switch.rs b/src/context/switch.rs index d054734248..09d7e550c4 100644 --- a/src/context/switch.rs +++ b/src/context/switch.rs @@ -426,7 +426,15 @@ fn steal_work( } if matches!(sw, UpdateResult::Blocked) { - idle_contexts(token.downgrade()).push_back(context_ref); + // Use try_lock to avoid deadlocking with + // wakeup_contexts (which holds IDLE_CONTEXTS then + // waits for our victim_lock). If IDLE_CONTEXTS is + // busy (owned by another CPU's wakeup_contexts), + // skip the push-back; the context will be re-checked + // on the next wakeup round. + if let Some(mut idle) = idle_contexts_try(token.downgrade()) { + idle.push_back(context_ref); + } } else { victim_queues[prio].push_back(context_ref); } @@ -584,6 +592,18 @@ fn wakeup_contexts(token: &mut CleanLockToken, percpu: &PercpuBlock, switch_time idle_contexts.push_back(context_ref); } + // Drop IDLE_CONTEXTS before acquiring SchedQueuesLock to avoid a + // deadlock with steal_work on another CPU. The previous code held + // both locks simultaneously: wakeup_contexts (this fn) held + // IDLE_CONTEXTS, then waited for SchedQueuesLock on its own CPU; + // steal_work on another CPU held that same SchedQueuesLock and + // waited for IDLE_CONTEXTS. Classic circular wait. + // + // The wakeups Vec is the only data we need from IDLE_CONTEXTS; + // releasing the lock here means steal_work on another CPU can + // proceed while this CPU is acquiring SchedQueuesLock. + drop(idle_contexts); + if wakeups.is_empty() { return; }