[libc-commits] [libc] fix 203411: EAGAIN treated as timeout in mutex and rwlock (PR #203574)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Fri Jun 12 09:17:27 PDT 2026
https://github.com/SchrodingerZhu created https://github.com/llvm/llvm-project/pull/203574
fix 203411. This PR addresses the problem that EAGAIN may be treated as timeout in mutex and rwlock. Two changes are applied: 1. timeout site always explicitly check for timeout now to make the logic more robust; 2. the futex wait now discards the error of EAGAIN/EWOULDBLOCK and returns 0; We don't distinguish waking up from signal and waking up from mismatch for the following 3 reasons: - We have userspace guard to avoid futex syscall if we already know value would match, it seems awkward to make that check returns error, as we may wake up and loop back to the check, where signal is consumed but we still return error....; - futex syscall can spuriously wake up anyway, there is no way to tell whether the signal is "indeed" consumed; - other platforms like darwin does not distinguish these states either. Assisted-by: Gemini powered automation tools (human-in-the-loop).
TAG=agy
CONV=5e48f6bc-22e7-4614-a572-5ed16ff5f9a9
>From 00edecd208f2b7ed0e884f34661927d374e32863 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 12 Jun 2026 09:16:48 -0700
Subject: [PATCH] fix 203411. This PR addresses the problem that EAGAIN may be
treated as timeout in mutex and rwlock. Two changes are applied: 1. timeout
site always explicitly check for timeout now to make the logic more robust;
2. the futex wait now discards the error of EAGAIN/EWOULDBLOCK and returns 0;
We don't distinguish waking up from signal and waking up from mismatch for
the following 3 reasons: - We have userspace guard to avoid futex syscall if
we already know value would match, it seems awkward to make that check
returns error, as we may wake up and loop back to the check, where signal is
consumed but we still return error....; - futex syscall can spuriously wake
up anyway, there is no way to tell whether the signal is "indeed" consumed; -
other platforms like darwin does not distinguish these states either.
Assisted-by: Gemini powered automation tools (human-in-the-loop).
TAG=agy
CONV=5e48f6bc-22e7-4614-a572-5ed16ff5f9a9
---
libc/src/__support/threads/linux/futex_utils.h | 5 +++++
libc/src/__support/threads/raw_mutex.h | 4 ++--
libc/src/__support/threads/raw_rwlock.h | 5 +++--
.../integration/src/__support/threads/futex_requeue_test.cpp | 2 +-
4 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/libc/src/__support/threads/linux/futex_utils.h b/libc/src/__support/threads/linux/futex_utils.h
index ff6b5d526a3c1..aa3499dd8a7b7 100644
--- a/libc/src/__support/threads/linux/futex_utils.h
+++ b/libc/src/__support/threads/linux/futex_utils.h
@@ -66,6 +66,11 @@ class Futex : public cpp::Atomic<FutexWordType> {
if (ret == -EINTR)
continue;
+ // EAGAIN and EWOULDBLOCK will be treated as a normal finish, as the value
+ // has changed.
+ if (ret == -EAGAIN || ret == -EWOULDBLOCK)
+ return 0;
+
if (ret < 0)
return cpp::unexpected(-ret);
return ret;
diff --git a/libc/src/__support/threads/raw_mutex.h b/libc/src/__support/threads/raw_mutex.h
index e216e0e95b77c..262899db24009 100644
--- a/libc/src/__support/threads/raw_mutex.h
+++ b/libc/src/__support/threads/raw_mutex.h
@@ -83,8 +83,8 @@ class RawMutex {
futex.exchange(IN_CONTENTION, cpp::MemoryOrder::ACQUIRE) == UNLOCKED)
return true;
// Contention persists. Park the thread and wait for further notification.
- if (!futex.wait(IN_CONTENTION, timeout, is_pshared).has_value() &&
- timeout.has_value())
+ if (ErrorOr<int> res = futex.wait(IN_CONTENTION, timeout, is_pshared);
+ !res.has_value() && res.error() == ETIMEDOUT)
return false;
// Continue to spin after waking up.
diff --git a/libc/src/__support/threads/raw_rwlock.h b/libc/src/__support/threads/raw_rwlock.h
index 987cfa4f82152..c0140b213a088 100644
--- a/libc/src/__support/threads/raw_rwlock.h
+++ b/libc/src/__support/threads/raw_rwlock.h
@@ -400,8 +400,9 @@ class RawRwLock {
// reached.
bool timeout_flag = false;
if (!old.can_acquire<role>(get_preference())) {
- auto wait_result = queue.wait<role>(serial_number, timeout, is_pshared);
- timeout_flag = (!wait_result.has_value() && timeout.has_value());
+ ErrorOr<int> wait_result = queue.wait<role>(serial_number, timeout, is_pshared);
+ timeout_flag =
+ (!wait_result.has_value() && wait_result.error() == ETIMEDOUT);
}
// Phase 7: unregister ourselves as a pending reader/writer.
diff --git a/libc/test/integration/src/__support/threads/futex_requeue_test.cpp b/libc/test/integration/src/__support/threads/futex_requeue_test.cpp
index b6ce58d2d1da8..ab13d36a79922 100644
--- a/libc/test/integration/src/__support/threads/futex_requeue_test.cpp
+++ b/libc/test/integration/src/__support/threads/futex_requeue_test.cpp
@@ -29,7 +29,7 @@ struct SharedState {
void wait_until_zero(LIBC_NAMESPACE::Futex &futex) {
while (futex.load() != 0) {
auto wait_result = futex.wait(1, LIBC_NAMESPACE::cpp::nullopt, false);
- ASSERT_TRUE(wait_result.has_value() || wait_result.error() == EAGAIN);
+ ASSERT_TRUE(wait_result.has_value());
}
}
More information about the libc-commits
mailing list