17 changes: 17 additions & 0 deletions libc/test/src/pthread/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,20 @@ add_libc_unittest(
libc.src.pthread.pthread_mutexattr_setrobust
libc.src.pthread.pthread_mutexattr_settype
)

add_libc_unittest(
phtread_mutex_test
SUITE
libc_pthread_unittests
SRCS
pthread_mutex_test.cpp
DEPENDS
libc.include.pthread
libc.src.errno.errno
libc.src.pthread.pthread_mutex_destroy
libc.src.pthread.pthread_mutex_init
libc.src.pthread.pthread_mutex_lock
libc.src.pthread.pthread_mutex_unlock
libc.src.threads.thrd_create
libc.src.threads.thrd_join
)
188 changes: 188 additions & 0 deletions libc/test/src/pthread/pthread_mutex_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//===-- Unittests for pthread_mutex_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 "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"

// TODO: When pthread_t type is available, use it to spawn threads instead of
// thrd_t.
#include "src/threads/thrd_create.h"
#include "src/threads/thrd_join.h"

#include "utils/UnitTest/Test.h"

#include <pthread.h>

constexpr int START = 0;
constexpr int MAX = 10000;

pthread_mutex_t mutex;
static int shared_int = START;

int counter(void *arg) {
int last_count = START;
while (true) {
__llvm_libc::pthread_mutex_lock(&mutex);
if (shared_int == last_count + 1) {
shared_int++;
last_count = shared_int;
}
__llvm_libc::pthread_mutex_unlock(&mutex);
if (last_count >= MAX)
break;
}
return 0;
}

TEST(LlvmLibcMutexTest, RelayCounter) {
ASSERT_EQ(__llvm_libc::pthread_mutex_init(&mutex, nullptr), 0);

// 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::pthread_mutex_lock(&mutex), 0);
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::pthread_mutex_unlock(&mutex), 0);
if (last_count > MAX)
break;
}

int retval = 123;
__llvm_libc::thrd_join(&thread, &retval);
ASSERT_EQ(retval, 0);

__llvm_libc::pthread_mutex_destroy(&mutex);
}

pthread_mutex_t start_lock, step_lock;
bool started, step;

int stepper(void *arg) {
__llvm_libc::pthread_mutex_lock(&start_lock);
started = true;
__llvm_libc::pthread_mutex_unlock(&start_lock);

__llvm_libc::pthread_mutex_lock(&step_lock);
step = true;
__llvm_libc::pthread_mutex_unlock(&step_lock);
return 0;
}

TEST(LlvmLibcMutexTest, WaitAndStep) {
ASSERT_EQ(__llvm_libc::pthread_mutex_init(&start_lock, nullptr), 0);
ASSERT_EQ(__llvm_libc::pthread_mutex_init(&step_lock, nullptr), 0);

// 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;
started = false;
ASSERT_EQ(__llvm_libc::pthread_mutex_lock(&step_lock), 0);

thrd_t thread;
__llvm_libc::thrd_create(&thread, stepper, nullptr);

while (true) {
// Make sure the thread actually started.
ASSERT_EQ(__llvm_libc::pthread_mutex_lock(&start_lock), 0);
bool s = started;
ASSERT_EQ(__llvm_libc::pthread_mutex_unlock(&start_lock), 0);
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::pthread_mutex_unlock(&step_lock), 0);

while (true) {
ASSERT_EQ(__llvm_libc::pthread_mutex_lock(&step_lock), 0);
bool current_step_value = step;
ASSERT_EQ(__llvm_libc::pthread_mutex_unlock(&step_lock), 0);
if (current_step_value)
break;
}

int retval = 123;
__llvm_libc::thrd_join(&thread, &retval);
ASSERT_EQ(retval, 0);

__llvm_libc::pthread_mutex_destroy(&start_lock);
__llvm_libc::pthread_mutex_destroy(&step_lock);
}

static constexpr int THREAD_COUNT = 10;
static pthread_mutex_t multiple_waiter_lock;
static pthread_mutex_t counter_lock;
static int wait_count = 0;

int waiter_func(void *) {
__llvm_libc::pthread_mutex_lock(&counter_lock);
++wait_count;
__llvm_libc::pthread_mutex_unlock(&counter_lock);

// Block on the waiter lock until the main
// thread unblocks.
__llvm_libc::pthread_mutex_lock(&multiple_waiter_lock);
__llvm_libc::pthread_mutex_unlock(&multiple_waiter_lock);

__llvm_libc::pthread_mutex_lock(&counter_lock);
--wait_count;
__llvm_libc::pthread_mutex_unlock(&counter_lock);

return 0;
}

TEST(LlvmLibcMutexTest, MultipleWaiters) {
__llvm_libc::pthread_mutex_init(&multiple_waiter_lock, nullptr);
__llvm_libc::pthread_mutex_init(&counter_lock, nullptr);

__llvm_libc::pthread_mutex_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::pthread_mutex_lock(&counter_lock);
if (wait_count == THREAD_COUNT) {
__llvm_libc::pthread_mutex_unlock(&counter_lock);
break;
}
__llvm_libc::pthread_mutex_unlock(&counter_lock);
}

__llvm_libc::pthread_mutex_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::pthread_mutex_destroy(&multiple_waiter_lock);
__llvm_libc::pthread_mutex_destroy(&counter_lock);
}