What is priority inversion?
The core scenario
Priority inversion happens when a high-priority task is blocked waiting on a resource held by a low-priority task, and that low-priority task gets delayed because the scheduler keeps running medium-priority work instead.
High priority task: waiting for lock ──────────────────────▶ (blocked the whole time)
Low priority task: holds lock ──X (preempted, never releases lock)
Medium priority tasks: ████████████████████████████████ (keep running, since they outrank Low)
Why this matters concretely on iOS
The classic example is the Mars Pathfinder incident (1997). On iOS, this shows up as: a .background/.utility QoS task holding a lock a .userInteractive task needs — resulting in unexplained UI jank/hangs.
The classic mitigation: priority inheritance
The scheduler temporarily boosts the lock-holder’s priority to match the highest-priority waiter. NSLock/pthread_mutex have some priority inheritance behavior; DispatchSemaphore notably does not.
How Swift’s concurrency model changes (but doesn’t eliminate) this
Task priority inheritance/escalation; actors process work without the traditional lock-holding pattern, though not immune if a low-priority task occupies an actor’s turn.
Why this is another “compiler can’t catch it” bug class
Same category as deadlocks and reentrancy — about scheduling behavior and timing, not aliasing or data races.
One-liner if pressed: “Priority inversion is when a high-priority task is blocked on a resource held by a low-priority task, and that low-priority task can’t finish because medium-priority work keeps preempting it. The classic fix is priority inheritance — Apple’s mutex primitives do this to a degree (though DispatchSemaphore doesn’t), and Swift’s Task scheduler has some built-in escalation — but it’s a scheduling/timing problem the compiler has no visibility into.”