| //===-- Tests for standard 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 "src/__support/CPP/atomic.h" | |
| #include "src/threads/cnd_broadcast.h" | |
| #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" | |
| #include "src/threads/mtx_lock.h" | |
| #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 { | |
| // The test in this namespace tests all condition variable operations. The | |
| // main thread spawns THRD_COUNT threads, each of which wait on a condition | |
| // variable |broadcast_cnd|. After spawing the threads, it waits on another | |
| // condition variable |threads_ready_cnd| which will be signalled by the last | |
| // thread before it starts waiting on |broadcast_cnd|. On signalled by the | |
| // last thread, the main thread then wakes up to broadcast to all waiting | |
| // threads to wake up. Each of the THRD_COUNT child threads increment | |
| // |broadcast_count| by 1 before they start waiting on |broadcast_cnd|, and | |
| // decrement it by 1 after getting signalled on |broadcast_cnd|. | |
| constexpr unsigned int THRD_COUNT = 1000; | |
| static LIBC_NAMESPACE::cpp::Atomic<unsigned int> broadcast_count(0); | |
| static cnd_t broadcast_cnd, threads_ready_cnd; | |
| static mtx_t broadcast_mtx, threads_ready_mtx; | |
| int broadcast_thread_func(void *) { | |
| LIBC_NAMESPACE::mtx_lock(&broadcast_mtx); | |
| unsigned oldval = broadcast_count.fetch_add(1); | |
| if (oldval == THRD_COUNT - 1) { | |
| LIBC_NAMESPACE::mtx_lock(&threads_ready_mtx); | |
| LIBC_NAMESPACE::cnd_signal(&threads_ready_cnd); | |
| LIBC_NAMESPACE::mtx_unlock(&threads_ready_mtx); | |
| } | |
| LIBC_NAMESPACE::cnd_wait(&broadcast_cnd, &broadcast_mtx); | |
| LIBC_NAMESPACE::mtx_unlock(&broadcast_mtx); | |
| broadcast_count.fetch_sub(1); | |
| return 0; | |
| } | |
| void wait_notify_broadcast_test() { | |
| LIBC_NAMESPACE::cnd_init(&broadcast_cnd); | |
| LIBC_NAMESPACE::cnd_init(&threads_ready_cnd); | |
| LIBC_NAMESPACE::mtx_init(&broadcast_mtx, mtx_plain); | |
| LIBC_NAMESPACE::mtx_init(&threads_ready_mtx, mtx_plain); | |
| LIBC_NAMESPACE::mtx_lock(&threads_ready_mtx); | |
| thrd_t threads[THRD_COUNT]; | |
| for (unsigned int i = 0; i < THRD_COUNT; ++i) | |
| LIBC_NAMESPACE::thrd_create(&threads[i], broadcast_thread_func, nullptr); | |
| LIBC_NAMESPACE::cnd_wait(&threads_ready_cnd, &threads_ready_mtx); | |
| LIBC_NAMESPACE::mtx_unlock(&threads_ready_mtx); | |
| LIBC_NAMESPACE::mtx_lock(&broadcast_mtx); | |
| ASSERT_EQ(broadcast_count.val, THRD_COUNT); | |
| LIBC_NAMESPACE::cnd_broadcast(&broadcast_cnd); | |
| LIBC_NAMESPACE::mtx_unlock(&broadcast_mtx); | |
| for (unsigned int i = 0; i < THRD_COUNT; ++i) { | |
| int retval = 0xBAD; | |
| LIBC_NAMESPACE::thrd_join(threads[i], &retval); | |
| ASSERT_EQ(retval, 0); | |
| } | |
| ASSERT_EQ(broadcast_count.val, 0U); | |
| LIBC_NAMESPACE::cnd_destroy(&broadcast_cnd); | |
| LIBC_NAMESPACE::cnd_destroy(&threads_ready_cnd); | |
| LIBC_NAMESPACE::mtx_destroy(&broadcast_mtx); | |
| LIBC_NAMESPACE::mtx_destroy(&threads_ready_mtx); | |
| } | |
| } // namespace wait_notify_broadcast_test | |
| namespace single_waiter_test { | |
| // In this namespace we set up test with two threads, one the main thread | |
| // and the other a waiter thread. They wait on each other using condition | |
| // variables and mutexes before proceeding to completion. | |
| mtx_t waiter_mtx, main_thread_mtx; | |
| cnd_t waiter_cnd, main_thread_cnd; | |
| enum class WaitMode { | |
| Default, | |
| Timed, | |
| }; | |
| int waiter_thread_func(void *arg) { | |
| auto *waiter_unblocked = | |
| static_cast<LIBC_NAMESPACE::cpp::Atomic<bool> *>(arg); | |
| LIBC_NAMESPACE::mtx_lock(&waiter_mtx); | |
| LIBC_NAMESPACE::mtx_lock(&main_thread_mtx); | |
| LIBC_NAMESPACE::cnd_signal(&main_thread_cnd); | |
| LIBC_NAMESPACE::mtx_unlock(&main_thread_mtx); | |
| LIBC_NAMESPACE::cnd_wait(&waiter_cnd, &waiter_mtx); | |
| waiter_unblocked->store(true); | |
| LIBC_NAMESPACE::mtx_unlock(&waiter_mtx); | |
| return 0x600D; | |
| } | |
| 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), | |
| int(thrd_success)); | |
| ASSERT_EQ(LIBC_NAMESPACE::cnd_init(&waiter_cnd), int(thrd_success)); | |
| ASSERT_EQ(LIBC_NAMESPACE::cnd_init(&main_thread_cnd), int(thrd_success)); | |
| ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&main_thread_mtx), int(thrd_success)); | |
| thrd_t waiter_thread; | |
| LIBC_NAMESPACE::cpp::Atomic<bool> waiter_unblocked(false); | |
| LIBC_NAMESPACE::thrd_create(&waiter_thread, waiter_thread_func, | |
| &waiter_unblocked); | |
| 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)); | |
| while (!waiter_unblocked.load()) { | |
| ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&waiter_mtx), int(thrd_success)); | |
| ASSERT_EQ(LIBC_NAMESPACE::cnd_signal(&waiter_cnd), int(thrd_success)); | |
| ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&waiter_mtx), int(thrd_success)); | |
| } | |
| int retval; | |
| LIBC_NAMESPACE::thrd_join(waiter_thread, &retval); | |
| ASSERT_EQ(retval, 0x600D); | |
| LIBC_NAMESPACE::mtx_destroy(&waiter_mtx); | |
| LIBC_NAMESPACE::mtx_destroy(&main_thread_mtx); | |
| LIBC_NAMESPACE::cnd_destroy(&waiter_cnd); | |
| LIBC_NAMESPACE::cnd_destroy(&main_thread_cnd); | |
| } | |
| } // 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::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; | |
| } |