2026-06-17
You've already learned that futexes are the foundation of every userspace lock. But what happens if a thread dies — segfault, SIGKILL, abort() — while holding a mutex? The lock word in shared memory still says "owned by TID 4217," but TID 4217 no longer exists. Every other waiter blocks forever. This is the orphaned lock problem, and the kernel's solution is the robust futex list.
The mechanism: each thread registers a linked list head with the kernel via set_robust_list(head, len). Before a thread locks a robust mutex, glibc inserts that mutex's address into the list. Before unlocking, it removes the entry. The list lives entirely in userspace memory — the kernel only stores a pointer to the head.
When a thread exits for any reason, the kernel walks this list. For each mutex still on it, the kernel atomically sets the FUTEX_OWNER_DIED bit (0x40000000) in the lock word and wakes one waiter. The next thread to acquire sees that bit and knows: the previous owner died mid-critical-section; the protected data may be inconsistent. It can then recover or mark the mutex permanently unusable via pthread_mutex_consistent().
Concrete example: A multi-process database where workers share a memory-mapped index protected by a PTHREAD_MUTEX_ROBUST mutex in shared memory. Worker PID 8821 grabs the lock, starts rebalancing a B-tree node, and the OOM killer takes it out. Without the robust list, every other worker hangs forever and you're restarting the whole cluster. With it, the next worker wakes with EOWNERDEAD, validates the half-rewritten node against a journal, calls pthread_mutex_consistent(), and continues.
The tricky bit — the "list_op_pending" slot: there's a race window where a thread has decided to add or remove a mutex from the list but hasn't finished the pointer manipulation. To cover this, the list head has a single extra field that holds the address being modified. The kernel checks this on exit too, so even a death mid-insertion is recoverable.
Rule of thumb: the list walk is bounded — the kernel limits it to 2048 entries (ROBUST_LIST_LIMIT) to prevent a malicious or corrupted list from hanging exit forever. If you're somehow holding more than 2048 robust mutexes on one thread, you have bigger problems.
Check it on a running process: cat /proc/PID/task/TID/status | grep -i robust won't show it, but you can read get_robust_list() via ptrace or inspect /proc/PID/syscall.
FUTEX_OWNER_DIED, letting survivors recover instead of deadlocking forever.
