Issue 203411
Summary [libc] `EAGAIN/EWOULDBLOCK` can be treated as `ETIMEOUT` in `Mutex` and `RwLock`
Labels libc
Assignees
Reporter SchrodingerZhu
    ## Problem Description
Under high contention, timed lock operations (`pthread_mutex_timedlock`, `pthread_rwlock_timedrdlock`, `pthread_rwlock_timedwrlock`) in LLVM libc can spuriously fail and return `ETIMEDOUT` (or `false`) before the specified timeout duration has actually elapsed.

This occurs because the slow path lock acquisition logic in both `RawMutex` and `RawRwLock` incorrectly treats any failure of the underlying `Futex::wait` call as a timeout when a timeout is present.

Specifically, if the futex value in memory changes between the user-space check and the kernel-space check, the kernel's `sys_futex(FUTEX_WAIT)` system call returns `-EAGAIN`. The `Futex::wait` helper propagates this as a `cpp::unexpected(EAGAIN)` error.

The lock implementations check the result as follows:

### In `RawMutex::lock_slow` (src/__support/threads/raw_mutex.h):
```cpp
if (!futex.wait(IN_CONTENTION, timeout, is_pshared).has_value() &&
    timeout.has_value())
  return false; // Returns false (Timeout) on any error, including EAGAIN
```

### In `RawRwLock::lock_slow` (src/__support/threads/raw_rwlock.h):
```cpp
auto wait_result = queue.wait<role>(serial_number, timeout, is_pshared);
timeout_flag = (!wait_result.has_value() && timeout.has_value()); // Sets timeout_flag on EAGAIN
```

Because `EAGAIN` is a normal transition state indicating that the lock state changed and the thread should retry, treating it as a timeout violates correct lock behavior and causes spurious failures under contention.

---

## Proposed Fix
Modify the timeout check in both `RawMutex::lock_slow` and `RawRwLock::lock_slow` to explicitly verify that the error returned by `wait` is indeed `ETIMEDOUT` before failing. Other transient errors like `EAGAIN` should be ignored, allowing the acquisition loop to retry.

### Fix for `RawMutex` (`src/__support/threads/raw_mutex.h`):
```diff
@@ -83,8 +83,8 @@
       if (state != IN_CONTENTION &&
 futex.exchange(IN_CONTENTION, cpp::MemoryOrder::ACQUIRE) == UNLOCKED)
 return true;
-      if (!futex.wait(IN_CONTENTION, timeout, is_pshared).has_value() &&
-          timeout.has_value())
-        return false;
+      auto wait_result = futex.wait(IN_CONTENTION, timeout, is_pshared);
+      if (!wait_result.has_value() && wait_result.error() == ETIMEDOUT)
+        return false;
       state = spin(spin_count);
 }
```

### Fix for `RawRwLock` (`src/__support/threads/raw_rwlock.h`):
```diff
@@ -401,5 +401,5 @@
 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());
+        timeout_flag = (!wait_result.has_value() && wait_result.error() == ETIMEDOUT);
 }
```

_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to