| //===-- Unittests for mtx_t -----------------------------------------------===// |
| // |
| // 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 "include/threads.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" |
| |
| constexpr int START = 0; |
| constexpr int MAX = 10000; |
| |
| mtx_t mutex; |
| static int shared_int = START; |
| |
| int counter(void *arg) { |
| int last_count = START; |
| while (true) { |
| __llvm_libc::mtx_lock(&mutex); |
| if (shared_int == last_count + 1) { |
| shared_int++; |
| last_count = shared_int; |
| } |
| __llvm_libc::mtx_unlock(&mutex); |
| if (last_count >= MAX) |
| break; |
| } |
| return 0; |
| } |
| |
| TEST(LlvmLibcMutexTest, RelayCounter) { |
| ASSERT_EQ(__llvm_libc::mtx_init(&mutex, mtx_plain), |
| static_cast<int>(thrd_success)); |
| |
| // The idea of this test is that two competing threads will update |
| // a counter only if the other thread has updated it. |
| thrd_t thread; |
| __llvm_libc::thrd_create(&thread, counter, nullptr); |
| |
| int last_count = START; |
| while (true) { |
| ASSERT_EQ(__llvm_libc::mtx_lock(&mutex), static_cast<int>(thrd_success)); |
| if (shared_int == START) { |
| ++shared_int; |
| last_count = shared_int; |
| } else if (shared_int != last_count) { |
| ASSERT_EQ(shared_int, last_count + 1); |
| ++shared_int; |
| last_count = shared_int; |
| } |
| ASSERT_EQ(__llvm_libc::mtx_unlock(&mutex), static_cast<int>(thrd_success)); |
| if (last_count > MAX) |
| break; |
| } |
| |
| int retval = 123; |
| __llvm_libc::thrd_join(&thread, &retval); |
| ASSERT_EQ(retval, 0); |
| |
| __llvm_libc::mtx_destroy(&mutex); |
| } |
| |
| mtx_t start_lock, step_lock; |
| bool start, step; |
| |
| int stepper(void *arg) { |
| __llvm_libc::mtx_lock(&start_lock); |
| start = true; |
| __llvm_libc::mtx_unlock(&start_lock); |
| |
| __llvm_libc::mtx_lock(&step_lock); |
| step = true; |
| __llvm_libc::mtx_unlock(&step_lock); |
| return 0; |
| } |
| |
| TEST(LlvmLibcMutexTest, WaitAndStep) { |
| ASSERT_EQ(__llvm_libc::mtx_init(&start_lock, mtx_plain), |
| static_cast<int>(thrd_success)); |
| ASSERT_EQ(__llvm_libc::mtx_init(&step_lock, mtx_plain), |
| static_cast<int>(thrd_success)); |
| |
| // In this test, we start a new thread but block it before it can make a |
| // step. Once we ensure that the thread is blocked, we unblock it. |
| // After unblocking, we then verify that the thread was indeed unblocked. |
| step = false; |
| start = false; |
| ASSERT_EQ(__llvm_libc::mtx_lock(&step_lock), static_cast<int>(thrd_success)); |
| |
| thrd_t thread; |
| __llvm_libc::thrd_create(&thread, stepper, nullptr); |
| |
| while (true) { |
| // Make sure the thread actually started. |
| ASSERT_EQ(__llvm_libc::mtx_lock(&start_lock), |
| static_cast<int>(thrd_success)); |
| bool s = start; |
| ASSERT_EQ(__llvm_libc::mtx_unlock(&start_lock), |
| static_cast<int>(thrd_success)); |
| if (s) |
| break; |
| } |
| |
| // Since |step_lock| is still locked, |step| should be false. |
| ASSERT_FALSE(step); |
| |
| // Unlock the step lock and wait until the step is made. |
| ASSERT_EQ(__llvm_libc::mtx_unlock(&step_lock), |
| static_cast<int>(thrd_success)); |
| |
| while (true) { |
| ASSERT_EQ(__llvm_libc::mtx_lock(&step_lock), |
| static_cast<int>(thrd_success)); |
| bool current_step_value = step; |
| ASSERT_EQ(__llvm_libc::mtx_unlock(&step_lock), |
| static_cast<int>(thrd_success)); |
| if (current_step_value) |
| break; |
| } |
| |
| int retval = 123; |
| __llvm_libc::thrd_join(&thread, &retval); |
| ASSERT_EQ(retval, 0); |
| |
| __llvm_libc::mtx_destroy(&start_lock); |
| __llvm_libc::mtx_destroy(&step_lock); |
| } |
| |
| static constexpr int THREAD_COUNT = 10; |
| static mtx_t multiple_waiter_lock; |
| static mtx_t counter_lock; |
| static int wait_count = 0; |
| |
| int waiter_func(void *) { |
| __llvm_libc::mtx_lock(&counter_lock); |
| ++wait_count; |
| __llvm_libc::mtx_unlock(&counter_lock); |
| |
| // Block on the waiter lock until the main |
| // thread unblocks. |
| __llvm_libc::mtx_lock(&multiple_waiter_lock); |
| __llvm_libc::mtx_unlock(&multiple_waiter_lock); |
| |
| __llvm_libc::mtx_lock(&counter_lock); |
| --wait_count; |
| __llvm_libc::mtx_unlock(&counter_lock); |
| |
| return 0; |
| } |
| |
| TEST(LlvmLibcMutexTest, MultipleWaiters) { |
| __llvm_libc::mtx_init(&multiple_waiter_lock, mtx_plain); |
| __llvm_libc::mtx_init(&counter_lock, mtx_plain); |
| |
| __llvm_libc::mtx_lock(&multiple_waiter_lock); |
| thrd_t waiters[THREAD_COUNT]; |
| for (int i = 0; i < THREAD_COUNT; ++i) { |
| __llvm_libc::thrd_create(waiters + i, waiter_func, nullptr); |
| } |
| |
| // Spin until the counter is incremented to the desired |
| // value. |
| while (true) { |
| __llvm_libc::mtx_lock(&counter_lock); |
| if (wait_count == THREAD_COUNT) { |
| __llvm_libc::mtx_unlock(&counter_lock); |
| break; |
| } |
| __llvm_libc::mtx_unlock(&counter_lock); |
| } |
| |
| __llvm_libc::mtx_unlock(&multiple_waiter_lock); |
| |
| int retval; |
| for (int i = 0; i < THREAD_COUNT; ++i) { |
| __llvm_libc::thrd_join(waiters + i, &retval); |
| } |
| |
| ASSERT_EQ(wait_count, 0); |
| |
| __llvm_libc::mtx_destroy(&multiple_waiter_lock); |
| __llvm_libc::mtx_destroy(&counter_lock); |
| } |