blob: 38f12eaa226390cf79cf08a65c556cf55c5a468b [file] [log] [blame]
//===-- Unittests for condition variable broadcast fucntionality ----------===//
//
// 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 <stdatomic.h>
#include "include/threads.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_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 "utils/UnitTest/Test.h"
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 int THRD_COUNT = 10000;
static atomic_uint broadcast_count = 0;
static cnd_t broadcast_cnd, threads_ready_cnd;
static mtx_t broadcast_mtx, threads_ready_mtx;
int broadcast_thread_func(void *) {
__llvm_libc::mtx_lock(&broadcast_mtx);
int oldval = atomic_fetch_add(&broadcast_count, 1);
if (oldval == THRD_COUNT - 1) {
__llvm_libc::mtx_lock(&threads_ready_mtx);
__llvm_libc::cnd_signal(&threads_ready_cnd);
__llvm_libc::mtx_unlock(&threads_ready_mtx);
}
__llvm_libc::cnd_wait(&broadcast_cnd, &broadcast_mtx);
__llvm_libc::mtx_unlock(&broadcast_mtx);
atomic_fetch_sub(&broadcast_count, 1);
return 0;
}
TEST(LlvmLibcCndVarTest, WaitNotifyBroadcastTest) {
__llvm_libc::cnd_init(&broadcast_cnd);
__llvm_libc::cnd_init(&threads_ready_cnd);
__llvm_libc::mtx_init(&broadcast_mtx, mtx_plain);
__llvm_libc::mtx_init(&threads_ready_mtx, mtx_plain);
__llvm_libc::mtx_lock(&threads_ready_mtx);
thrd_t threads[THRD_COUNT];
for (unsigned int i = 0; i < THRD_COUNT; ++i)
__llvm_libc::thrd_create(&threads[i], broadcast_thread_func, nullptr);
__llvm_libc::cnd_wait(&threads_ready_cnd, &threads_ready_mtx);
__llvm_libc::mtx_unlock(&threads_ready_mtx);
__llvm_libc::mtx_lock(&broadcast_mtx);
ASSERT_EQ(int(broadcast_count), THRD_COUNT);
__llvm_libc::cnd_broadcast(&broadcast_cnd);
__llvm_libc::mtx_unlock(&broadcast_mtx);
for (unsigned int i = 0; i < THRD_COUNT; ++i) {
int retval = 0xBAD;
__llvm_libc::thrd_join(&threads[i], &retval);
ASSERT_EQ(retval, 0);
}
ASSERT_EQ(int(broadcast_count), 0);
__llvm_libc::cnd_destroy(&broadcast_cnd);
__llvm_libc::cnd_destroy(&threads_ready_cnd);
__llvm_libc::mtx_destroy(&broadcast_mtx);
__llvm_libc::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;
int waiter_thread_func(void *unused) {
__llvm_libc::mtx_lock(&waiter_mtx);
__llvm_libc::mtx_lock(&main_thread_mtx);
__llvm_libc::cnd_signal(&main_thread_cnd);
__llvm_libc::mtx_unlock(&main_thread_mtx);
__llvm_libc::cnd_wait(&waiter_cnd, &waiter_mtx);
__llvm_libc::mtx_unlock(&waiter_mtx);
return 0x600D;
}
TEST(LlvmLibcCndVarTest, SingleWaiterTest) {
ASSERT_EQ(__llvm_libc::mtx_init(&waiter_mtx, mtx_plain), int(thrd_success));
ASSERT_EQ(__llvm_libc::mtx_init(&main_thread_mtx, mtx_plain),
int(thrd_success));
ASSERT_EQ(__llvm_libc::cnd_init(&waiter_cnd), int(thrd_success));
ASSERT_EQ(__llvm_libc::cnd_init(&main_thread_cnd), int(thrd_success));
ASSERT_EQ(__llvm_libc::mtx_lock(&main_thread_mtx), int(thrd_success));
thrd_t waiter_thread;
__llvm_libc::thrd_create(&waiter_thread, waiter_thread_func, nullptr);
ASSERT_EQ(__llvm_libc::cnd_wait(&main_thread_cnd, &main_thread_mtx),
int(thrd_success));
ASSERT_EQ(__llvm_libc::mtx_unlock(&main_thread_mtx), int(thrd_success));
ASSERT_EQ(__llvm_libc::mtx_lock(&waiter_mtx), int(thrd_success));
ASSERT_EQ(__llvm_libc::cnd_signal(&waiter_cnd), int(thrd_success));
ASSERT_EQ(__llvm_libc::mtx_unlock(&waiter_mtx), int(thrd_success));
int retval;
__llvm_libc::thrd_join(&waiter_thread, &retval);
ASSERT_EQ(retval, 0x600D);
__llvm_libc::mtx_destroy(&waiter_mtx);
__llvm_libc::mtx_destroy(&main_thread_mtx);
__llvm_libc::cnd_destroy(&waiter_cnd);
__llvm_libc::cnd_destroy(&main_thread_cnd);
}
} // namespace single_waiter_test