[libc-commits] [libc] [libc] fix EAGAIN being 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:23:15 PDT 2026


https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/203574

>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 1/2] 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());
   }
 }
 

>From 4a93f639f6cf00156a946b32e527922d424f6d03 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 12 Jun 2026 09:20:04 -0700
Subject: [PATCH 2/2] format

---
 libc/src/__support/threads/raw_rwlock.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libc/src/__support/threads/raw_rwlock.h b/libc/src/__support/threads/raw_rwlock.h
index c0140b213a088..b08d8e4c0768a 100644
--- a/libc/src/__support/threads/raw_rwlock.h
+++ b/libc/src/__support/threads/raw_rwlock.h
@@ -400,7 +400,8 @@ class RawRwLock {
       // reached.
       bool timeout_flag = false;
       if (!old.can_acquire<role>(get_preference())) {
-        ErrorOr<int> wait_result = queue.wait<role>(serial_number, timeout, is_pshared);
+        ErrorOr<int> wait_result =
+            queue.wait<role>(serial_number, timeout, is_pshared);
         timeout_flag =
             (!wait_result.has_value() && wait_result.error() == ETIMEDOUT);
       }



More information about the libc-commits mailing list