0.3.0: converge relibc to upstream 0.6.0 + Red Bear patches

This commit is contained in:
2026-07-06 19:13:08 +03:00
parent 1a0edd8eeb
commit 4ef7e57571
1466 changed files with 75236 additions and 13644 deletions
+124
View File
@@ -0,0 +1,124 @@
#include "../test_helpers.h"
#include "common.h"
#include <assert.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
// Same test logic as test_barrier in rustc/library/std/sync/barrier/tests.rs
#define N 10
struct arg {
pthread_barrier_t *barrier;
volatile _Atomic(unsigned) *count;
bool is_leader;
};
void *routine(void *arg_raw) {
struct arg *arg = arg_raw;
int status = pthread_barrier_wait(arg->barrier);
arg->is_leader = status == PTHREAD_BARRIER_SERIAL_THREAD;
if (!arg->is_leader)
ERROR_IF(pthread_barrier_wait, status, != 0);
// We can now modify the counter.
atomic_fetch_add_explicit(arg->count, 1, memory_order_relaxed);
return NULL;
}
int main(void) {
int status;
pthread_barrier_t barrier;
pthread_barrierattr_t attr;
status = pthread_barrierattr_init(&attr);
ERROR_IF(pthread_barrierattr_init, status, != 0);
int pshared;
//
// BARRIER ATTR
//
status = pthread_barrierattr_getpshared(&attr, &pshared);
// PTHREAD_PROCESS_PRIVATE is default according to POSIX.
assert(pshared == PTHREAD_PROCESS_PRIVATE);
ERROR_IF(pthread_barrierattr_getpshared, status, != 0);
status = pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
ERROR_IF(pthread_barrierattr_setpshared, status, != 0);
status = pthread_barrierattr_getpshared(&attr, &pshared);
assert(pshared == PTHREAD_PROCESS_SHARED);
ERROR_IF(pthread_barrierattr_getpshared, status, != 0);
status = pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
ERROR_IF(pthread_barrierattr_setpshared, status, != 0);
status = pthread_barrierattr_getpshared(&attr, &pshared);
assert(pshared == PTHREAD_PROCESS_PRIVATE);
ERROR_IF(pthread_barrierattr_getpshared, status, != 0);
//
// BARRIER
//
status = pthread_barrier_init(&barrier, &attr, N);
ERROR_IF(pthread_barrier_init, status, != 0);
status = pthread_barrierattr_destroy(&attr);
ERROR_IF(pthread_barrierattr_destroy, status, != 0);
//
// CREATE THREAD
//
pthread_t threads[N - 1];
struct arg args[N - 1];
_Atomic(unsigned) count = false;
for (size_t i = 0; i < N - 1; i++) {
args[i] = (struct arg){ .count = &count, .barrier = &barrier, .is_leader = false };
status = pthread_create(&threads[i], NULL, routine, &args[i]);
ERROR_IF(pthread_create, status, != 0);
}
// Must not be set before having waited for the barrier. This is part of the
// test. Normally spawned threads run before the scheduler returns to the
// parent thread, so it should at least partially verify that barriers work.
unsigned value = atomic_load_explicit(&count, memory_order_relaxed);
UNEXP_IF(count_before_barrier_wait, value, > 0);
status = pthread_barrier_wait(&barrier);
bool leader_found = status == PTHREAD_BARRIER_SERIAL_THREAD;
if (!leader_found) {
ERROR_IF(pthread_barrier_wait, status, != 0);
}
for (size_t i = 0; i < N - 1; i++) {
status = pthread_join(threads[i], NULL);
ERROR_IF(pthread_join, status, != 0);
// SAFETY: pthread_create and pthread_join are Acquire-Release
leader_found |= args[i].is_leader;
}
assert(leader_found);
status = pthread_barrier_destroy(&barrier);
ERROR_IF(pthread_barrier_destroy, status, != 0);
return 0;
}
+65
View File
@@ -0,0 +1,65 @@
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
// TODO: glibc requires arg to be not const, but not relibc
#ifndef __GLIBC__
const char *msg1 = "first";
const char *msg2 = "second";
const char *msg3 = "third";
#else
char *msg1 = "first";
char *msg2 = "second";
char *msg3 = "third";
#endif
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) {
assert(arg == NULL);
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);
return NULL;
}
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;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef _COMMON_H
#define _COMMON_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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
+81
View File
@@ -0,0 +1,81 @@
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#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;
}
+31
View File
@@ -0,0 +1,31 @@
#include <assert.h>
#include <stddef.h>
#include <unistd.h>
#include <pthread.h>
#include "common.h"
void *routine(void *arg) {
assert(arg == NULL);
usleep(100000);
puts("Thread succeeded");
return NULL;
}
int main(void) {
int status;
pthread_t thread;
if ((status = pthread_create(&thread, NULL, routine, NULL)) != 0) {
return fail(status, "failed to create thread");
}
if ((status = pthread_detach(thread)) != 0) {
return fail(status, "failed to detach thread");
}
pthread_exit(NULL);
}
+68
View File
@@ -0,0 +1,68 @@
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include "common.h"
struct arg2 {
int status;
pthread_t t1;
};
void *routine1(void *arg) {
assert(arg == NULL);
puts("Thread 1 spawned, waiting 1s.");
usleep(100000);
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;
}
+38
View File
@@ -0,0 +1,38 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include "common.h"
void *thread_main(void *arg) {
puts("Thread main");
assert(arg == NULL);
return NULL;
}
int main(void) {
int status;
puts("Start, sleeping 1 second");
usleep(100000);
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");
}
assert(retval == NULL);
puts("Joined");
return EXIT_SUCCESS;
}
+108
View File
@@ -0,0 +1,108 @@
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include "../test_helpers.h"
#define N 10
#define M 10000
struct arg {
pthread_mutex_t *mutex;
unsigned *protected;
};
void *routine(void *arg_raw) {
struct arg *arg = arg_raw;
int status;
unsigned depth = 0;
unsigned i = 0;
while (i < M) {
// Bad random distribution, but should work.
bool lock_again = (depth == 0) || (random_bool() && random_bool());
if (lock_again) {
status = pthread_mutex_lock(arg->mutex);
ERROR_IF(pthread_mutex_lock, status, != 0);
depth += 1;
} else {
status = pthread_mutex_unlock(arg->mutex);
ERROR_IF(pthread_mutex_unlock, status, != 0);
depth -= 1;
}
if (depth == 0) {
continue;
}
unsigned value = *arg->protected;
*arg->protected = value + 1;
i += 1;
}
while (depth > 0) {
status = pthread_mutex_unlock(arg->mutex);
ERROR_IF(pthread_mutex_unlock, status, != 0);
depth--;
}
return NULL;
}
int main(void) {
int status;
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
status = pthread_mutexattr_init(&attr);
ERROR_IF(pthread_mutexattr_init, status, != 0);
status = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
ERROR_IF(pthread_mutexattr_settype, status, != 0);
status = pthread_mutex_init(&mutex, &attr);
ERROR_IF(pthread_mutex_init, status, != 0);
status = pthread_mutexattr_destroy(&attr);
ERROR_IF(pthread_mutexattr_destroy, status, != 0);
status = pthread_mutex_lock(&mutex);
ERROR_IF(pthread_mutex_lock, status, != 0);
status = pthread_mutex_trylock(&mutex);
ERROR_IF(pthread_mutex_trylock, status, != 0);
status = pthread_mutex_unlock(&mutex);
ERROR_IF(pthread_mutex_unlock, status, != 0);
// Still locked with count = 1.
pthread_t threads[N];
struct arg args[N];
unsigned protected = 0;
for (size_t i = 0; i < N; i++) {
args[i] = (struct arg){ .mutex = &mutex, .protected = &protected };
status = pthread_create(&threads[i], NULL, routine, &args[i]);
ERROR_IF(pthread_create, status, != 0);
}
protected = 1;
status = pthread_mutex_unlock(&mutex);
ERROR_IF(pthread_mutex_unlock, status, != 0);
for (size_t i = 0; i < N; i++) {
status = pthread_join(threads[i], NULL);
ERROR_IF(pthread_join, status, != 0);
}
status = pthread_mutex_destroy(&mutex);
ERROR_IF(pthread_mutex_destroy, status, != 0);
return 0;
}
+100
View File
@@ -0,0 +1,100 @@
#include <assert.h>
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "common.h"
#include "../test_helpers.h"
_Thread_local size_t this_thread_count = 0;
#define COUNT 1024
#define THREADS 4
static void constructor(void) {
this_thread_count++;
}
struct arg {
pthread_barrier_t *barrier;
pthread_once_t *onces;
size_t count;
size_t index;
};
void *routine(void *arg_raw) {
int status;
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);
}
status = pthread_barrier_wait(arg->barrier);
printf("waited %zu leader=%s\n", arg->index, (status == PTHREAD_BARRIER_SERIAL_THREAD) ? "true" : "false");
if (status != PTHREAD_BARRIER_SERIAL_THREAD) {
ERROR_IF(pthread_barrier_wait, status, != 0);
return NULL;
}
for (size_t i = 0; i < COUNT; i++) {
status = pthread_once(&arg->onces[i], constructor);
ERROR_IF(pthread_once, status, != 0);
}
arg->count = this_thread_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);
status = pthread_barrier_init(&barrier, NULL, THREADS);
ERROR_IF(pthread_barrier_init, status, != 0);
pthread_t threads[THREADS];
struct arg args[THREADS];
for (size_t i = 0; i < THREADS; i++) {
args[i] = (struct arg){ .barrier = &barrier, .onces = onces, .count = 0, .index = i };
printf("spawning %zu\n", i);
status = pthread_create(&threads[i], NULL, routine, &args[i]);
ERROR_IF(pthread_create, status, != 0);
}
size_t total_count = 0;
for (size_t i = 0; i < THREADS; i++) {
status = pthread_join(threads[i], NULL);
ERROR_IF(pthread_join, status, != 0);
total_count += args[i].count;
}
status = pthread_barrier_destroy(&barrier);
ERROR_IF(pthread_barrier_destroy, status, != 0);
assert(total_count == COUNT);
return EXIT_SUCCESS;
}
+64
View File
@@ -0,0 +1,64 @@
#include "../test_helpers.h"
#include "common.h"
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
// Same test logic as frob in rustc/library/std/sync/rwlock/tests.rs
#define N 10
//#define M 1000
#define M 100000
struct arg {
pthread_rwlock_t *rwlock;
};
void *routine(void *arg_raw) {
struct arg *arg = arg_raw;
int status;
for (uint64_t i = 0; i < M; i++) {
if (random_bool()) {
status = pthread_rwlock_wrlock(arg->rwlock);
ERROR_IF(pthread_rwlock_wrlock, status, != 0);
} else {
status = pthread_rwlock_rdlock(arg->rwlock);
ERROR_IF(pthread_rwlock_rdlock, status, != 0);
}
status = pthread_rwlock_unlock(arg->rwlock);
ERROR_IF(pthread_rwlock_unlock, status, != 0);
}
return NULL;
}
int main(void) {
int status;
pthread_rwlock_t rwlock;
status = pthread_rwlock_init(&rwlock, NULL);
ERROR_IF(pthread_rwlock_init, status, != 0);
pthread_t threads[N];
struct arg args[N];
for (size_t i = 0; i < N; i++) {
args[i] = (struct arg){ .rwlock = &rwlock };
status = pthread_create(&threads[i], NULL, routine, &args[i]);
ERROR_IF(pthread_create, status, != 0);
}
for (size_t i = 0; i < N; i++) {
status = pthread_join(threads[i], NULL);
ERROR_IF(pthread_join, status, != 0);
}
status = pthread_rwlock_destroy(&rwlock);
ERROR_IF(pthread_rwlock_destroy, status, != 0);
return 0;
}
+58
View File
@@ -0,0 +1,58 @@
#include "../test_helpers.h"
#include "common.h"
#include <assert.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
// Adapted from test_rwlock_try_write in rustc/library/std/sync/rwlock/tests.rs
int main(void) {
int status;
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
pthread_rwlockattr_t attr;
status = pthread_rwlockattr_init(&attr);
ERROR_IF(pthread_rwlockattr_init, status, != 0);
// Call setpshared twice to check both constants work.
status = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
ERROR_IF(pthread_rwlockattr_setpshared, status, != 0);
status = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
ERROR_IF(pthread_rwlockattr_setpshared, status, != 0);
status = pthread_rwlock_init(&rwlock, &attr);
ERROR_IF(pthread_rwlock_init, status, != 0);
status = pthread_rwlockattr_destroy(&attr);
ERROR_IF(pthread_rwlockattr_destroy, status, != 0);
status = pthread_rwlock_rdlock(&rwlock);
ERROR_IF(pthread_rwlock_rdlock, status, != 0);
status = pthread_rwlock_trywrlock(&rwlock);
UNEXP_IF(pthread_rwlock_trywrlock, status, != EBUSY);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += 200 * 1000000;
status = pthread_rwlock_timedwrlock(&rwlock, &ts);
UNEXP_IF(pthread_rwlock_timedwrlock, status, != ETIMEDOUT);
status = pthread_rwlock_unlock(&rwlock);
ERROR_IF(pthread_rwlock_unlock, status, != 0);
status = pthread_rwlock_trywrlock(&rwlock);
ERROR_IF(pthread_rwlock_rdlock, status, != 0);
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += 200 * 1000000;
status = pthread_rwlock_timedrdlock(&rwlock, &ts);
UNEXP_IF(pthread_rwlock_timedrdlock, status, != ETIMEDOUT);
status = pthread_rwlock_destroy(&rwlock);
ERROR_IF(pthread_rwlock_destroy, status, != 0);
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
/// test fork inside thread
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
#include "test_helpers.h"
#define NUM_THREADS 4
#define ITERATIONS 3
void* thread_func(void* arg) {
long tid = (long)arg;
for (int i = 0; i < ITERATIONS; i++) {
printf("thread %ld loop %i\n", tid, i);
pid_t pid = fork();
ERROR_IF(fork, pid, < 0);
if (pid == 0) {
_exit(0);
} else {
waitpid(pid, NULL, 0);
}
usleep(1000);
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
int status;
for (long i = 0; i < NUM_THREADS; i++) {
status = pthread_create(&threads[i], NULL, thread_func, (void*)i);
ERROR_IF(pthread_create, status, != 0);
}
printf("joining threads\n");
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
+86
View File
@@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include "test_helpers.h"
pthread_mutex_t lock;
pthread_cond_t cond;
struct timespec start_time;
int ready = 0;
struct timespec get_timeout(long msec) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += msec * 1000000;
return ts;
}
void print_timed(char* msg) {
struct timespec end_time;
clock_gettime(CLOCK_MONOTONIC, &end_time);
printf("[%.3f]: ", (end_time.tv_nsec - start_time.tv_nsec) / 1000000000.0 +
(end_time.tv_sec - start_time.tv_sec));
printf("%s\n", msg);
fflush(NULL);
}
void* signaler_thread(void* arg) {
(void)arg;
usleep(150000); // 150ms
ready = 1;
print_timed("signaler_thread");
pthread_cond_signal(&cond);
return NULL;
}
void test_timeout_case() {
pthread_mutex_lock(&lock);
struct timespec ts = get_timeout(500);
print_timed("test_timeout_case start");
int rc = pthread_cond_timedwait(&cond, &lock, &ts);
UNEXP_IF(pthread_cond_timedwait, rc, != ETIMEDOUT);
print_timed("test_timeout_case end");
pthread_mutex_unlock(&lock);
}
void test_success_case() {
ready = 0;
pthread_t thread;
pthread_create(&thread, NULL, signaler_thread, NULL);
pthread_mutex_lock(&lock);
struct timespec ts = get_timeout(50000);
print_timed("test_success_case start");
while (!ready) {
print_timed("test_success_case waiting");
int rc = pthread_cond_timedwait(&cond, &lock, &ts);
UNEXP_IF(pthread_cond_timedwait, rc, != 0);
}
print_timed("test_success_case end");
pthread_mutex_unlock(&lock);
pthread_join(thread, NULL);
}
int main() {
clock_gettime(CLOCK_MONOTONIC, &start_time);
if (pthread_mutex_init(&lock, NULL) != 0) {
perror("mutex init failed");
return 1;
}
if (pthread_cond_init(&cond, NULL) != 0) {
perror("cond init failed");
return 1;
}
test_timeout_case();
// TODO: "rlct_clone not implemented for aarch64 yet"
#if defined(__linux__) && defined(__aarch64__)
printf("test_success_case skipped");
#else
test_success_case();
#endif
return 0;
}
+91
View File
@@ -0,0 +1,91 @@
#include "../test_helpers.h"
#include "common.h"
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
struct arg {
int status;
bool completed;
pthread_barrier_t barrier;
pthread_mutex_t mutex;
};
void *routine(void *arg_raw) {
struct arg *arg = (struct arg *)arg_raw;
int barrier_status = pthread_barrier_wait(&arg->barrier);
if (barrier_status != 0 && barrier_status != PTHREAD_BARRIER_SERIAL_THREAD) {
arg->status = barrier_status;
fputs("failed to wait for barrier\n", stderr);
return NULL;
}
puts("thread waited");
struct timespec abstime;
if ((arg->status = clock_gettime(CLOCK_MONOTONIC, &abstime)) != 0) {
fputs("failed to get current time\n", stderr);
return NULL;
}
abstime.tv_sec += 1;
if ((arg->status = pthread_mutex_timedlock(&arg->mutex, &abstime)) != ETIMEDOUT) {
fputs("failed to fail at locking mutex\n", stderr);
return NULL;
}
arg->status = 0;
return NULL;
}
int main(void) {
int status;
struct arg arg;
arg.completed = false;
status = pthread_barrier_init(&arg.barrier, NULL, 2);
ERROR_IF2(pthread_barrier_init, status, != 0);
status = pthread_mutex_init(&arg.mutex, NULL);
ERROR_IF2(pthread_mutex_init, status, != 0);
status = pthread_mutex_trylock(&arg.mutex);
ERROR_IF2(pthread_mutex_trylock, status, != 0);
pthread_t thread;
status = pthread_create(&thread, NULL, routine, &arg);
ERROR_IF2(pthread_create, status, != 0);
status = pthread_barrier_wait(&arg.barrier);
if (status != PTHREAD_BARRIER_SERIAL_THREAD) {
ERROR_IF2(pthread_barrier_wait, status, != 0);
}
puts("main waited");
status = pthread_join(thread, NULL);
ERROR_IF2(pthread_join, status, != 0);
status = pthread_mutex_unlock(&arg.mutex);
ERROR_IF2(pthread_mutex_unlock, status, != 0);
if (arg.status != 0) {
fprintf(stderr, "thread failed: %s\n", strerror(arg.status));
return EXIT_FAILURE;
}
status = pthread_mutex_destroy(&arg.mutex);
ERROR_IF2(pthread_mutex_destroy, status, != 0);
status = pthread_barrier_destroy(&arg.barrier);
ERROR_IF2(pthread_barrier_destroy, status, != 0);
return EXIT_SUCCESS;
}
+99
View File
@@ -0,0 +1,99 @@
#include <assert.h>
// PTHREAD_KEYS_MAX, PTHREAD_DESTRUCTOR_ITERATIONS
#include <limits.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
pthread_key_t key_a, key_b, key_c, key_d, key_e;
size_t total_destructor_runs = 0;
void drop_key_a(void *val_a) {
assert(pthread_getspecific(key_a) == NULL && val_a == (void *)0xaaaa);
total_destructor_runs++;
void *val_b = pthread_getspecific(key_b);
printf("drop_key_a(a=%p, b=%p)\n", val_a, val_b);
if (total_destructor_runs <= 2) {
pthread_setspecific(key_a, val_a);
return;
}
if (val_b != NULL) {
assert(val_b == (void *)0xbbbb);
} else {
pthread_setspecific(key_b, (void *)0xbbbb);
}
}
void drop_key_b(void *val_b) {
assert(pthread_getspecific(key_b) == NULL && val_b == (void *)0xbbbb);
total_destructor_runs++;
void *val_a = pthread_getspecific(key_a);
printf("drop_key_b(a=%p, b=%p)\n", val_a, val_b);
if (total_destructor_runs <= 2) {
pthread_setspecific(key_b, val_b);
return;
}
if (val_a != NULL) {
assert(val_a == (void *)0xaaaa);
} else {
pthread_setspecific(key_a, (void *)0xaaaa);
}
}
void drop_unreachable(void *val_c) {
// Should never be called.
(void)val_c;
assert(false);
}
void drop_key_d(void *val_d) {
assert(pthread_getspecific(key_d) == NULL && val_d == (void *)0xdddd);
// [`pthread_key_{create,setspecific,getspecific,delete}`] can still be used
// inside the destructor itself.
printf("drop_key_d(d=%p)\n", val_d);
pthread_key_t key_g;
assert(!pthread_key_create(&key_g, drop_unreachable));
assert(!pthread_setspecific(key_g, &key_g));
assert(pthread_getspecific(key_g) == &key_g);
assert(!pthread_key_delete(key_g));
}
void *test_tls_destructors(void *arg) {
(void)arg;
assert(!pthread_key_create(&key_a, drop_key_a));
assert(!pthread_key_create(&key_b, drop_key_b));
assert(!pthread_key_create(&key_c, drop_unreachable));
assert(!pthread_key_create(&key_d, drop_key_d));
assert(!pthread_key_create(&key_e, drop_unreachable));
assert(!pthread_setspecific(key_a, (void *)0xaaaa));
assert(!pthread_setspecific(key_b, (void *)0xbbbb));
assert(!pthread_setspecific(key_c, (void *)0xcccc));
assert(!pthread_setspecific(key_d, (void *)0xdddd));
assert(!pthread_key_delete(key_c));
return NULL;
}
int main(void) {
pthread_key_t key_f;
assert(!pthread_key_create(&key_f, NULL));
assert(!pthread_setspecific(key_f, &key_f));
assert(pthread_getspecific(key_f) == &key_f);
assert(!pthread_key_delete(key_f));
pthread_t t1;
assert(!pthread_create(&t1, NULL, test_tls_destructors, NULL));
assert(!pthread_join(t1, NULL));
printf("total_destructor_iterations: %zu\n", total_destructor_runs);
// There are two destructors (i.e., `drop_key_{a, b}`), so the total number
// of iterations will be `2 * PTHREAD_DESTRUCTOR_ITERATIONS`.
assert((total_destructor_runs / 2) == PTHREAD_DESTRUCTOR_ITERATIONS);
return EXIT_SUCCESS;
}