From 9cc1bd04b2f85f4b106ab2b20532aa45bc88d3be Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 14 Dec 2024 09:39:51 -0800 Subject: [PATCH] Test rwlock more --- libc/testlib/BUILD.mk | 6 +- libc/testlib/trace.c | 151 ++++++++++++++++++ libc/testlib/trace.h | 11 ++ test/libc/thread/footek_test.c | 39 +++-- test/libc/thread/pthread_rwlock_rdlock_test.c | 62 +++---- 5 files changed, 230 insertions(+), 39 deletions(-) create mode 100644 libc/testlib/trace.c create mode 100644 libc/testlib/trace.h diff --git a/libc/testlib/BUILD.mk b/libc/testlib/BUILD.mk index 83fb3dd4002..401a81093bd 100644 --- a/libc/testlib/BUILD.mk +++ b/libc/testlib/BUILD.mk @@ -30,7 +30,8 @@ LIBC_TESTLIB_A_HDRS = \ libc/testlib/moby.h \ libc/testlib/subprocess.h \ libc/testlib/testlib.h \ - libc/testlib/viewables.h + libc/testlib/trace.h \ + libc/testlib/viewables.h \ LIBC_TESTLIB_A_SRCS_S = \ libc/testlib/bench.S \ @@ -80,9 +81,10 @@ LIBC_TESTLIB_A_SRCS_C = \ libc/testlib/testrunner.c \ libc/testlib/thunks.c \ libc/testlib/tmptest.c \ + libc/testlib/trace.c \ libc/testlib/waitforexit.c \ libc/testlib/waitforterm.c \ - libc/testlib/yield.c + libc/testlib/yield.c \ LIBC_TESTLIB_A_SRCS = \ $(LIBC_TESTLIB_A_SRCS_S) \ diff --git a/libc/testlib/trace.c b/libc/testlib/trace.c new file mode 100644 index 00000000000..ca9b753c8a3 --- /dev/null +++ b/libc/testlib/trace.c @@ -0,0 +1,151 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "trace.h" +#include +#include +#include +#include +#include +#include + +struct TraceEvent { + unsigned long long ts; + int pid; + int tid; + const char* name; + const char* cat; + char ph; +}; + +static int g_pid; +static atomic_bool g_oom; +static atomic_int g_count; +static thread_local int g_id; +static thread_local int g_ids; +static thread_local int g_tid; +static unsigned long g_start_rdtsc; +static struct TraceEvent g_events[1000000]; + +static unsigned long rdtsc(void) { +#ifdef __x86_64__ + unsigned ax, dx; + __asm__ volatile("rdtsc" : "=a"(ax), "=d"(dx)); + return (unsigned long)dx << 32 | ax; +#else + unsigned long c; + __asm__ volatile("mrs %0, cntvct_el0" : "=r"(c)); + return c * 48; // the fudge factor +#endif +} + +static int cosmo_trace_oom(void) { + if (atomic_load_explicit(&g_oom, memory_order_relaxed)) + return -1; + if (atomic_exchange_explicit(&g_oom, true, memory_order_acq_rel)) + return -1; + fprintf(stderr, "warning: ran out of trace event memory\n"); + return -1; +} + +static int cosmo_trace_reserve(int count) { + int id = atomic_load_explicit(&g_count, memory_order_relaxed); + if (id + count > sizeof(g_events) / sizeof(*g_events)) + return cosmo_trace_oom(); + id = atomic_fetch_add_explicit(&g_count, count, memory_order_acq_rel); + if (id + count > sizeof(g_events) / sizeof(*g_events)) + return cosmo_trace_oom(); + return id; +} + +static void cosmo_trace_event(int id, const char* name, const char* cat, + char ph) { + g_events[id].ts = rdtsc(); + g_events[id].pid = g_pid ? g_pid - 1 : getpid(); + g_events[id].tid = g_tid ? g_tid - 1 : gettid(); + g_events[id].name = name; + g_events[id].cat = cat; + g_events[id].ph = ph; +} + +void cosmo_trace_set_pid(int pid) { + g_pid = pid + 1; +} + +void cosmo_trace_set_tid(int tid) { + g_tid = tid + 1; +} + +void cosmo_trace_begin(const char* name) { + if (g_ids < 2) { + g_ids = 20; + g_id = cosmo_trace_reserve(g_ids); + if (g_id == -1) { + g_ids = 0; + return; + } + } + cosmo_trace_event(g_id++, name, "category", 'B'); + --g_ids; +} + +void cosmo_trace_end(const char* name) { + if (g_ids < 1) + return; + cosmo_trace_event(g_id++, name, "category", 'E'); + --g_ids; +} + +static void cosmo_trace_save(const char* filename) { + int count = atomic_load_explicit(&g_count, memory_order_relaxed); + if (!count) + return; + fprintf(stderr, "saving trace to %s...\n", filename); + FILE* file = fopen(filename, "w"); + if (!file) { + perror(filename); + return; + } + fprintf(file, "[\n"); + bool once = false; + for (int i = 0; i < count; i++) { + if (!g_events[i].name) + continue; + if (!once) { + once = true; + } else { + fputs(",\n", file); + } + fprintf(file, + "{\"name\": \"%s\", \"cat\": \"%s\", \"ph\": \"%c\", " + "\"ts\": %.3f, \"pid\": %d, \"tid\": %d}", + g_events[i].name, g_events[i].cat, g_events[i].ph, + (g_events[i].ts - g_start_rdtsc) / 3000., g_events[i].pid, + g_events[i].tid); + } + fprintf(file, "\n]\n"); + fclose(file); +} + +__attribute__((__constructor__)) static void trace_startup(void) { + g_start_rdtsc = rdtsc(); +} + +__attribute__((__destructor__)) static void trace_shutdown(void) { + cosmo_trace_save("trace.json"); // see chrome://tracing/ +} diff --git a/libc/testlib/trace.h b/libc/testlib/trace.h new file mode 100644 index 00000000000..05d438ff56e --- /dev/null +++ b/libc/testlib/trace.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_LIBC_TESTLIB_TRACE_H_ +#define COSMOPOLITAN_LIBC_TESTLIB_TRACE_H_ +COSMOPOLITAN_C_START_ + +void cosmo_trace_set_pid(int); +void cosmo_trace_set_tid(int); +void cosmo_trace_begin(const char*); +void cosmo_trace_end(const char*); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_LIBC_TESTLIB_TRACE_H_ */ diff --git a/test/libc/thread/footek_test.c b/test/libc/thread/footek_test.c index c089c10853d..a07ea6a3872 100644 --- a/test/libc/thread/footek_test.c +++ b/test/libc/thread/footek_test.c @@ -1,11 +1,14 @@ -#define USE POSIX_RECURSIVE +#define USE POSIX #define ITERATIONS 100000 #define THREADS 30 #define SPIN 1 #define FUTEX 2 -#define POSIX 3 -#define POSIX_RECURSIVE 4 +#define FUTEX_SHARED 3 +#define POSIX 4 +#define POSIX_RECURSIVE 5 +#define RWLOCK 6 +#define RWLOCK_SHARED 7 #ifdef __COSMOPOLITAN__ #include @@ -274,8 +277,11 @@ void lock(atomic_int *futex) { word = atomic_exchange_explicit(futex, 2, memory_order_acquire); while (word > 0) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); -#if USE == FUTEX - nsync_futex_wait_(futex, 2, 0, 0, 0); +#if USE == FUTEX || USE == FUTEX_SHARED + cosmo_futex_wait( + futex, 2, + USE == FUTEX_SHARED ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE, + 0, 0); #else pthread_yield_np(); #endif @@ -288,8 +294,10 @@ void unlock(atomic_int *futex) { int word = atomic_fetch_sub_explicit(futex, 1, memory_order_release); if (word == 2) { atomic_store_explicit(futex, 0, memory_order_release); -#if USE == FUTEX - nsync_futex_wake_(futex, 1, 0); +#if USE == FUTEX || USE == FUTEX_SHARED + cosmo_futex_wake( + futex, 1, + USE == FUTEX_SHARED ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE); #endif } } @@ -297,6 +305,7 @@ void unlock(atomic_int *futex) { int g_chores; atomic_int g_lock; pthread_mutex_t g_locker; +pthread_rwlock_t g_rwlocker; void *worker(void *arg) { for (int i = 0; i < ITERATIONS; ++i) { @@ -304,6 +313,10 @@ void *worker(void *arg) { pthread_mutex_lock(&g_locker); ++g_chores; pthread_mutex_unlock(&g_locker); +#elif USE == RWLOCK || USE == RWLOCK_SHARED + pthread_rwlock_wrlock(&g_rwlocker); + ++g_chores; + pthread_rwlock_unlock(&g_rwlocker); #else lock(&g_lock); ++g_chores; @@ -331,7 +344,6 @@ int main() { struct timeval start; gettimeofday(&start, 0); - pthread_mutex_t lock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); #if USE == POSIX_RECURSIVE @@ -342,6 +354,14 @@ int main() { pthread_mutex_init(&g_locker, &attr); pthread_mutexattr_destroy(&attr); + pthread_rwlockattr_t rwattr; + pthread_rwlockattr_init(&rwattr); +#if USE == RWLOCK_SHARED + pthread_rwlockattr_setpshared(&rwattr, PTHREAD_PROCESS_SHARED); +#endif + pthread_rwlock_init(&g_rwlocker, &rwattr); + pthread_rwlockattr_destroy(&rwattr); + pthread_t th[THREADS]; for (int i = 0; i < THREADS; ++i) pthread_create(&th[i], 0, worker, 0); @@ -360,7 +380,8 @@ int main() { tomicros(ru.ru_utime), // tomicros(ru.ru_stime)); - pthread_mutex_destroy(&lock); + pthread_rwlock_destroy(&g_rwlocker); + pthread_mutex_destroy(&g_locker); #ifdef __COSMOPOLITAN__ CheckForMemoryLeaks(); diff --git a/test/libc/thread/pthread_rwlock_rdlock_test.c b/test/libc/thread/pthread_rwlock_rdlock_test.c index e2bb27e3489..f804efe49d6 100644 --- a/test/libc/thread/pthread_rwlock_rdlock_test.c +++ b/test/libc/thread/pthread_rwlock_rdlock_test.c @@ -18,34 +18,32 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/atomic.h" #include "libc/calls/calls.h" +#include "libc/intrin/atomic.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" +#include "libc/stdalign.h" +#include "libc/stdio/rand.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" -#define READERS 8 -#define WRITERS 2 -#define READER_ITERATIONS 10000 -#define WRITER_ITERATIONS 1000 +#define READERS 8 +#define WRITERS 2 +#define ITERATIONS 1000 -int writes; -atomic_int reads; +atomic_bool done; +alignas(128) int foo; +alignas(128) int bar; pthread_rwlock_t lock; pthread_rwlockattr_t attr; pthread_barrier_t barrier; -FIXTURE(pthread_rwlock, private) { - reads = 0; - writes = 0; - ASSERT_EQ(0, pthread_rwlockattr_init(&attr)); - ASSERT_EQ(0, pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE)); - ASSERT_EQ(0, pthread_rwlock_init(&lock, &attr)); - ASSERT_EQ(0, pthread_rwlockattr_destroy(&attr)); +void delay(int k) { + int n = rand() % k; + for (volatile int i = 0; i < n; ++i) { + } } -FIXTURE(pthread_rwlock, pshared) { - reads = 0; - writes = 0; +void SetUp(void) { ASSERT_EQ(0, pthread_rwlockattr_init(&attr)); ASSERT_EQ(0, pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); ASSERT_EQ(0, pthread_rwlock_init(&lock, &attr)); @@ -58,23 +56,33 @@ void TearDown(void) { void *Reader(void *arg) { pthread_barrier_wait(&barrier); - for (int i = 0; i < READER_ITERATIONS; ++i) { + while (!atomic_load_explicit(&done, memory_order_relaxed)) { ASSERT_EQ(0, pthread_rwlock_rdlock(&lock)); - ++reads; + // cosmo_trace_begin("reader"); + int x = foo; + usleep(1); // delay(100000); + int y = bar; + ASSERT_EQ(x, y); + // cosmo_trace_end("reader"); ASSERT_EQ(0, pthread_rwlock_unlock(&lock)); + usleep(1); // delay(100000); } return 0; } void *Writer(void *arg) { pthread_barrier_wait(&barrier); - for (int i = 0; i < WRITER_ITERATIONS; ++i) { + for (int i = 0; i < ITERATIONS; ++i) { ASSERT_EQ(0, pthread_rwlock_wrlock(&lock)); - ++writes; + // cosmo_trace_begin("writer"); + ++foo; + delay(100); + ++bar; + // cosmo_trace_end("writer"); ASSERT_EQ(0, pthread_rwlock_unlock(&lock)); - for (volatile int i = 0; i < 100; ++i) - pthread_pause_np(); + delay(100); } + done = true; return 0; } @@ -82,14 +90,12 @@ TEST(pthread_rwlock_rdlock, test) { int i; pthread_t *t = gc(malloc(sizeof(pthread_t) * (READERS + WRITERS))); ASSERT_EQ(0, pthread_barrier_init(&barrier, 0, READERS + WRITERS)); - for (i = 0; i < READERS + WRITERS; ++i) { + for (i = 0; i < READERS + WRITERS; ++i) ASSERT_SYS(0, 0, pthread_create(t + i, 0, i < READERS ? Reader : Writer, 0)); - } - for (i = 0; i < READERS + WRITERS; ++i) { + for (i = 0; i < READERS + WRITERS; ++i) EXPECT_SYS(0, 0, pthread_join(t[i], 0)); - } - EXPECT_EQ(READERS * READER_ITERATIONS, reads); - EXPECT_EQ(WRITERS * WRITER_ITERATIONS, writes); + EXPECT_EQ(WRITERS * ITERATIONS, foo); + EXPECT_EQ(WRITERS * ITERATIONS, bar); ASSERT_EQ(0, pthread_barrier_destroy(&barrier)); }