[libc-commits] [libc] [libc][CndVar] switch to doubly-linked list and implement timeout (PR #191827)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Mon Apr 13 07:50:08 PDT 2026


https://github.com/SchrodingerZhu created https://github.com/llvm/llvm-project/pull/191827

None

>From 977ebd8f386a2f30b29871fafeece565b1fe412d Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 13 Apr 2026 10:44:19 -0400
Subject: [PATCH] [libc][CndVar] switch to doubly-linked list and implement
 timeout

---
 libc/src/__support/threads/CndVar.h         | 40 +++++++--
 libc/src/__support/threads/linux/CndVar.cpp | 97 +++++++++++++++------
 libc/src/threads/linux/cnd_wait.cpp         |  3 +-
 3 files changed, 103 insertions(+), 37 deletions(-)

diff --git a/libc/src/__support/threads/CndVar.h b/libc/src/__support/threads/CndVar.h
index 406bcf193704e..4aa6c9dbd24c3 100644
--- a/libc/src/__support/threads/CndVar.h
+++ b/libc/src/__support/threads/CndVar.h
@@ -10,43 +10,69 @@
 #define LLVM_LIBC___SUPPORT_SRC_THREADS_LINUX_CNDVAR_H
 
 #include "hdr/stdint_proxy.h" // uint32_t
+#include "src/__support/CPP/optional.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/linux/futex_utils.h" // Futex
 #include "src/__support/threads/mutex.h"             // Mutex
 #include "src/__support/threads/raw_mutex.h"         // RawMutex
+#include "src/__support/time/abs_timeout.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
+// TODO: this implementation seems to be contention heavy. We probably want to
+// use a more efficient implementation.
+// TODO: thread local stack is not accessible in shared mode. We need to either
+// fallback to single futex word under shared memory or use a totally
+// different implementation.
+// TODO: node clean up needs to be registered into pthread cancellation point
+// cleanup routine once that functionality is available.
 class CndVar {
   enum CndWaiterStatus : uint32_t {
     WS_Waiting = 0xE,
     WS_Signalled = 0x5,
   };
 
-  struct CndWaiter {
+  struct WQNode {
+    WQNode *prev;
+    WQNode *next;
+  };
+
+  struct CndWaiter : WQNode {
     Futex futex_word = WS_Waiting;
-    CndWaiter *next = nullptr;
   };
 
-  CndWaiter *waitq_front;
-  CndWaiter *waitq_back;
+  WQNode waitq;
   RawMutex qmtx;
 
 public:
+  enum class Result {
+    Success,
+    MutexError,
+    Timeout,
+  };
+
   LIBC_INLINE static int init(CndVar *cv) {
-    cv->waitq_front = cv->waitq_back = nullptr;
+    cv->waitq.prev = cv->waitq.next = nullptr;
     RawMutex::init(&cv->qmtx);
     return 0;
   }
 
   LIBC_INLINE static void destroy(CndVar *cv) {
-    cv->waitq_front = cv->waitq_back = nullptr;
+    cv->waitq.prev = cv->waitq.next = nullptr;
   }
 
   // Returns 0 on success, -1 on error.
-  int wait(Mutex *m);
+  Result wait(Mutex *m,
+              cpp::optional<internal::AbsTimeout> timeout = cpp::nullopt);
   void notify_one();
   void broadcast();
+
+private:
+  void ensure_cyclic_queue();
+  void push_back(CndWaiter *w);
+  CndWaiter *pop_front();
+  void remove(CndWaiter *w);
+  CndWaiter *take_all();
 };
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/threads/linux/CndVar.cpp b/libc/src/__support/threads/linux/CndVar.cpp
index 60424673e819c..4131e532b8d2c 100644
--- a/libc/src/__support/threads/linux/CndVar.cpp
+++ b/libc/src/__support/threads/linux/CndVar.cpp
@@ -13,12 +13,17 @@
 #include "src/__support/threads/linux/futex_word.h" // FutexWordType
 #include "src/__support/threads/mutex.h"            // Mutex
 #include "src/__support/threads/raw_mutex.h"        // RawMutex
-
+#include "src/__support/time/monotonicity.h"
 #include <sys/syscall.h> // For syscall numbers.
 
+#ifndef LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
+#define LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY 1
+#endif
+
 namespace LIBC_NAMESPACE_DECL {
 
-int CndVar::wait(Mutex *m) {
+CndVar::Result CndVar::wait(Mutex *m,
+                            cpp::optional<internal::AbsTimeout> timeout) {
   // The goal is to perform "unlock |m| and wait" in an
   // atomic operation. However, it is not possible to do it
   // in the true sense so we do it in spirit. Before unlocking
@@ -27,18 +32,10 @@ int CndVar::wait(Mutex *m) {
   // the waiter before the waiter actually starts waiting, the
   // wait operation will not begin at all and the waiter immediately
   // returns.
-
   CndWaiter waiter;
   {
     cpp::lock_guard ml(qmtx);
-    CndWaiter *old_back = nullptr;
-    if (waitq_front == nullptr) {
-      waitq_front = waitq_back = &waiter;
-    } else {
-      old_back = waitq_back;
-      waitq_back->next = &waiter;
-      waitq_back = &waiter;
-    }
+    push_back(&waiter);
 
     if (m->unlock() != MutexError::NONE) {
       // If we do not remove the queued up waiter before returning,
@@ -46,36 +43,35 @@ int CndVar::wait(Mutex *m) {
       // waiter. Note also that we do this with |qmtx| locked. This
       // ensures that another thread will not signal the withdrawing
       // waiter.
-      waitq_back = old_back;
-      if (waitq_back == nullptr)
-        waitq_front = nullptr;
-      else
-        waitq_back->next = nullptr;
+      remove(&waiter);
 
-      return -1;
+      return Result::MutexError;
     }
   }
-
-  waiter.futex_word.wait(WS_Waiting, cpp::nullopt, true);
+#if LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
+  if (timeout.has_value())
+    internal::ensure_monotonicity(*timeout);
+#endif
+  if (waiter.futex_word.wait(WS_Waiting, timeout, true) == -ETIMEDOUT) {
+    cpp::lock_guard ml(qmtx);
+    remove(&waiter);
+    return Result::Timeout;
+  }
 
   // At this point, if locking |m| fails, we can simply return as the
   // queued up waiter would have been removed from the queue.
   auto err = m->lock();
-  return err == MutexError::NONE ? 0 : -1;
+  return err == MutexError::NONE ? Result::Success : Result::MutexError;
 }
 
 void CndVar::notify_one() {
   // We don't use an RAII locker in this method as we want to unlock
   // |qmtx| and signal the waiter using a single FUTEX_WAKE_OP signal.
   qmtx.lock();
-  if (waitq_front == nullptr)
+  CndWaiter *first = pop_front();
+  if (first == nullptr)
     qmtx.unlock();
 
-  CndWaiter *first = waitq_front;
-  waitq_front = waitq_front->next;
-  if (waitq_front == nullptr)
-    waitq_back = nullptr;
-
   qmtx.reset();
 
   // this is a special WAKE_OP, so we use syscall directly
@@ -86,10 +82,10 @@ void CndVar::notify_one() {
 }
 
 void CndVar::broadcast() {
+  // needs to hold until broadcast is done to avoid timeout race condition
   cpp::lock_guard ml(qmtx);
+  CndWaiter *waiter = take_all();
   uint32_t dummy_futex_word;
-  CndWaiter *waiter = waitq_front;
-  waitq_front = waitq_back = nullptr;
   while (waiter != nullptr) {
     // FUTEX_WAKE_OP is used instead of just FUTEX_WAKE as it allows us to
     // atomically update the waiter status to WS_Signalled before waking
@@ -99,8 +95,51 @@ void CndVar::broadcast() {
         FUTEX_SYSCALL_ID, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1,
         &waiter->futex_word.val,
         FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting));
-    waiter = waiter->next;
+    waiter = static_cast<CndWaiter *>(waiter->next);
   }
 }
 
+void CndVar::ensure_cyclic_queue() {
+  if (waitq.prev || waitq.next)
+    return;
+  waitq.prev = waitq.next = &waitq;
+}
+
+void CndVar::push_back(CndWaiter *w) {
+  ensure_cyclic_queue();
+  w->next = &waitq;
+  w->prev = waitq.prev;
+  w->next->prev = w;
+  w->prev->next = w;
+}
+
+CndVar::CndWaiter *CndVar::pop_front() {
+  ensure_cyclic_queue();
+  if (waitq.next == &waitq)
+    return nullptr;
+  CndWaiter *first = static_cast<CndWaiter *>(waitq.next);
+  remove(first);
+  return first;
+}
+
+CndVar::CndWaiter *CndVar::take_all() {
+  ensure_cyclic_queue();
+  if (waitq.next == &waitq)
+    return nullptr;
+  waitq.next->prev = nullptr;
+  waitq.prev->next = nullptr;
+  CndVar::CndWaiter *first = static_cast<CndWaiter *>(waitq.next);
+  waitq.next = waitq.prev = &waitq;
+  return first;
+}
+
+void CndVar::remove(CndWaiter *w) {
+  // node may have already been removed from the queue
+  if (w->next == nullptr || w->prev == nullptr)
+    return;
+  w->next->prev = w->prev;
+  w->prev->next = w->next;
+  w->next = w->prev = nullptr;
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/threads/linux/cnd_wait.cpp b/libc/src/threads/linux/cnd_wait.cpp
index 3633cc85277b9..d7d9687a144a1 100644
--- a/libc/src/threads/linux/cnd_wait.cpp
+++ b/libc/src/threads/linux/cnd_wait.cpp
@@ -21,7 +21,8 @@ static_assert(sizeof(CndVar) == sizeof(cnd_t));
 LLVM_LIBC_FUNCTION(int, cnd_wait, (cnd_t * cond, mtx_t *mtx)) {
   CndVar *cndvar = reinterpret_cast<CndVar *>(cond);
   Mutex *mutex = reinterpret_cast<Mutex *>(mtx);
-  return cndvar->wait(mutex) ? thrd_error : thrd_success;
+  return cndvar->wait(mutex) == CndVar::Result::Success ? thrd_success
+                                                        : thrd_error;
 }
 
 } // namespace LIBC_NAMESPACE_DECL



More information about the libc-commits mailing list