From 7e91f3f950530252bb86655bc181051695f2b8b1 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 2 May 2023 11:41:09 +0200 Subject: [PATCH] Add pthread tests. --- tests/pthread/cleanup.c | 54 +++++++++++++++++ tests/pthread/common.h | 24 ++++++++ tests/pthread/customstack.c | 81 +++++++++++++++++++++++++ tests/pthread/extjoin.c | 67 +++++++++++++++++++++ tests/pthread/main.c | 33 +++++++++++ tests/pthread/once.c | 115 ++++++++++++++++++++++++++++++++++++ 6 files changed, 374 insertions(+) create mode 100644 tests/pthread/cleanup.c create mode 100644 tests/pthread/common.h create mode 100644 tests/pthread/customstack.c create mode 100644 tests/pthread/extjoin.c create mode 100644 tests/pthread/main.c create mode 100644 tests/pthread/once.c diff --git a/tests/pthread/cleanup.c b/tests/pthread/cleanup.c new file mode 100644 index 0000000000..446e946cf9 --- /dev/null +++ b/tests/pthread/cleanup.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#include + +const char *msg1 = "first"; +const char *msg2 = "second"; +const char *msg3 = "third"; + +void cleanup1(void *arg) { + printf("Running %s cleanup callback\n", (const char *)arg); +} +void cleanup2(void *arg) { + fprintf(stderr, "Running %s cleanup callback, to stderr\n", (const char *)arg); +} +void cleanup3(void *arg) { + printf("Running final (%s) callback\n", (const char *)arg); +} + +void *routine(void *arg) { + puts("1"); + pthread_cleanup_push(cleanup1, msg1); + puts("2"); + pthread_cleanup_push(cleanup2, msg2); + puts("3"); + pthread_cleanup_push(cleanup3, msg3); + puts("4"); + pthread_cleanup_pop(true); + puts("5"); + //exit(EXIT_SUCCESS); + pthread_exit(NULL); + puts("6"); + pthread_cleanup_pop(true); + pthread_cleanup_pop(true); +} + +int main(void) { + int result; + + puts("Main thread started"); + pthread_t second_thread; + if ((result = pthread_create(&second_thread, NULL, routine, NULL)) != 0) { + fprintf(stderr, "thread creation failed: %s\n", strerror(result)); + return EXIT_FAILURE; + } + if ((result = pthread_join(second_thread, NULL)) != 0) { + fprintf(stderr, "failed to join thread: %s\n", strerror(result)); + return EXIT_FAILURE; + } + puts("Main thread about to exit"); + return EXIT_SUCCESS; +} diff --git a/tests/pthread/common.h b/tests/pthread/common.h new file mode 100644 index 0000000000..2c2dbc305a --- /dev/null +++ b/tests/pthread/common.h @@ -0,0 +1,24 @@ +#ifndef _COMMON_H +#define _COMMON_H + +#include +#include +#include +#include + +int fail(int status, const char *reason) { + fprintf(stderr, "%s failed: %s\n", reason, strerror(status)); + + return EXIT_FAILURE; +} + +uintptr_t black_box_uintptr_t(uintptr_t arg) { + // Rust implements this by putting the value behind a volatile pointer and then simply loading it. + + uintptr_t arg_slot = arg; + volatile uintptr_t *ptr = &arg_slot; + + return *ptr; +} + +#endif // _COMMON_H diff --git a/tests/pthread/customstack.c b/tests/pthread/customstack.c new file mode 100644 index 0000000000..ebbc5eed10 --- /dev/null +++ b/tests/pthread/customstack.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define STACKSIZE 128 * 1024 + +struct retval { + uintptr_t some_stack_pointer; +}; +struct arg { + int status; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + + struct retval *retval = malloc(sizeof(struct retval)); + + if (retval == NULL) { + arg->status = ENOMEM; + return NULL; + } + + char some_array[256] = { 0 }; + retval->some_stack_pointer = black_box_uintptr_t((uintptr_t)some_array); + + return retval; +} + +int main(void) { + int status; + static char stack[STACKSIZE]; + + pthread_attr_t attr; + if ((status = pthread_attr_init(&attr)) != 0) { + return fail(status, "attr init"); + } + if ((status = pthread_attr_setstack(&attr, stack, STACKSIZE)) != 0) { + return fail(status, "attr setstack"); + } + + void *stack_again; + size_t stacksize_again; + + if ((status = pthread_attr_getstack(&attr, &stack_again, &stacksize_again)) != 0) { + return fail(status, "attr getstack"); + } + + assert(stack_again == stack); + assert(stacksize_again == STACKSIZE); + + pthread_t thread; + + if ((status = pthread_create(&thread, &attr, routine, NULL)) != 0) { + return fail(status, "pthread create"); + } + + if ((status = pthread_attr_destroy(&attr)) != 0) { + return fail(status, "attr destroy"); + } + + void *retval_raw; + + if ((status = pthread_join(thread, &retval_raw)) != 0) { + return fail(status, "pthread join"); + } + + struct retval *retval = retval_raw; + + assert(retval->some_stack_pointer >= (uintptr_t)stack); + assert(retval->some_stack_pointer < ((uintptr_t)stack) + STACKSIZE); + + free(retval); + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/extjoin.c b/tests/pthread/extjoin.c new file mode 100644 index 0000000000..78ab5b6a77 --- /dev/null +++ b/tests/pthread/extjoin.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +struct arg2 { + int status; + pthread_t t1; +}; + +void *routine1(void *arg) { + puts("Thread 1 spawned, waiting 1s."); + sleep(1); + puts("Thread 1 finished."); + return strdup("message from thread 1"); +} +void *routine2(void *arg_raw) { + struct arg2 *arg = arg_raw; + + puts("Thread 2 spawned, awaiting thread 1."); + + void *retval_raw; + int status; + + if ((status = pthread_join(arg->t1, &retval_raw)) != 0) { + arg->status = fail(status, "t1 join from thread 2"); + return NULL; + } + char *retval = retval_raw; + + assert(strcmp(retval, "message from thread 1") == 0); + + free(retval); + + return NULL; +} + +int main(void) { + pthread_t t1; + pthread_t t2; + + int status; + + puts("Main thread."); + + if ((status = pthread_create(&t1, NULL, routine1, NULL)) != 0) { + return fail(status, "t1 create"); + } + + puts("Created thread 1."); + + struct arg2 arg = { .status = 0, .t1 = t1 }; + + if ((status = pthread_create(&t2, NULL, routine2, &arg)) != 0) { + return fail(status, "t2 create"); + } + + if ((status = pthread_join(t2, NULL)) != 0) { + return fail(status, "t2 join"); + } + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/main.c b/tests/pthread/main.c new file mode 100644 index 0000000000..65c8a2d41c --- /dev/null +++ b/tests/pthread/main.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +void *thread_main(void *arg) { + puts("Thread main"); + return NULL; +} + +int main(void) { + int status; + + puts("Start, sleeping 1 second"); + sleep(1); + pthread_t thread; + void *arg = NULL; + if ((status = pthread_create(&thread, NULL, thread_main, arg)) != 0) { + return fail(status, "create thread"); + } + puts("Started"); + void *retval; + if ((status = pthread_join(thread, &retval)) != 0) { + return fail(status, "join thread"); + } + puts("Joined"); + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/once.c b/tests/pthread/once.c new file mode 100644 index 0000000000..e96e7f172b --- /dev/null +++ b/tests/pthread/once.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +struct once_data { + size_t count; +}; + +_Thread_local struct once_data once_data = {0}; + +#define COUNT 1024 +#define THREADS 4 + +void constructor() { + once_data.count += 1; +} + +struct arg { + pthread_barrier_t *barrier; + pthread_once_t *onces; + int status; + size_t count; + size_t index; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + + if (arg->index == THREADS - 1) { + printf("main thread at %zu\n", arg->index); + } else { + printf("spawned %zu\n", arg->index); + } + + int wait_status = pthread_barrier_wait(arg->barrier); + + printf("waited %zu leader=%s\n", arg->index, (wait_status == PTHREAD_BARRIER_SERIAL_THREAD) ? "true" : "false"); + + if (wait_status != PTHREAD_BARRIER_SERIAL_THREAD && wait_status != 0) { + return NULL; + } + + for (size_t i = 0; i < COUNT; i++) { + if ((arg->status = pthread_once(&arg->onces[i], constructor)) != 0) { + return NULL; + } + } + + arg->count = once_data.count; + + return NULL; +} + +int main(void) { + // TODO: Better test to simulate contention? + + int status; + + pthread_once_t onces[COUNT]; + + for (size_t i = 0; i < COUNT; i++) { + onces[i] = PTHREAD_ONCE_INIT; + } + + pthread_barrier_t barrier; + + printf("Barrier at %p, onces at %p\n", &barrier, onces); + + if ((status = pthread_barrier_init(&barrier, NULL, THREADS)) != 0) { + return fail(status, "barrier init"); + } + + pthread_t threads[THREADS]; + struct arg args[THREADS]; + + threads[0] = pthread_self(); + + for (size_t i = 0; i < THREADS; i++) { + args[i] = (struct arg){ .barrier = &barrier, .onces = onces, .status = 0, .count = 0, .index = i }; + printf("spawning %zu\n", i); + + if (i == 0) { + continue; + } + + if ((status = pthread_create(&threads[i], NULL, routine, &args[i])) != 0) { + return fail(status, "thread create"); + } + } + + routine(&args[0]); + + size_t total_count = 0; + + for (size_t i = 0; i < THREADS; i++) { + if (i != 0) { + if ((status = pthread_join(threads[i], NULL)) != 0) { + return fail(status, "join"); + } + } + if (args[i].status != 0) { + return fail(args[i].status, "thread"); + } + total_count += args[i].count; + } + + assert(total_count == COUNT); + + return EXIT_SUCCESS; +}