Test rwlock more

This commit is contained in:
Justine Tunney 2024-12-14 09:39:51 -08:00
parent 69402f4d78
commit 9cc1bd04b2
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
5 changed files with 230 additions and 39 deletions

View file

@ -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) \

151
libc/testlib/trace.c Normal file
View file

@ -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 <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <threads.h>
#include <unistd.h>
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/
}

11
libc/testlib/trace.h Normal file
View file

@ -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_ */

View file

@ -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 <cosmo.h>
@ -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();

View file

@ -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));
}