[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 08:32:00 PDT 2026
https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/191827
>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 1/3] [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
>From b67ed52377f37a074771945e6935cc480c864ccc Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 13 Apr 2026 11:13:08 -0400
Subject: [PATCH 2/3] expose timedwait
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/CMakeLists.txt | 1 +
libc/include/threads.yaml | 8 +++
libc/src/__support/threads/linux/CndVar.cpp | 13 +++--
libc/src/threads/CMakeLists.txt | 7 +++
libc/src/threads/cnd_timedwait.h | 22 ++++++++
libc/src/threads/linux/CMakeLists.txt | 12 +++++
libc/src/threads/linux/cnd_timedwait.cpp | 53 +++++++++++++++++++
.../integration/src/threads/CMakeLists.txt | 2 +
.../test/integration/src/threads/cnd_test.cpp | 33 ++++++++++++
12 files changed, 151 insertions(+), 3 deletions(-)
create mode 100644 libc/src/threads/cnd_timedwait.h
create mode 100644 libc/src/threads/linux/cnd_timedwait.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index c0d2f6c39f9f9..a8bf089a48001 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1160,6 +1160,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.threads.cnd_destroy
libc.src.threads.cnd_init
libc.src.threads.cnd_signal
+ libc.src.threads.cnd_timedwait
libc.src.threads.cnd_wait
libc.src.threads.mtx_destroy
libc.src.threads.mtx_init
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index fe393241ce8d0..f901bad85f4d1 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1283,6 +1283,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.threads.cnd_destroy
libc.src.threads.cnd_init
libc.src.threads.cnd_signal
+ libc.src.threads.cnd_timedwait
libc.src.threads.cnd_wait
libc.src.threads.mtx_destroy
libc.src.threads.mtx_init
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 184aa57016ce5..29a68e73fee8f 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1351,6 +1351,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.threads.cnd_destroy
libc.src.threads.cnd_init
libc.src.threads.cnd_signal
+ libc.src.threads.cnd_timedwait
libc.src.threads.cnd_wait
libc.src.threads.mtx_destroy
libc.src.threads.mtx_init
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 07a39c045178e..6543bd972a956 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -331,6 +331,7 @@ add_header_macro(
.llvm-libc-types.thrd_start_t
.llvm-libc-types.tss_t
.llvm-libc-types.tss_dtor_t
+ .llvm-libc-types.struct_timespec
)
add_header_macro(
diff --git a/libc/include/threads.yaml b/libc/include/threads.yaml
index 6f7796e090ab6..461be4eb03992 100644
--- a/libc/include/threads.yaml
+++ b/libc/include/threads.yaml
@@ -63,6 +63,14 @@ functions:
return_type: int
arguments:
- type: cnd_t *
+ - name: cnd_timedwait
+ standards:
+ - stdc
+ return_type: int
+ arguments:
+ - type: cnd_t *
+ - type: mtx_t *
+ - type: const struct timespec *
- name: cnd_wait
standards:
- stdc
diff --git a/libc/src/__support/threads/linux/CndVar.cpp b/libc/src/__support/threads/linux/CndVar.cpp
index 4131e532b8d2c..d3b1230c164d0 100644
--- a/libc/src/__support/threads/linux/CndVar.cpp
+++ b/libc/src/__support/threads/linux/CndVar.cpp
@@ -52,16 +52,21 @@ CndVar::Result CndVar::wait(Mutex *m,
if (timeout.has_value())
internal::ensure_monotonicity(*timeout);
#endif
+ Result res = Result::Success;
if (waiter.futex_word.wait(WS_Waiting, timeout, true) == -ETIMEDOUT) {
cpp::lock_guard ml(qmtx);
remove(&waiter);
- return Result::Timeout;
+ // POSIX.1-2024 says the following:
+ // "When such timeouts occur, pthread_cond_clockwait() shall nonetheless
+ // release and re-acquire the mutex referenced by mutex, and may consume a
+ // condition signal directed concurrently at the condition variable."
+ res = 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 ? Result::Success : Result::MutexError;
+ return err == MutexError::NONE ? res : Result::MutexError;
}
void CndVar::notify_one() {
@@ -82,7 +87,9 @@ void CndVar::notify_one() {
}
void CndVar::broadcast() {
- // needs to hold until broadcast is done to avoid timeout race condition
+ // TODO: currently, we need to hold lock until broadcast is done to avoid
+ // timeout race condition. We could mimic musl to alternate the list state
+ // first and then wake or requeue after releasing the lock.
cpp::lock_guard ml(qmtx);
CndWaiter *waiter = take_all();
uint32_t dummy_futex_word;
diff --git a/libc/src/threads/CMakeLists.txt b/libc/src/threads/CMakeLists.txt
index 17dea3920a07a..925f77235a6d1 100644
--- a/libc/src/threads/CMakeLists.txt
+++ b/libc/src/threads/CMakeLists.txt
@@ -193,6 +193,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.cnd_wait
)
+add_entrypoint_object(
+ cnd_timedwait
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.cnd_timedwait
+)
+
add_entrypoint_object(
cnd_signal
ALIAS
diff --git a/libc/src/threads/cnd_timedwait.h b/libc/src/threads/cnd_timedwait.h
new file mode 100644
index 0000000000000..3056b8bbddeaf
--- /dev/null
+++ b/libc/src/threads/cnd_timedwait.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for cnd_timedwait function -------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_THREADS_CND_TIMEDWAIT_H
+#define LLVM_LIBC_SRC_THREADS_CND_TIMEDWAIT_H
+
+#include "src/__support/macros/config.h"
+
+#include <threads.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+int cnd_timedwait(cnd_t *cond, mtx_t *mutex, const struct timespec *time_point);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_THREADS_CND_TIMEDWAIT_H
diff --git a/libc/src/threads/linux/CMakeLists.txt b/libc/src/threads/linux/CMakeLists.txt
index 3cbf2f85f3f9d..2311d4aa7c35f 100644
--- a/libc/src/threads/linux/CMakeLists.txt
+++ b/libc/src/threads/linux/CMakeLists.txt
@@ -47,6 +47,18 @@ add_entrypoint_object(
libc.src.__support.threads.CndVar
)
+add_entrypoint_object(
+ cnd_timedwait
+ SRCS
+ cnd_timedwait.cpp
+ HDRS
+ ../cnd_timedwait.h
+ DEPENDS
+ libc.include.threads
+ libc.src.__support.threads.mutex
+ libc.src.__support.threads.CndVar
+)
+
add_entrypoint_object(
cnd_signal
SRCS
diff --git a/libc/src/threads/linux/cnd_timedwait.cpp b/libc/src/threads/linux/cnd_timedwait.cpp
new file mode 100644
index 0000000000000..9e3b6455d52d9
--- /dev/null
+++ b/libc/src/threads/linux/cnd_timedwait.cpp
@@ -0,0 +1,53 @@
+//===-- Linux implementation of the cnd_timedwait function ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/threads/cnd_timedwait.h"
+
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/threads/CndVar.h"
+#include "src/__support/threads/mutex.h"
+#include "src/__support/time/abs_timeout.h"
+
+#include <threads.h> // cnd_t, mtx_t, thrd_error, thrd_success, thrd_timedout
+
+namespace LIBC_NAMESPACE_DECL {
+
+static_assert(sizeof(CndVar) == sizeof(cnd_t));
+
+LLVM_LIBC_FUNCTION(int, cnd_timedwait,
+ (cnd_t * cond, mtx_t *mtx,
+ const struct timespec *abs_time)) {
+ LIBC_CRASH_ON_NULLPTR(cond);
+ LIBC_CRASH_ON_NULLPTR(mtx);
+ LIBC_CRASH_ON_NULLPTR(abs_time);
+
+ auto timeout =
+ internal::AbsTimeout::from_timespec(*abs_time, /*is_realtime=*/true);
+ if (LIBC_UNLIKELY(!timeout.has_value()))
+ return timeout.error() == internal::AbsTimeout::Error::Invalid
+ ? thrd_error
+ : thrd_timedout;
+
+ CndVar *cndvar = reinterpret_cast<CndVar *>(cond);
+ Mutex *mutex = reinterpret_cast<Mutex *>(mtx);
+ switch (cndvar->wait(mutex, timeout.value())) {
+ case CndVar::Result::Success:
+ return thrd_success;
+ case CndVar::Result::Timeout:
+ return thrd_timedout;
+ case CndVar::Result::MutexError:
+ return thrd_error;
+ }
+ __builtin_unreachable();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/integration/src/threads/CMakeLists.txt b/libc/test/integration/src/threads/CMakeLists.txt
index d74b923751ad6..378d5fb16ab6a 100644
--- a/libc/test/integration/src/threads/CMakeLists.txt
+++ b/libc/test/integration/src/threads/CMakeLists.txt
@@ -108,6 +108,7 @@ add_integration_test(
libc.src.threads.cnd_broadcast
libc.src.threads.cnd_signal
libc.src.threads.cnd_destroy
+ libc.src.threads.cnd_timedwait
libc.src.threads.cnd_wait
libc.src.threads.mtx_destroy
libc.src.threads.mtx_init
@@ -116,4 +117,5 @@ add_integration_test(
libc.src.threads.thrd_create
libc.src.threads.thrd_join
libc.src.threads.linux.threads_utils
+ libc.src.time.clock_gettime
)
diff --git a/libc/test/integration/src/threads/cnd_test.cpp b/libc/test/integration/src/threads/cnd_test.cpp
index 4eaab9ac08bc1..a7c5a2424e484 100644
--- a/libc/test/integration/src/threads/cnd_test.cpp
+++ b/libc/test/integration/src/threads/cnd_test.cpp
@@ -11,6 +11,7 @@
#include "src/threads/cnd_destroy.h"
#include "src/threads/cnd_init.h"
#include "src/threads/cnd_signal.h"
+#include "src/threads/cnd_timedwait.h"
#include "src/threads/cnd_wait.h"
#include "src/threads/mtx_destroy.h"
#include "src/threads/mtx_init.h"
@@ -18,9 +19,12 @@
#include "src/threads/mtx_unlock.h"
#include "src/threads/thrd_create.h"
#include "src/threads/thrd_join.h"
+#include "src/time/clock_gettime.h"
#include "test/IntegrationTest/test.h"
+#include "hdr/time_macros.h"
+
#include <threads.h>
namespace wait_notify_broadcast_test {
@@ -146,8 +150,37 @@ void single_waiter_test() {
} // namespace single_waiter_test
+namespace timedwait_test {
+
+void timedwait_test() {
+ cnd_t cnd;
+ mtx_t mtx;
+
+ ASSERT_EQ(LIBC_NAMESPACE::cnd_init(&cnd), int(thrd_success));
+ ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&mtx, mtx_plain), int(thrd_success));
+ ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&mtx), int(thrd_success));
+
+ timespec timeout;
+ ASSERT_EQ(LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &timeout), 0);
+ timeout.tv_nsec -= 1;
+ if (timeout.tv_nsec < 0) {
+ timeout.tv_nsec = 999999999;
+ timeout.tv_sec -= 1;
+ }
+
+ ASSERT_EQ(LIBC_NAMESPACE::cnd_timedwait(&cnd, &mtx, &timeout),
+ int(thrd_timedout));
+ ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&mtx), int(thrd_success));
+
+ LIBC_NAMESPACE::mtx_destroy(&mtx);
+ LIBC_NAMESPACE::cnd_destroy(&cnd);
+}
+
+} // namespace timedwait_test
+
TEST_MAIN() {
wait_notify_broadcast_test::wait_notify_broadcast_test();
single_waiter_test::single_waiter_test();
+ timedwait_test::timedwait_test();
return 0;
}
>From bf68e07f09d9dea589e37341f92b4a831e6e4329 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 13 Apr 2026 11:31:46 -0400
Subject: [PATCH 3/3] fix a race condition found by copilot
---
libc/src/__support/threads/linux/CndVar.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/libc/src/__support/threads/linux/CndVar.cpp b/libc/src/__support/threads/linux/CndVar.cpp
index d3b1230c164d0..62cdb93f63c86 100644
--- a/libc/src/__support/threads/linux/CndVar.cpp
+++ b/libc/src/__support/threads/linux/CndVar.cpp
@@ -98,11 +98,12 @@ void CndVar::broadcast() {
// atomically update the waiter status to WS_Signalled before waking
// up the waiter. A dummy location is used for the other futex of
// FUTEX_WAKE_OP.
+ CndWaiter *snapshot = waiter;
+ waiter = static_cast<CndWaiter *>(snapshot->next);
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1,
- &waiter->futex_word.val,
+ &snapshot->futex_word.val,
FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting));
- waiter = static_cast<CndWaiter *>(waiter->next);
}
}
More information about the libc-commits
mailing list