From d8bc60e0fb9c66238e295c0f8d2018099a08afe9 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Thu, 4 May 2023 10:02:22 +0200 Subject: [PATCH] Add more pthread tests. --- tests/Makefile | 6 +- tests/pthread/barrier.c | 122 ++++++++++++++++++++++++++++++++ tests/pthread/mutex_recursive.c | 108 ++++++++++++++++++++++++++++ tests/pthread/rwlock_randtest.c | 64 +++++++++++++++++ tests/pthread/rwlock_trylock.c | 43 +++++++++++ tests/test_helpers.h | 2 + 6 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 tests/pthread/barrier.c create mode 100644 tests/pthread/mutex_recursive.c create mode 100644 tests/pthread/rwlock_randtest.c create mode 100644 tests/pthread/rwlock_trylock.c diff --git a/tests/Makefile b/tests/Makefile index 1e6fae921d..748b8356c5 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -155,7 +155,11 @@ NAMES=\ pthread/cleanup \ pthread/extjoin \ pthread/once \ - pthread/customstack + pthread/customstack \ + pthread/barrier \ + pthread/rwlock_trylock \ + pthread/rwlock_randtest \ + pthread/mutex_recursive # resource/getrusage # time/times diff --git a/tests/pthread/barrier.c b/tests/pthread/barrier.c new file mode 100644 index 0000000000..ace032f871 --- /dev/null +++ b/tests/pthread/barrier.c @@ -0,0 +1,122 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include +#include + +// Same test logic as test_barrier in rustc/library/std/sync/barrier/tests.rs + +#define N 10 + +struct arg { + pthread_barrier_t *barrier; + volatile _Atomic(unsigned) *count; + bool is_leader; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + + int status = pthread_barrier_wait(arg->barrier); + + arg->is_leader = status == PTHREAD_BARRIER_SERIAL_THREAD; + ERROR_IF(pthread_barrier_wait, status, != 0); + + // We can now modify the counter. + atomic_fetch_add_explicit(arg->count, 1, memory_order_relaxed); + + return NULL; +} + +int main(void) { + int status; + + pthread_barrier_t barrier; + + pthread_barrierattr_t attr; + status = pthread_barrierattr_init(&attr); + ERROR_IF(pthread_barrierattr_init, status, != 0); + + int pshared; + + // + // BARRIER ATTR + // + + status = pthread_barrierattr_getpshared(&attr, &pshared); + + // PTHREAD_PROCESS_PRIVATE is default according to POSIX. + assert(pshared == PTHREAD_PROCESS_PRIVATE); + + ERROR_IF(pthread_barrierattr_getpshared, status, != 0); + + status = pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + ERROR_IF(pthread_barrierattr_setpshared, status, != 0); + + status = pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_SHARED); + ERROR_IF(pthread_barrierattr_getpshared, status, != 0); + + status = pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + ERROR_IF(pthread_barrierattr_setpshared, status, != 0); + + status = pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + ERROR_IF(pthread_barrierattr_getpshared, status, != 0); + + // + // BARRIER + // + + status = pthread_barrier_init(&barrier, &attr, N); + ERROR_IF(pthread_barrier_init, status, != 0); + + status = pthread_barrierattr_destroy(&attr); + ERROR_IF(pthread_barrierattr_destroy, status, != 0); + + // + // CREATE THREAD + // + + pthread_t threads[N - 1]; + struct arg args[N - 1]; + _Atomic(unsigned) count = false; + + for (size_t i = 0; i < N - 1; i++) { + args[i] = (struct arg){ .count = &count, .barrier = &barrier, .is_leader = false }; + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + // Must not be set before having waited for the barrier. This is part of the + // test. Normally spawned threads run before the scheduler returns to the + // parent thread, so it should at least partially verify that barriers work. + unsigned value = atomic_load_explicit(&count, memory_order_relaxed); + + UNEXP_IF(count_before_barrier_wait, value, > 0); + + status = pthread_barrier_wait(&barrier); + + bool leader_found = status == PTHREAD_BARRIER_SERIAL_THREAD; + + if (!leader_found) { + ERROR_IF(pthread_barrier_wait, status, != 0); + } + + for (size_t i = 0; i < N - 1; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + + // SAFETY: pthread_create and pthread_join are Acquire-Release + leader_found |= args[i].is_leader; + } + + assert(leader_found); + + status = pthread_barrier_destroy(&barrier); + ERROR_IF(pthread_barrier_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/mutex_recursive.c b/tests/pthread/mutex_recursive.c new file mode 100644 index 0000000000..2e22a30f58 --- /dev/null +++ b/tests/pthread/mutex_recursive.c @@ -0,0 +1,108 @@ +#include +#include +#include + +#include "../test_helpers.h" + +#define N 10 +#define M 10000 + +struct arg { + pthread_mutex_t *mutex; + unsigned *protected; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + int status; + + unsigned depth = 0; + unsigned i = 0; + + while (i < M) { + // Bad random distribution, but should work. + bool lock_again = (depth == 0) || (random_bool() && random_bool()); + + if (lock_again) { + status = pthread_mutex_lock(arg->mutex); + ERROR_IF(pthread_mutex_lock, status, != 0); + + depth += 1; + } else { + status = pthread_mutex_unlock(arg->mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + depth -= 1; + } + if (depth == 0) { + continue; + } + + unsigned value = *arg->protected; + *arg->protected = value + 1; + + i += 1; + } + while (depth > 0) { + status = pthread_mutex_unlock(arg->mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + depth--; + } + + return NULL; +} + +int main(void) { + int status; + pthread_mutex_t mutex; + pthread_mutexattr_t attr; + + status = pthread_mutexattr_init(&attr); + ERROR_IF(pthread_mutexattr_init, status, != 0); + + status = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + ERROR_IF(pthread_mutexattr_settype, status, != 0); + + status = pthread_mutex_init(&mutex, &attr); + ERROR_IF(pthread_mutex_init, status, != 0); + + status = pthread_mutexattr_destroy(&attr); + ERROR_IF(pthread_mutexattr_destroy, status, != 0); + + status = pthread_mutex_lock(&mutex); + ERROR_IF(pthread_mutex_lock, status, != 0); + + status = pthread_mutex_trylock(&mutex); + ERROR_IF(pthread_mutex_trylock, status, != 0); + + status = pthread_mutex_unlock(&mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + // Still locked with count = 1. + + pthread_t threads[N]; + struct arg args[N]; + unsigned protected = 0; + + for (size_t i = 0; i < N; i++) { + args[i] = (struct arg){ .mutex = &mutex, .protected = &protected }; + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + protected = 1; + + status = pthread_mutex_unlock(&mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + for (size_t i = 0; i < N; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + } + + status = pthread_mutex_destroy(&mutex); + ERROR_IF(pthread_mutex_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/rwlock_randtest.c b/tests/pthread/rwlock_randtest.c new file mode 100644 index 0000000000..e263efd472 --- /dev/null +++ b/tests/pthread/rwlock_randtest.c @@ -0,0 +1,64 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include + +// Same test logic as frob in rustc/library/std/sync/rwlock/tests.rs + +#define N 10 +//#define M 1000 +#define M 100000 + +struct arg { + pthread_rwlock_t *rwlock; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + int status; + + for (uint64_t i = 0; i < M; i++) { + if (random_bool()) { + status = pthread_rwlock_wrlock(arg->rwlock); + ERROR_IF(pthread_rwlock_wrlock, status, != 0); + } else { + status = pthread_rwlock_rdlock(arg->rwlock); + ERROR_IF(pthread_rwlock_rdlock, status, != 0); + } + status = pthread_rwlock_unlock(arg->rwlock); + ERROR_IF(pthread_rwlock_unlock, status, != 0); + } + + return NULL; +} + +int main(void) { + int status; + + pthread_rwlock_t rwlock; + + status = pthread_rwlock_init(&rwlock, NULL); + ERROR_IF(pthread_rwlock_init, status, != 0); + + pthread_t threads[N]; + struct arg args[N]; + + for (size_t i = 0; i < N; i++) { + args[i] = (struct arg){ .rwlock = &rwlock }; + + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + for (size_t i = 0; i < N; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + } + + status = pthread_rwlock_destroy(&rwlock); + ERROR_IF(pthread_rwlock_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/rwlock_trylock.c b/tests/pthread/rwlock_trylock.c new file mode 100644 index 0000000000..9990a0cdb3 --- /dev/null +++ b/tests/pthread/rwlock_trylock.c @@ -0,0 +1,43 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include + +// Same test logic as test_rwlock_try_write in rustc/library/std/sync/rwlock/tests.rs + +int main(void) { + int status; + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + + pthread_rwlockattr_t attr; + status = pthread_rwlockattr_init(&attr); + ERROR_IF(pthread_rwlockattr_init, status, != 0); + + // Call setpshared twice to check both constants work. + status = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + ERROR_IF(pthread_rwlockattr_setpshared, status, != 0); + status = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + ERROR_IF(pthread_rwlockattr_setpshared, status, != 0); + + status = pthread_rwlock_init(&rwlock, &attr); + ERROR_IF(pthread_rwlock_init, status, != 0); + + status = pthread_rwlockattr_destroy(&attr); + ERROR_IF(pthread_rwlockattr_destroy, status, != 0); + + status = pthread_rwlock_rdlock(&rwlock); + ERROR_IF(pthread_rwlock_rdlock, status, != 0); + + status = pthread_rwlock_trywrlock(&rwlock); + assert(status == EBUSY); + + status = pthread_rwlock_unlock(&rwlock); + ERROR_IF(pthread_rwlock_unlock, status, != 0); + + status = pthread_rwlock_destroy(&rwlock); + ERROR_IF(pthread_rwlock_destroy, status, != 0); + + return 0; +} diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 68705d8673..c03a8c1059 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -88,4 +88,6 @@ _exit(code); \ } while(0) +#define random_bool() (lrand48() % 2 == 0) + #endif /* _TEST_HELPERS */