FUTEX_WAIT(2const)

πŸ’€ FUTEX_WAIT β€” like, the 2const one, bestie ✨

okay so. you know how sometimes you just gotta nap until somebody pokes you? that's this. this is a section of the futex(2) saga (it's filed under section 2const, iykyk), and it's literally just: "check the number, and if it's still the number I think it is, go to sleep until someone wakes me up." iconic behaviour, honestly πŸ’…


🌸 NAME 🌸

FUTEX_WAIT β€” sleep waiting for a FUTEX_WAKE operation

like. the whole entire job. you wait. someone else wakes. the end 🫢


πŸ“š LIBRARY πŸ“š

Standard C library (libc, -lc) β€” shoutout to libc for existing, we love her


✨ SYNOPSIS ✨

#include <linux/futex.h>  /* Definition of FUTEX_* constants */
#include <sys/syscall.h>  /* Definition of SYS_* constants */
#include <unistd.h>

long syscall(SYS_futex, uint32_t *uaddr, FUTEX_WAIT, uint32_t val,
             const struct timespec *_Nullable timeout);

yes bestie you call it through syscall(2), no there is not a cute little libc wrapper for you, this is raw syscall energy πŸ”₯


πŸ’– DESCRIPTION πŸ’–

so here's the vibe. this operation peeks at the futex word that uaddr points to, checks it still holds the expected value val, and β€” if the answer is yes β€” goes to sleep waiting for a FUTEX_WAKE(2const) operation to come along and wake it up. 😴

not me making sure the value hasn't changed before I pass out, that's called ✨self-respect✨

that load of the futex word? it's an atomic memory access, fr fr β€” like, actual atomic machine instructions of the respective architecture. and here's the juicy part: that load, the comparison against the expected value, and the starting to sleep all happen atomically and totally ordered with respect to other futex operations on the same futex word. no gaps, no sneaky reordering, no crumbs. πŸŽ€

if the thread actually starts to sleep, we call it a waiter on this futex word. and if the futex value does not match val? the call bails immediately with the error EAGAIN. no nap for you, bestie. πŸ’€

okay so why the comparison though? the whole point is to prevent lost wake-ups. picture it: another thread changed the value of the futex word after our calling thread decided to block based on the prior value, and that other thread ran a FUTEX_WAKE(2const) operation (or some similar wake-up) after the value change but before this FUTEX_WAIT operation. what happens? the calling thread will observe the value change and will not start to sleep. saved from the void. we love a plot twist 😭

now, the timeout:

  • if timeout is not NULL, the structure it points to specifies a timeout for the wait. πŸ•°οΈ (heads up: this interval will be rounded up to the system clock granularity, and it is guaranteed not to expire early β€” she's punctual, but never too early, queen behaviour)
  • if timeout is NULL, the call blocks indefinitely. forever. until the heat death of the universe or a wake, whichever hits first. πŸ™ˆ

🫢 RETURN VALUE 🫢

on error, -1 is returned, and errno is set to indicate the error. classic.

on success, FUTEX_WAIT returns 0 if the caller was woken up. BUT β€” and this is the important tea β€” a wake-up can also be caused by common futex usage patterns in unrelated code that happened to have previously used the futex word's memory location. (like, typical futex-based implementations of Pthreads mutexes can cause this under some conditions. messy!! πŸ™ˆ)

so therefore: callers should always conservatively assume that a return value of 0 can mean a spurious wake-up, and use the futex word's value (i.e., the user-space synchronization scheme) to decide whether to continue to block or not. don't trust the 0, trust the value. periodt. πŸ‘‘


😀 ERRORS 😀

see futex(2) for the whole saga. but specifically here:

  • EAGAIN or EWOULDBLOCK β€” the value pointed to by uaddr was not equal to the expected value val at the time of the call. (yes they're the same thing here, siblings, twins, whatever πŸ’…)
  • EFAULT β€” timeout did not point to a valid user-space address. that pointer is giving nowhere. πŸ’€
  • EINTR β€” the operation was interrupted by a signal (see signal(7)). also, before Linux 2.6.22, this error could also be returned for a spurious wakeup; since Linux 2.6.22, this no longer happens. glow-up confirmed ✨
  • EINVAL β€” the supplied timeout argument was invalid (tv_sec was less than zero, or tv_nsec was not less than 1,000,000,000). bestie we do not do negative seconds in this house 🚫
  • ETIMEDOUT β€” the timeout expired before the operation completed. she waited, she gave up, no shame. 😭

πŸ“ STANDARDS πŸ“

Linux. just Linux. that's the whole standard. we're built different 🌈


πŸ¦‹ HISTORY πŸ¦‹

Linux 2.6.0. ancient. vintage. still slaying. πŸ‘‘


⚠️ CAVEATS ⚠️

okay this one actually matters so listen up bestie:

timeout is interpreted as a relative value. this differs from other futex operations, where timeout is interpreted as an absolute value. πŸ‘€

not me being the ONE futex operation that measures time differently, main character energy fr

to obtain the equivalent of FUTEX_WAIT with an absolute timeout, employ FUTEX_WAIT_BITSET(2const) with val3 specified as FUTEX_BITSET_MATCH_ANY. that's the move. copy that. πŸ“


πŸ‘€ SEE ALSO πŸ‘€


and that's the whole page, bestie! this one came out of Linux man-pages 6.20, dated 2026-02-10 β€” so yeah, fresh, current, and she's still sleeping on it πŸ›ŒπŸ’– go wait on a futex, you deserve it βœ¨πŸ’…πŸ¦‹

πŸ“¦ where this page comes from
  • arch rolling: man-pages 6.20-1 Β· BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms