[libc] C11 threads: Add cnd_timedwait and mtx_trylock. (#195966)

Use recently added support for timed_wait in CndVar and try_lock in
Mutex to implement the C11 functions `cnd_timedwait` and `mtx_trylock`
(counterparts of `pthread_cond_timedwait` and `pthread_mutex_trylock`,
respectively).

Fixes a minor edge case in `ensure_monotonicity` conversion where the
converted timeout could fall before epoch - clamp it to be 0 / epoch in
that case.

Assisted by: Gemini
This commit is contained in:
Alexey Samsonov authored and GitHub committed 2026-05-11 10:50:33 -07:00
1 parent 36f60d8486
commit 2204247150
17 files changed
+294 -7

No files matched your search

@@ -1195,10 +1195,12 @@ 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
libc.src.threads.mtx_lock
libc.src.threads.mtx_trylock
libc.src.threads.mtx_unlock
libc.src.threads.thrd_create
libc.src.threads.thrd_current
+2
View File
@@ -1318,10 +1318,12 @@ 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
libc.src.threads.mtx_lock
libc.src.threads.mtx_trylock
libc.src.threads.mtx_unlock
libc.src.threads.thrd_create
libc.src.threads.thrd_current
+2
View File
@@ -1392,10 +1392,12 @@ 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
libc.src.threads.mtx_lock
libc.src.threads.mtx_trylock
libc.src.threads.mtx_unlock
libc.src.threads.thrd_create
libc.src.threads.thrd_current
+1
View File
@@ -340,6 +340,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(
+15
View File
@@ -13,6 +13,7 @@ types:
- type_name: thrd_t
- type_name: tss_t
- type_name: tss_dtor_t
- type_name: struct_timespec
enums:
- name: mtx_plain
value: null
@@ -63,6 +64,14 @@ functions:
return_type: int
arguments:
- type: cnd_t *
- name: cnd_timedwait
standards:
- stdc
return_type: int
arguments:
- type: cnd_t *__restrict
- type: mtx_t *__restrict
- type: const struct timespec *__restrict
- name: cnd_wait
standards:
- stdc
@@ -89,6 +98,12 @@ functions:
return_type: int
arguments:
- type: mtx_t *
- name: mtx_trylock
standards:
- stdc
return_type: int
arguments:
- type: mtx_t *
- name: mtx_unlock
standards:
- stdc
+2 -2
View File
@@ -21,7 +21,7 @@
// Mutex with non-static methods having the following signature:
//
// MutexError lock();
// MutexError trylock();
// MutexError try_lock();
// MutexError timed_lock(...);
// MutexError unlock();
// MutexError reset(); // Used to reset inconsistent robust mutexes.
@@ -61,7 +61,7 @@ struct Mutex {
LIBC_INLINE MutexError lock() { return MutexError::NONE; }
LIBC_INLINE MutexError unlock() { return MutexError::NONE; }
LIBC_INLINE MutexError reset() { return MutexError::NONE; }
LIBC_INLINE MutexError trylock() { return MutexError::NONE; }
LIBC_INLINE MutexError try_lock() { return MutexError::NONE; }
LIBC_INLINE bool is_robust() const { return false; }
};
+1 -1
View File
@@ -48,7 +48,7 @@ LIBC_INLINE timespec convert_clock(timespec input, clockid_t from,
output.tv_sec = input.tv_sec - from_time.tv_sec + to_time.tv_sec;
output.tv_nsec = input.tv_nsec - from_time.tv_nsec + to_time.tv_nsec;
if (output.tv_nsec > 1_s_ns) {
if (output.tv_nsec >= 1_s_ns) {
output.tv_sec++;
output.tv_nsec -= 1_s_ns;
} else if (output.tv_nsec < 0) {
+4
View File
@@ -31,6 +31,10 @@ LIBC_INLINE void ensure_monotonicity(AbsTimeout &timeout) {
convert_clock(timeout.get_timespec(), CLOCK_REALTIME, CLOCK_MONOTONIC),
false);
// Clamp the timeout to epoch if becomes negative after the conversion.
if (!res.has_value() && res.error() == AbsTimeout::Error::BeforeEpoch)
res = AbsTimeout::from_timespec(timespec{0, 0}, false);
LIBC_ASSERT(res.has_value());
if (!res.has_value())
__builtin_unreachable();
+18
View File
@@ -117,6 +117,17 @@ add_entrypoint_object(
libc.src.__support.threads.mutex
)
add_entrypoint_object(
mtx_trylock
SRCS
mtx_trylock.cpp
HDRS
mtx_trylock.h
DEPENDS
libc.include.threads
libc.src.__support.threads.mutex
)
add_entrypoint_object(
mtx_unlock
SRCS
@@ -193,6 +204,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
+22
View File
@@ -0,0 +1,22 @@
//===-- Internal header for cnd_timedwait -----------------------*- 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 *__restrict cond, mtx_t *__restrict mutex,
const struct timespec *__restrict time_point);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_THREADS_CND_TIMEDWAIT_H
+14
View File
@@ -48,6 +48,20 @@ 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
libc.src.__support.time.abs_timeout
libc.src.__support.macros.null_check
)
add_entrypoint_object(
cnd_signal
SRCS
+57
View File
@@ -0,0 +1,57 @@
//===-- 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/macros/config.h"
#include "src/__support/macros/null_check.h"
#include "src/__support/threads/CndVar.h"
#include "src/__support/threads/mutex.h"
#include "src/__support/time/abs_timeout.h"
#include <threads.h>
namespace LIBC_NAMESPACE_DECL {
static_assert(sizeof(CndVar) == sizeof(cnd_t));
static_assert(sizeof(Mutex) == sizeof(mtx_t) &&
alignof(Mutex) == alignof(mtx_t));
LLVM_LIBC_FUNCTION(int, cnd_timedwait,
(cnd_t *__restrict cond, mtx_t *__restrict mtx,
const struct timespec *__restrict time_point)) {
LIBC_CRASH_ON_NULLPTR(time_point);
CndVar *cndvar = reinterpret_cast<CndVar *>(cond);
Mutex *mutex = reinterpret_cast<Mutex *>(mtx);
// time_point is TIME_UTC-based, so we assume realtime clock here.
auto timeout =
internal::AbsTimeout::from_timespec(*time_point, /*realtime=*/true);
if (!timeout.has_value()) {
switch (timeout.error()) {
case internal::AbsTimeout::Error::BeforeEpoch:
return thrd_timedout;
case internal::AbsTimeout::Error::Invalid:
return thrd_error;
}
__builtin_unreachable();
}
switch (cndvar->wait(mutex, timeout.value())) {
case CndVarResult::Success:
return thrd_success;
case CndVarResult::Timeout:
return thrd_timedout;
case CndVarResult::MutexError:
return thrd_error;
}
__builtin_unreachable();
}
} // namespace LIBC_NAMESPACE_DECL
+27
View File
@@ -0,0 +1,27 @@
//===-- Linux implementation of the mtx_trylock 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/mtx_trylock.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/threads/mutex.h"
#include <threads.h> // For mtx_t definition.
namespace LIBC_NAMESPACE_DECL {
// The implementation currently handles only plain mutexes.
LLVM_LIBC_FUNCTION(int, mtx_trylock, (mtx_t * mutex)) {
auto *m = reinterpret_cast<Mutex *>(mutex);
auto err = m->try_lock();
if (err == MutexError::BUSY)
return thrd_busy;
return err == MutexError::NONE ? thrd_success : thrd_error;
}
} // namespace LIBC_NAMESPACE_DECL
+21
View File
@@ -0,0 +1,21 @@
//===-- Internal header for mtx_trylock -------------------------*- 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_MTX_TRYLOCK_H
#define LLVM_LIBC_SRC_THREADS_MTX_TRYLOCK_H
#include "src/__support/macros/config.h"
#include <threads.h>
namespace LIBC_NAMESPACE_DECL {
int mtx_trylock(mtx_t *mutex);
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_THREADS_MTX_TRYLOCK_H
@@ -12,6 +12,7 @@ add_integration_test(
libc.src.threads.mtx_destroy
libc.src.threads.mtx_init
libc.src.threads.mtx_lock
libc.src.threads.mtx_trylock
libc.src.threads.mtx_unlock
libc.src.threads.thrd_create
libc.src.threads.thrd_join
@@ -104,12 +105,14 @@ add_integration_test(
cnd_test.cpp
DEPENDS
libc.include.threads
libc.include.time
libc.src.__support.CPP.atomic
libc.src.threads.cnd_init
libc.src.threads.cnd_broadcast
libc.src.threads.cnd_signal
libc.src.threads.cnd_destroy
libc.src.threads.cnd_wait
libc.src.threads.cnd_timedwait
libc.src.threads.mtx_destroy
libc.src.threads.mtx_init
libc.src.threads.mtx_lock
@@ -117,4 +120,5 @@ add_integration_test(
libc.src.threads.thrd_create
libc.src.threads.thrd_join
libc.src.threads.linux.threads_utils
libc.src.time.timespec_get
)
+75 -4
View File
@@ -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,10 +19,20 @@
#include "src/threads/mtx_unlock.h"
#include "src/threads/thrd_create.h"
#include "src/threads/thrd_join.h"
#include "src/time/timespec_get.h"
#include "test/IntegrationTest/test.h"
#include <threads.h>
#include <time.h> // for TIME_UTC
static void add_ns(struct timespec &ts, long ns) {
ts.tv_nsec += ns;
if (ts.tv_nsec >= 1'000'000'000) {
++ts.tv_sec;
ts.tv_nsec -= 1'000'000'000;
}
}
namespace wait_notify_broadcast_test {
@@ -100,6 +111,11 @@ namespace single_waiter_test {
mtx_t waiter_mtx, main_thread_mtx;
cnd_t waiter_cnd, main_thread_cnd;
enum class WaitMode {
Default,
Timed,
};
int waiter_thread_func([[maybe_unused]] void *unused) {
LIBC_NAMESPACE::mtx_lock(&waiter_mtx);
@@ -113,7 +129,7 @@ int waiter_thread_func([[maybe_unused]] void *unused) {
return 0x600D;
}
void single_waiter_test() {
void single_waiter_test(WaitMode wait_mode) {
ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&waiter_mtx, mtx_plain),
int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&main_thread_mtx, mtx_plain),
@@ -126,8 +142,18 @@ void single_waiter_test() {
thrd_t waiter_thread;
LIBC_NAMESPACE::thrd_create(&waiter_thread, waiter_thread_func, nullptr);
ASSERT_EQ(LIBC_NAMESPACE::cnd_wait(&main_thread_cnd, &main_thread_mtx),
int(thrd_success));
if (wait_mode == WaitMode::Default) {
ASSERT_EQ(LIBC_NAMESPACE::cnd_wait(&main_thread_cnd, &main_thread_mtx),
int(thrd_success));
} else {
ASSERT_EQ(wait_mode, WaitMode::Timed);
struct timespec ts;
ASSERT_EQ(LIBC_NAMESPACE::timespec_get(&ts, TIME_UTC), TIME_UTC);
add_ns(ts, 50'000);
int result =
LIBC_NAMESPACE::cnd_timedwait(&main_thread_cnd, &main_thread_mtx, &ts);
ASSERT_TRUE(result == int(thrd_success) || result == int(thrd_timedout));
}
ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&main_thread_mtx), int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&waiter_mtx), int(thrd_success));
@@ -146,8 +172,53 @@ void single_waiter_test() {
} // namespace single_waiter_test
namespace timed_wait_test {
void timeout_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));
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 0;
ASSERT_EQ(LIBC_NAMESPACE::cnd_timedwait(&cnd, &mtx, &ts), int(thrd_timedout));
ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&mtx), int(thrd_success));
LIBC_NAMESPACE::cnd_destroy(&cnd);
LIBC_NAMESPACE::mtx_destroy(&mtx);
}
void future_timeout_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));
struct timespec ts;
ASSERT_EQ(LIBC_NAMESPACE::timespec_get(&ts, TIME_UTC), TIME_UTC);
add_ns(ts, 50'000);
ASSERT_EQ(LIBC_NAMESPACE::cnd_timedwait(&cnd, &mtx, &ts), int(thrd_timedout));
ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&mtx), int(thrd_success));
LIBC_NAMESPACE::cnd_destroy(&cnd);
LIBC_NAMESPACE::mtx_destroy(&mtx);
}
} // namespace timed_wait_test
TEST_MAIN() {
wait_notify_broadcast_test::wait_notify_broadcast_test();
single_waiter_test::single_waiter_test();
single_waiter_test::single_waiter_test(single_waiter_test::WaitMode::Default);
single_waiter_test::single_waiter_test(single_waiter_test::WaitMode::Timed);
timed_wait_test::timeout_test();
timed_wait_test::future_timeout_test();
return 0;
}
@@ -10,6 +10,7 @@
#include "src/threads/mtx_destroy.h"
#include "src/threads/mtx_init.h"
#include "src/threads/mtx_lock.h"
#include "src/threads/mtx_trylock.h"
#include "src/threads/mtx_unlock.h"
#include "src/threads/thrd_create.h"
#include "src/threads/thrd_join.h"
@@ -230,10 +231,36 @@ void multiple_waiters() {
LIBC_NAMESPACE::mtx_destroy(&counter_lock);
}
void trylock_test() {
mtx_t plain_mutex;
ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&plain_mutex, mtx_plain),
int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_trylock(&plain_mutex), int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_trylock(&plain_mutex), int(thrd_busy));
ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&plain_mutex), int(thrd_success));
LIBC_NAMESPACE::mtx_destroy(&plain_mutex);
mtx_t recursive_mutex;
ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&recursive_mutex, mtx_recursive),
int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_trylock(&recursive_mutex), int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_trylock(&recursive_mutex), int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&recursive_mutex), int(thrd_success));
ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&recursive_mutex), int(thrd_success));
LIBC_NAMESPACE::mtx_destroy(&recursive_mutex);
}
TEST_MAIN() {
relay_counter();
wait_and_step();
recursive_mutex_test();
multiple_waiters();
trylock_test();
return 0;
}