[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