| //===-- Integration test for pthread condition variables ------------------===// |
| // |
| // 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 "hdr/errno_macros.h" |
| #include "hdr/time_macros.h" |
| #include "src/__support/CPP/atomic.h" |
| #include "src/pthread/pthread_cond_broadcast.h" |
| #include "src/pthread/pthread_cond_clockwait.h" |
| #include "src/pthread/pthread_cond_destroy.h" |
| #include "src/pthread/pthread_cond_init.h" |
| #include "src/pthread/pthread_cond_signal.h" |
| #include "src/pthread/pthread_cond_timedwait.h" |
| #include "src/pthread/pthread_cond_wait.h" |
| #include "src/pthread/pthread_condattr_destroy.h" |
| #include "src/pthread/pthread_condattr_init.h" |
| #include "src/pthread/pthread_condattr_setpshared.h" |
| #include "src/pthread/pthread_create.h" |
| #include "src/pthread/pthread_join.h" |
| #include "src/pthread/pthread_mutex_destroy.h" |
| #include "src/pthread/pthread_mutex_init.h" |
| #include "src/pthread/pthread_mutex_lock.h" |
| #include "src/pthread/pthread_mutex_unlock.h" |
| #include "src/pthread/pthread_mutexattr_destroy.h" |
| #include "src/pthread/pthread_mutexattr_init.h" |
| #include "src/pthread/pthread_mutexattr_setpshared.h" |
| #include "src/time/clock_gettime.h" |
| #include "test/IntegrationTest/test.h" |
| |
| #include <pthread.h> |
| |
| namespace { |
| |
| constexpr size_t PRODUCER_COUNT = 4; |
| constexpr size_t CONSUMER_COUNT = 4; |
| constexpr size_t ITEMS_PER_PRODUCER = 80; |
| constexpr int NUM_ITERATIONS = 4; |
| |
| enum class TimeoutMode { |
| Disabled, |
| Default, |
| Realtime, |
| Monotonic, |
| }; |
| |
| template <bool UseBroadcast, bool IsShared, TimeoutMode Timeout> |
| struct TestConfig { |
| LIBC_INLINE_VAR static constexpr bool USE_BROADCAST = UseBroadcast; |
| LIBC_INLINE_VAR static constexpr bool IS_SHARED = IsShared; |
| LIBC_INLINE_VAR static constexpr TimeoutMode TIMEOUT = Timeout; |
| }; |
| |
| struct QueueState { |
| pthread_cond_t cond; |
| pthread_mutex_t mutex; |
| size_t consumed; |
| size_t produced; |
| LIBC_NAMESPACE::cpp::Atomic<size_t> exited_consumers; |
| }; |
| |
| static void add_ns(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; |
| } |
| } |
| |
| template <typename Config> static timespec deadline() { |
| static_assert(Config::TIMEOUT != TimeoutMode::Disabled); |
| |
| constexpr clockid_t CLOCK_ID = Config::TIMEOUT == TimeoutMode::Monotonic |
| ? CLOCK_MONOTONIC |
| : CLOCK_REALTIME; |
| timespec ts{}; |
| ASSERT_EQ(LIBC_NAMESPACE::clock_gettime(CLOCK_ID, &ts), 0); |
| add_ns(ts, 1000); |
| return ts; |
| } |
| |
| template <typename Config> |
| static int cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { |
| if constexpr (Config::TIMEOUT == TimeoutMode::Disabled) { |
| return LIBC_NAMESPACE::pthread_cond_wait(cond, mutex); |
| } else if constexpr (Config::TIMEOUT == TimeoutMode::Default) { |
| timespec ts = deadline<Config>(); |
| return LIBC_NAMESPACE::pthread_cond_timedwait(cond, mutex, &ts); |
| } else { |
| constexpr clockid_t CLOCK_ID = Config::TIMEOUT == TimeoutMode::Monotonic |
| ? CLOCK_MONOTONIC |
| : CLOCK_REALTIME; |
| timespec ts = deadline<Config>(); |
| return LIBC_NAMESPACE::pthread_cond_clockwait(cond, mutex, CLOCK_ID, &ts); |
| } |
| } |
| |
| template <typename Config> static void *consumer(void *arg) { |
| auto *state = static_cast<QueueState *>(arg); |
| constexpr size_t TOTAL_ITEMS = PRODUCER_COUNT * ITEMS_PER_PRODUCER; |
| |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_lock(&state->mutex), 0); |
| while (state->consumed != TOTAL_ITEMS) { |
| int wait_result = cond_wait<Config>(&state->cond, &state->mutex); |
| if constexpr (Config::TIMEOUT == TimeoutMode::Disabled) { |
| ASSERT_EQ(wait_result, 0); |
| } else { |
| ASSERT_TRUE(wait_result == 0 || wait_result == ETIMEDOUT); |
| } |
| |
| if (state->produced == 0) |
| continue; |
| |
| --state->produced; |
| ++state->consumed; |
| } |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_unlock(&state->mutex), 0); |
| state->exited_consumers.fetch_add(1); |
| return nullptr; |
| } |
| |
| template <typename Config> static void *producer(void *arg) { |
| auto *state = static_cast<QueueState *>(arg); |
| for (size_t i = 0; i < ITEMS_PER_PRODUCER; ++i) { |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_lock(&state->mutex), 0); |
| ++state->produced; |
| if constexpr (Config::USE_BROADCAST) { |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_broadcast(&state->cond), 0); |
| } else { |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_signal(&state->cond), 0); |
| } |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_unlock(&state->mutex), 0); |
| } |
| return nullptr; |
| } |
| |
| template <typename Config> static void init_state(QueueState &state) { |
| state.consumed = 0; |
| state.produced = 0; |
| state.exited_consumers = 0; |
| |
| pthread_condattr_t cond_attr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_condattr_init(&cond_attr), 0); |
| pthread_mutexattr_t mutex_attr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutexattr_init(&mutex_attr), 0); |
| |
| if constexpr (Config::IS_SHARED) { |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_condattr_setpshared( |
| &cond_attr, PTHREAD_PROCESS_SHARED), |
| 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutexattr_setpshared( |
| &mutex_attr, PTHREAD_PROCESS_SHARED), |
| 0); |
| } |
| |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_init(&state.cond, &cond_attr), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_init(&state.mutex, &mutex_attr), 0); |
| |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_condattr_destroy(&cond_attr), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutexattr_destroy(&mutex_attr), 0); |
| } |
| |
| static void notify(QueueState &state, bool use_broadcast) { |
| if (use_broadcast) { |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_broadcast(&state.cond), 0); |
| } else { |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_signal(&state.cond), 0); |
| } |
| } |
| |
| template <typename Config> static void stress_test() { |
| for (int iter = 0; iter < NUM_ITERATIONS; ++iter) { |
| QueueState state{}; |
| init_state<Config>(state); |
| |
| pthread_t producer_threads[PRODUCER_COUNT]; |
| pthread_t consumer_threads[CONSUMER_COUNT]; |
| |
| for (size_t i = 0; i < CONSUMER_COUNT; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&consumer_threads[i], nullptr, |
| consumer<Config>, &state), |
| 0); |
| |
| for (size_t i = 0; i < PRODUCER_COUNT; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&producer_threads[i], nullptr, |
| producer<Config>, &state), |
| 0); |
| |
| for (size_t i = 0; i < PRODUCER_COUNT; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(producer_threads[i], nullptr), 0); |
| |
| while (state.exited_consumers != CONSUMER_COUNT) |
| notify(state, Config::USE_BROADCAST); |
| |
| for (size_t i = 0; i < CONSUMER_COUNT; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(consumer_threads[i], nullptr), 0); |
| |
| ASSERT_EQ(state.consumed, PRODUCER_COUNT * ITEMS_PER_PRODUCER); |
| ASSERT_EQ(state.produced, size_t(0)); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_destroy(&state.cond), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_destroy(&state.mutex), 0); |
| } |
| } |
| |
| template <bool UseBroadcast, bool IsShared> static void run_timeout_modes() { |
| stress_test<TestConfig<UseBroadcast, IsShared, TimeoutMode::Disabled>>(); |
| stress_test<TestConfig<UseBroadcast, IsShared, TimeoutMode::Default>>(); |
| stress_test<TestConfig<UseBroadcast, IsShared, TimeoutMode::Realtime>>(); |
| stress_test<TestConfig<UseBroadcast, IsShared, TimeoutMode::Monotonic>>(); |
| } |
| |
| template <bool UseBroadcast> static void run_shared_modes() { |
| run_timeout_modes<UseBroadcast, false>(); |
| run_timeout_modes<UseBroadcast, true>(); |
| } |
| |
| void clockwait_returns_einval_for_invalid_clockid() { |
| pthread_mutex_t mutex; |
| pthread_cond_t cond; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_init(&mutex, nullptr), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_init(&cond, nullptr), 0); |
| |
| timespec ts{}; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_clockwait(&cond, &mutex, -1, &ts), |
| EINVAL); |
| |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_destroy(&mutex), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_destroy(&cond), 0); |
| } |
| |
| void initializer_act_the_same_as_null_attr() { |
| constexpr size_t EFFECTIVE_BYTES = sizeof(pthread_cond_t) - 2; |
| union { |
| pthread_cond_t cond = PTHREAD_COND_INITIALIZER; |
| char cond_bytes[EFFECTIVE_BYTES]; |
| }; |
| union { |
| pthread_cond_t cond_from_init; |
| char cond_from_init_bytes[EFFECTIVE_BYTES]; |
| }; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_cond_init(&cond_from_init, nullptr), 0); |
| // Read as bytes is a defined behavior for trivial types. |
| for (size_t i = 0; i < EFFECTIVE_BYTES; ++i) |
| ASSERT_EQ(cond_bytes[i], cond_from_init_bytes[i]); |
| } |
| |
| } // namespace |
| |
| TEST_MAIN() { |
| run_shared_modes<false>(); |
| run_shared_modes<true>(); |
| initializer_act_the_same_as_null_attr(); |
| clockwait_returns_einval_for_invalid_clockid(); |
| return 0; |
| } |