Mechanism: Scheduler::is_current_idle_thread and the dead Scheduler::try_schedule acquire the global SCHEDULER spin lock with interrupts enabled. If an EL1 IRQ lands while the same CPU holds that lock, the IRQ reschedule path reaches lock_for_context_switch and blocks on SCHEDULER.lock(), self-deadlocking the holder; peer vCPUs then pile onto the same lock, while the watchdog is structurally silent because its host timer interrupt is masked on the spinning CPU.
Status on main: latent. is_current_idle_thread has zero callers on main, and try_schedule is dead code, so main does not currently expose the producer through a live caller.
Repaired by slice 2 branch fix/789-sched-lock-irq-shape.
Mechanism: Scheduler::is_current_idle_thread and the dead Scheduler::try_schedule acquire the global SCHEDULER spin lock with interrupts enabled. If an EL1 IRQ lands while the same CPU holds that lock, the IRQ reschedule path reaches lock_for_context_switch and blocks on SCHEDULER.lock(), self-deadlocking the holder; peer vCPUs then pile onto the same lock, while the watchdog is structurally silent because its host timer interrupt is masked on the spinning CPU.
Status on main: latent. is_current_idle_thread has zero callers on main, and try_schedule is dead code, so main does not currently expose the producer through a live caller.
Repaired by slice 2 branch fix/789-sched-lock-irq-shape.