| //===-- 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 "hdr/errno_macros.h" |
| #include "hdr/time_macros.h" |
| #include "src/__support/CPP/atomic.h" |
| #include "src/__support/CPP/new.h" |
| #include "src/__support/OSUtil/syscall.h" |
| #include "src/__support/macros/config.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_clockrdlock.h" |
| #include "src/pthread/pthread_rwlock_clockwrlock.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 <pthread.h> |
| |
| namespace LIBC_NAMESPACE_DECL { |
| namespace rwlock { |
| class RwLockTester { |
| public: |
| static constexpr int full_reader_state() { |
| return (~0) & (~RwState::PENDING_MASK) & (~RwState::ACTIVE_WRITER_BIT); |
| } |
| }; |
| } // namespace rwlock |
| } // namespace LIBC_NAMESPACE_DECL |
| |
| 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_clockrdlock(nullptr, CLOCK_MONOTONIC, &ts), |
| EINVAL); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockwrlock(nullptr, CLOCK_MONOTONIC, &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); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
| EINVAL); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &ts), |
| EINVAL); |
| ts.tv_nsec = 1'000'000'000; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
| EINVAL); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &ts), |
| EINVAL); |
| ts.tv_nsec += 1; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
| EINVAL); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &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); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
| ETIMEDOUT); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &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_clockwrlock(&rwlock, CLOCK_REALTIME, &ts), |
| ETIMEDOUT); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_REALTIME, &ts), |
| 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); |
| // 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, |
| CLOCK_READ = 4, |
| CLOCK_WRITE = 5, |
| TRY_READ = 6, |
| TRY_WRITE = 7, |
| COUNT = 8 |
| }; |
| |
| 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::CLOCK_READ: { |
| timespec ts = get_ts(); |
| if (LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&data->lock, CLOCK_MONOTONIC, |
| &ts) == 0) { |
| read_ops(); |
| LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
| } |
| break; |
| } |
| case Operation::CLOCK_WRITE: { |
| timespec ts = get_ts(); |
| if (LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&data->lock, CLOCK_MONOTONIC, |
| &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; |
| } |