//===-- 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 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(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 reader_count; bool writer_flag; LIBC_NAMESPACE::cpp::Atomic 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(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(Operation::COUNT); Operation op = static_cast(((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 &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(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 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 finish_count; }; PShared *shared_data = reinterpret_cast( 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; }