The FUTEX_WAIT_REQUEUE_PI Operation: How pthread_cond_wait Atomically Hands You Off From a Condvar to a PI-Mutex

2026-07-15

You've seen FUTEX_LOCK_PI (priority inheritance) and FUTEX_CMP_REQUEUE (moving waiters between queues). Today's operation is what happens when you need both at once: a thread waiting on a priority-inheriting condition variable that must be requeued onto a PI-mutex when signaled.

The naive pthread_cond_wait on a PI-mutex would look like: unlock the mutex, wait on the condvar futex, then re-lock the mutex on wakeup. This has a fatal race. Between wake and re-lock, a lower-priority thread can grab the mutex, and now your high-priority waiter blocks on a mutex owned by a low-priority thread without the kernel knowing to boost it. Priority inversion, silently reintroduced by the very primitive designed to prevent it.

The fix: FUTEX_WAIT_REQUEUE_PI. When the waiter calls this, the kernel puts it to sleep on the condvar futex, but also records that if woken via FUTEX_CMP_REQUEUE_PI, it must be atomically moved onto a PI-mutex's wait queue — with priority inheritance chains recomputed as part of the requeue itself. The waiter never runs user-space code between "woken from condvar" and "owner of PI-mutex or blocked with PI boost active."

Concrete example. A real-time audio thread (SCHED_FIFO priority 80) sleeps on pthread_cond_wait guarded by a PI-mutex. A worker thread (priority 10) holds the mutex, signals the condvar, then releases the mutex. Without WAIT_REQUEUE_PI, a middle-priority thread (priority 40) can slip in during the gap and hold the mutex for a full 5ms scheduling slice — the audio thread misses its 2.9ms buffer deadline and you get a click. With WAIT_REQUEUE_PI, the moment the audio thread is requeued, its priority propagates through the mutex owner chain; the middle thread is preempted, the low thread runs to completion in microseconds.

The kernel-side dance. The requeue op takes both futex addresses, a comparison value (to detect condvar state change and abort atomically), and walks the requeued task through rt_mutex_start_proxy_lock. This installs the waiter into the mutex's PI waiter tree on behalf of a task that isn't even running yet.

Rule of thumb: if you use PTHREAD_PRIO_INHERIT mutexes, glibc automatically uses WAIT_REQUEUE_PI under the hood for pthread_cond_wait. If you're implementing your own condvar over futexes and you support PI-mutexes, you must use this op — using plain FUTEX_WAIT + FUTEX_CMP_REQUEUE silently breaks priority inheritance for anyone who signals while holding the mutex.

Key Takeaway: FUTEX_WAIT_REQUEUE_PI exists because condvar wakeup and PI-mutex acquisition must be a single atomic kernel transition — any user-space gap between them reopens the priority inversion window the PI-mutex was supposed to close.

All newsletters