diff options
author | Schrodinger ZHU Yifan <yifanzhu@rochester.edu> | 2024-06-14 13:34:28 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-14 13:34:28 -0700 |
commit | 41fecca97b77a80926cb1b1a83c8af3c90d354bd (patch) | |
tree | ef38d428265bb92f0f6ee181d5e5f20f8c30fc63 /libc/test | |
parent | cccc43725798de5371eb893f11f3940e6a954a0f (diff) | |
download | llvm-41fecca97b77a80926cb1b1a83c8af3c90d354bd.zip llvm-41fecca97b77a80926cb1b1a83c8af3c90d354bd.tar.gz llvm-41fecca97b77a80926cb1b1a83c8af3c90d354bd.tar.bz2 |
[libc] add rwlock (#94156)
Diffstat (limited to 'libc/test')
-rw-r--r-- | libc/test/integration/src/pthread/CMakeLists.txt | 39 | ||||
-rw-r--r-- | libc/test/integration/src/pthread/pthread_rwlock_test.cpp | 478 |
2 files changed, 517 insertions, 0 deletions
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt index a10dc256..16a1ff6 100644 --- a/libc/test/integration/src/pthread/CMakeLists.txt +++ b/libc/test/integration/src/pthread/CMakeLists.txt @@ -18,6 +18,45 @@ add_integration_test( ) add_integration_test( + pthread_rwlock_test + SUITE + libc-pthread-integration-tests + SRCS + pthread_rwlock_test.cpp + DEPENDS + libc.include.pthread + libc.include.time + libc.include.errno + libc.src.pthread.pthread_rwlock_destroy + libc.src.pthread.pthread_rwlock_init + libc.src.pthread.pthread_rwlock_rdlock + libc.src.pthread.pthread_rwlock_tryrdlock + libc.src.pthread.pthread_rwlock_timedrdlock + libc.src.pthread.pthread_rwlock_wrlock + libc.src.pthread.pthread_rwlock_trywrlock + libc.src.pthread.pthread_rwlock_timedwrlock + libc.src.pthread.pthread_rwlock_unlock + libc.src.pthread.pthread_create + libc.src.pthread.pthread_join + libc.src.pthread.pthread_rwlockattr_init + libc.src.pthread.pthread_rwlockattr_destroy + libc.src.pthread.pthread_rwlockattr_setpshared + libc.src.pthread.pthread_rwlockattr_setkind_np + libc.src.__support.threads.linux.raw_mutex + libc.src.stdio.printf + libc.src.stdlib.getenv + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.time.clock_gettime + libc.src.sys.random.getrandom + libc.src.unistd.fork + libc.src.sys.wait.waitpid + libc.src.stdlib.exit + libc.src.__support.CPP.atomic + libc.src.__support.threads.sleep +) + +add_integration_test( pthread_test SUITE libc-pthread-integration-tests diff --git a/libc/test/integration/src/pthread/pthread_rwlock_test.cpp b/libc/test/integration/src/pthread/pthread_rwlock_test.cpp new file mode 100644 index 0000000..215db1fc --- /dev/null +++ b/libc/test/integration/src/pthread/pthread_rwlock_test.cpp @@ -0,0 +1,478 @@ +//===-- Tests for pthread_rwlock ------------------------------------------===// +// +// 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/__support/OSUtil/syscall.h" +#include "src/__support/threads/linux/raw_mutex.h" +#include "src/__support/threads/linux/rwlock.h" +#include "src/__support/threads/sleep.h" +#include "src/pthread/pthread_create.h" +#include "src/pthread/pthread_join.h" +#include "src/pthread/pthread_rwlock_destroy.h" +#include "src/pthread/pthread_rwlock_init.h" +#include "src/pthread/pthread_rwlock_rdlock.h" +#include "src/pthread/pthread_rwlock_timedrdlock.h" +#include "src/pthread/pthread_rwlock_timedwrlock.h" +#include "src/pthread/pthread_rwlock_tryrdlock.h" +#include "src/pthread/pthread_rwlock_trywrlock.h" +#include "src/pthread/pthread_rwlock_unlock.h" +#include "src/pthread/pthread_rwlock_wrlock.h" +#include "src/pthread/pthread_rwlockattr_destroy.h" +#include "src/pthread/pthread_rwlockattr_init.h" +#include "src/pthread/pthread_rwlockattr_setkind_np.h" +#include "src/pthread/pthread_rwlockattr_setpshared.h" +#include "src/stdio/printf.h" +#include "src/stdlib/exit.h" +#include "src/stdlib/getenv.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/munmap.h" +#include "src/sys/random/getrandom.h" +#include "src/sys/wait/waitpid.h" +#include "src/time/clock_gettime.h" +#include "src/unistd/fork.h" +#include "test/IntegrationTest/test.h" +#include <errno.h> +#include <optional> +#include <pthread.h> +#include <time.h> + +namespace LIBC_NAMESPACE::rwlock { +class RwLockTester { +public: + static constexpr int full_reader_state() { + return (~0) & (~RwState::PENDING_MASK) & (~RwState::ACTIVE_WRITER_BIT); + } +}; +} // namespace LIBC_NAMESPACE::rwlock + +static void smoke_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), EDEADLK); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), EDEADLK); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); +} + +static void deadlock_detection_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); + // We only detect RAW, WAW deadlocks. + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), EDEADLK); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); +} + +static void try_lock_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); +} + +static void destroy_before_unlock_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); +} + +static void nullptr_test() { + timespec ts = {}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(nullptr, &ts), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(nullptr, &ts), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(nullptr), EINVAL); +} + +// If you are a user reading this code, please do not do something like this. +// We manually modify the internal state of the rwlock to test high reader +// counts. +static void high_reader_count_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + rwlock.__state = LIBC_NAMESPACE::rwlock::RwLockTester::full_reader_state(); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), EAGAIN); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EAGAIN); + // allocate 4 reader slots. + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + + pthread_t threads[20]; + for (auto &i : threads) + ASSERT_EQ(LIBC_NAMESPACE::pthread_create( + &i, nullptr, + [](void *arg) -> void * { + pthread_rwlock_t *rwlock = + reinterpret_cast<pthread_rwlock_t *>(arg); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(rwlock), + EBUSY); + while (LIBC_NAMESPACE::pthread_rwlock_rdlock(rwlock) == + EAGAIN) + LIBC_NAMESPACE::sleep_briefly(); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(rwlock), 0); + return nullptr; + }, + &rwlock), + 0); + + for (auto &i : threads) + ASSERT_EQ(LIBC_NAMESPACE::pthread_join(i, nullptr), 0); +} + +static void unusual_timespec_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + timespec ts = {0, -1}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL); + ts.tv_nsec = 1'000'000'000; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL); + ts.tv_nsec += 1; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL); + ts.tv_nsec = 0; + ts.tv_sec = -1; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), + ETIMEDOUT); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), + ETIMEDOUT); +} + +static void timedlock_with_deadlock_test() { + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + timespec ts{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); + LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 50'000; + if (ts.tv_nsec >= 1'000'000'000) { + ts.tv_nsec -= 1'000'000'000; + ts.tv_sec += 1; + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), + ETIMEDOUT); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + // notice that ts is already expired, but the following should still succeed. + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); +} + +static void attributed_initialization_test() { + pthread_rwlockattr_t attr{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( + &attr, PTHREAD_RWLOCK_PREFER_READER_NP), + 0); + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( + &attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP), + 0); + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( + &attr, PTHREAD_RWLOCK_PREFER_WRITER_NP), + 0); + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( + &attr, PTHREAD_RWLOCK_PREFER_READER_NP), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared( + &attr, PTHREAD_PROCESS_PRIVATE), + 0); + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared( + &attr, PTHREAD_PROCESS_SHARED), + 0); + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); + } + attr.pref = -1; + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL); + } + attr.pref = PTHREAD_RWLOCK_PREFER_READER_NP; + attr.pshared = -1; + { + pthread_rwlock_t rwlock{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_destroy(&attr), 0); +} + +struct SharedData { + pthread_rwlock_t lock; + int data; + LIBC_NAMESPACE::cpp::Atomic<int> reader_count; + bool writer_flag; + LIBC_NAMESPACE::cpp::Atomic<int> total_writer_count; +}; + +enum class Operation : int { + READ = 0, + WRITE = 1, + TIMED_READ = 2, + TIMED_WRITE = 3, + TRY_READ = 4, + TRY_WRITE = 5, + COUNT = 6 +}; + +LIBC_NAMESPACE::RawMutex *io_mutex; +struct ThreadGuard { + Operation record[64]{}; + size_t cursor = 0; + void push(Operation op) { record[cursor++] = op; } + ~ThreadGuard() { + if (!LIBC_NAMESPACE::getenv("LIBC_PTHREAD_RWLOCK_TEST_VERBOSE")) + return; + pid_t pid = LIBC_NAMESPACE::syscall_impl(SYS_getpid); + pid_t tid = LIBC_NAMESPACE::syscall_impl(SYS_gettid); + io_mutex->lock(LIBC_NAMESPACE::cpp::nullopt, true); + LIBC_NAMESPACE::printf("process %d thread %d: ", pid, tid); + for (size_t i = 0; i < cursor; ++i) + LIBC_NAMESPACE::printf("%d ", static_cast<int>(record[i])); + LIBC_NAMESPACE::printf("\n"); + io_mutex->unlock(true); + } +}; + +static void randomized_thread_operation(SharedData *data, ThreadGuard &guard) { + int buffer; + // We cannot reason about thread order anyway, let's go wild and randomize it + // directly using getrandom. + LIBC_NAMESPACE::getrandom(&buffer, sizeof(buffer), 0); + constexpr int TOTAL = static_cast<int>(Operation::COUNT); + Operation op = static_cast<Operation>(((buffer % TOTAL) + TOTAL) % TOTAL); + guard.push(op); + auto read_ops = [data]() { + ASSERT_FALSE(data->writer_flag); + data->reader_count.fetch_add(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED); + for (int i = 0; i < 10; ++i) + LIBC_NAMESPACE::sleep_briefly(); + data->reader_count.fetch_sub(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED); + }; + auto write_ops = [data]() { + ASSERT_FALSE(data->writer_flag); + data->data += 1; + data->writer_flag = true; + for (int i = 0; i < 10; ++i) + LIBC_NAMESPACE::sleep_briefly(); + ASSERT_EQ(data->reader_count, 0); + data->writer_flag = false; + data->total_writer_count.fetch_add(1); + }; + auto get_ts = []() { + timespec ts{}; + LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 5'000; + if (ts.tv_nsec >= 1'000'000'000) { + ts.tv_nsec -= 1'000'000'000; + ts.tv_sec += 1; + } + return ts; + }; + switch (op) { + case Operation::READ: { + LIBC_NAMESPACE::pthread_rwlock_rdlock(&data->lock); + read_ops(); + LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); + break; + } + case Operation::WRITE: { + LIBC_NAMESPACE::pthread_rwlock_wrlock(&data->lock); + write_ops(); + LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); + break; + } + case Operation::TIMED_READ: { + timespec ts = get_ts(); + if (LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&data->lock, &ts) == 0) { + read_ops(); + LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); + } + break; + } + case Operation::TIMED_WRITE: { + timespec ts = get_ts(); + if (LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&data->lock, &ts) == 0) { + write_ops(); + LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); + } + break; + } + case Operation::TRY_READ: { + if (LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&data->lock) == 0) { + read_ops(); + LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); + } + break; + } + case Operation::TRY_WRITE: { + if (LIBC_NAMESPACE::pthread_rwlock_trywrlock(&data->lock) == 0) { + write_ops(); + LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); + } + break; + } + case Operation::COUNT: + __builtin_trap(); + } +} + +static void +randomized_process_operation(SharedData &data, + LIBC_NAMESPACE::cpp::Atomic<int> &finish_count, + int expected_count) { + pthread_t threads[32]; + for (auto &i : threads) + ASSERT_EQ(LIBC_NAMESPACE::pthread_create( + &i, nullptr, + [](void *arg) -> void * { + ThreadGuard guard{}; + for (int i = 0; i < 64; ++i) + randomized_thread_operation( + reinterpret_cast<SharedData *>(arg), guard); + return nullptr; + }, + &data), + 0); + + for (auto &i : threads) + ASSERT_EQ(LIBC_NAMESPACE::pthread_join(i, nullptr), 0); + + finish_count.fetch_add(1); + while (finish_count.load() != expected_count) + LIBC_NAMESPACE::sleep_briefly(); + + ASSERT_EQ(data.total_writer_count.load(), data.data); + ASSERT_FALSE(data.writer_flag); + ASSERT_EQ(data.reader_count, 0); +} + +static void single_process_test(int preference) { + SharedData data{}; + data.data = 0; + data.reader_count = 0; + data.writer_flag = false; + data.total_writer_count.store(0); + pthread_rwlockattr_t attr{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(&attr, preference), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&data.lock, nullptr), 0); + LIBC_NAMESPACE::cpp::Atomic<int> finish_count{0}; + randomized_process_operation(data, finish_count, 1); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&data.lock), 0); +} + +static void multiple_process_test(int preference) { + struct PShared { + SharedData data; + LIBC_NAMESPACE::cpp::Atomic<int> finish_count; + }; + PShared *shared_data = reinterpret_cast<PShared *>( + LIBC_NAMESPACE::mmap(nullptr, sizeof(PShared), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + shared_data->data.data = 0; + shared_data->data.reader_count = 0; + shared_data->data.writer_flag = false; + shared_data->data.total_writer_count.store(0); + shared_data->finish_count.store(0); + pthread_rwlockattr_t attr{}; + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(&attr, preference), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared( + &attr, PTHREAD_PROCESS_SHARED), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&shared_data->data.lock, &attr), + 0); + int pid = LIBC_NAMESPACE::fork(); + randomized_process_operation(shared_data->data, shared_data->finish_count, 2); + if (pid == 0) + LIBC_NAMESPACE::exit(0); + else { + int status; + LIBC_NAMESPACE::waitpid(pid, &status, 0); + ASSERT_EQ(status, 0); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&shared_data->data.lock), 0); + LIBC_NAMESPACE::munmap(shared_data, sizeof(PShared)); +} + +TEST_MAIN() { + io_mutex = new (LIBC_NAMESPACE::mmap( + nullptr, sizeof(LIBC_NAMESPACE::RawMutex), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, -1, 0)) LIBC_NAMESPACE::RawMutex(); + smoke_test(); + deadlock_detection_test(); + try_lock_test(); + destroy_before_unlock_test(); + nullptr_test(); + high_reader_count_test(); + unusual_timespec_test(); + timedlock_with_deadlock_test(); + attributed_initialization_test(); + single_process_test(PTHREAD_RWLOCK_PREFER_READER_NP); + single_process_test(PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + multiple_process_test(PTHREAD_RWLOCK_PREFER_READER_NP); + multiple_process_test(PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + io_mutex->~RawMutex(); + LIBC_NAMESPACE::munmap(io_mutex, sizeof(LIBC_NAMESPACE::RawMutex)); + return 0; +} |