Support process shared condition variables

This commit is contained in:
Justine Tunney 2024-07-22 16:33:23 -07:00
parent 3de6632be6
commit 0a9a6f86bb
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
14 changed files with 168 additions and 19 deletions

View file

@ -37,6 +37,7 @@ static errno_t pthread_mutex_lock_impl(pthread_mutex_t *mutex) {
// get current state of lock // get current state of lock
word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); word = atomic_load_explicit(&mutex->_word, memory_order_relaxed);
#if PTHREAD_USE_NSYNC
// use fancy nsync mutex if possible // use fancy nsync mutex if possible
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && // if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && //
MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && //
@ -44,6 +45,7 @@ static errno_t pthread_mutex_lock_impl(pthread_mutex_t *mutex) {
_weaken(nsync_mu_lock)((nsync_mu *)mutex); _weaken(nsync_mu_lock)((nsync_mu *)mutex);
return 0; return 0;
} }
#endif
// implement barebones normal mutexes // implement barebones normal mutexes
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) {

View file

@ -46,6 +46,7 @@ errno_t pthread_mutex_trylock(pthread_mutex_t *mutex) {
// get current state of lock // get current state of lock
word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); word = atomic_load_explicit(&mutex->_word, memory_order_relaxed);
#if PTHREAD_USE_NSYNC
// delegate to *NSYNC if possible // delegate to *NSYNC if possible
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL &&
MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && //
@ -56,6 +57,7 @@ errno_t pthread_mutex_trylock(pthread_mutex_t *mutex) {
return EBUSY; return EBUSY;
} }
} }
#endif
// handle normal mutexes // handle normal mutexes
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) {

View file

@ -45,6 +45,7 @@ errno_t pthread_mutex_unlock(pthread_mutex_t *mutex) {
// get current state of lock // get current state of lock
word = atomic_load_explicit(&mutex->_word, memory_order_relaxed); word = atomic_load_explicit(&mutex->_word, memory_order_relaxed);
#if PTHREAD_USE_NSYNC
// use fancy nsync mutex if possible // use fancy nsync mutex if possible
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && // if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL && //
MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && // MUTEX_PSHARED(word) == PTHREAD_PROCESS_PRIVATE && //
@ -52,6 +53,7 @@ errno_t pthread_mutex_unlock(pthread_mutex_t *mutex) {
_weaken(nsync_mu_unlock)((nsync_mu *)mutex); _weaken(nsync_mu_unlock)((nsync_mu *)mutex);
return 0; return 0;
} }
#endif
// implement barebones normal mutexes // implement barebones normal mutexes
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) {

View file

@ -22,6 +22,8 @@
#include "libc/runtime/syslib.internal.h" #include "libc/runtime/syslib.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
void sys_sched_yield(void);
/** /**
* Yields current thread's remaining timeslice to operating system. * Yields current thread's remaining timeslice to operating system.
* *
@ -30,13 +32,16 @@
int pthread_yield_np(void) { int pthread_yield_np(void) {
if (IsXnuSilicon()) { if (IsXnuSilicon()) {
__syslib->__pthread_yield_np(); __syslib->__pthread_yield_np();
} else if (IsOpenbsd()) { } else if (IsOpenbsd() || IsNetbsd()) {
pthread_pause_np(); // sched_yield() is punishingly slow on OpenBSD // sched_yield() is punishingly slow on OpenBSD
// it's ruinously slow it'll destroy everything
pthread_pause_np();
} else { } else {
sched_yield(); sys_sched_yield();
} }
return 0; return 0;
} }
__weak_reference(pthread_yield_np, thrd_yield); __weak_reference(pthread_yield_np, thrd_yield);
__weak_reference(pthread_yield_np, sched_yield);
__weak_reference(pthread_yield_np, pthread_yield); __weak_reference(pthread_yield_np, pthread_yield);

View file

@ -19,12 +19,11 @@
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/sysv/consts/nr.h" #include "libc/sysv/consts/nr.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
.privileged
// Relinquishes scheduled quantum. // Relinquishes scheduled quantum.
// //
// @return 0 on success, or -1 w/ errno // @return 0 on success, or -1 w/ errno
sched_yield: sys_sched_yield:
#ifdef __x86_64__ #ifdef __x86_64__
push %rbp push %rbp
mov %rsp,%rbp mov %rsp,%rbp
@ -107,5 +106,5 @@ sched_yield:
#else #else
#error "arch unsupported" #error "arch unsupported"
#endif #endif
.endfn sched_yield,globl .endfn sys_sched_yield,globl
.previous .previous

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ /*-*- 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 vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney Copyright 2024 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the any purpose with or without fee is hereby granted, provided that the
@ -16,8 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/intrin/atomic.h"
#include "libc/limits.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/cv.h" #include "third_party/nsync/cv.h"
#include "third_party/nsync/futex.internal.h"
/** /**
* Wakes all threads waiting on condition, e.g. * Wakes all threads waiting on condition, e.g.
@ -37,6 +40,19 @@
* @see pthread_cond_wait * @see pthread_cond_wait
*/ */
errno_t pthread_cond_broadcast(pthread_cond_t *cond) { errno_t pthread_cond_broadcast(pthread_cond_t *cond) {
#if PTHREAD_USE_NSYNC
// favor *NSYNC if this is a process private condition variable
// if using Mike Burrows' code isn't possible, use a naive impl
if (!cond->_pshared) {
nsync_cv_broadcast((nsync_cv *)cond); nsync_cv_broadcast((nsync_cv *)cond);
return 0; return 0;
}
#endif
// roll forward the monotonic sequence
atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel);
if (atomic_load_explicit(&cond->_waiters, memory_order_acquire))
nsync_futex_wake_((atomic_int *)&cond->_sequence, INT_MAX, cond->_pshared);
return 0;
} }

View file

@ -16,8 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/cv.h"
/** /**
* Destroys condition. * Destroys condition.
@ -26,6 +29,22 @@
* @raise EINVAL if threads are still waiting on condition * @raise EINVAL if threads are still waiting on condition
*/ */
errno_t pthread_cond_destroy(pthread_cond_t *cond) { errno_t pthread_cond_destroy(pthread_cond_t *cond) {
// check if there's active waiters
#if PTHREAD_USE_NSYNC
if (cond->_pshared) {
if (((nsync_cv *)cond)->waiters)
return EINVAL;
} else {
if (atomic_load_explicit(&cond->_waiters, memory_order_relaxed))
return EINVAL;
}
#else
if (atomic_load_explicit(&cond->_waiters, memory_order_relaxed))
return EINVAL;
#endif
// destroy object
memset(cond, -1, sizeof(*cond)); memset(cond, -1, sizeof(*cond));
return 0; return 0;
} }

View file

@ -27,5 +27,7 @@
errno_t pthread_cond_init(pthread_cond_t *cond, errno_t pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr) { const pthread_condattr_t *attr) {
*cond = (pthread_cond_t){0}; *cond = (pthread_cond_t){0};
if (attr)
cond->_pshared = *attr;
return 0; return 0;
} }

View file

@ -16,8 +16,10 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/intrin/atomic.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/cv.h" #include "third_party/nsync/cv.h"
#include "third_party/nsync/futex.internal.h"
/** /**
* Wakes at least one thread waiting on condition, e.g. * Wakes at least one thread waiting on condition, e.g.
@ -37,6 +39,19 @@
* @see pthread_cond_wait * @see pthread_cond_wait
*/ */
errno_t pthread_cond_signal(pthread_cond_t *cond) { errno_t pthread_cond_signal(pthread_cond_t *cond) {
#if PTHREAD_USE_NSYNC
// favor *NSYNC if this is a process private condition variable
// if using Mike Burrows' code isn't possible, use a naive impl
if (!cond->_pshared) {
nsync_cv_signal((nsync_cv *)cond); nsync_cv_signal((nsync_cv *)cond);
return 0; return 0;
}
#endif
// roll forward the monotonic sequence
atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel);
if (atomic_load_explicit(&cond->_waiters, memory_order_acquire))
nsync_futex_wake_((atomic_int *)&cond->_sequence, 1, cond->_pshared);
return 0;
} }

View file

@ -16,14 +16,61 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/thread/lock.h" #include "libc/thread/lock.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/thread2.h" #include "libc/thread/thread2.h"
#include "third_party/nsync/common.internal.h" #include "third_party/nsync/common.internal.h"
#include "third_party/nsync/cv.h" #include "third_party/nsync/cv.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/time.h" #include "third_party/nsync/time.h"
struct PthreadWait {
pthread_cond_t *cond;
pthread_mutex_t *mutex;
};
static void pthread_cond_leave(void *arg) {
struct PthreadWait *wait = (struct PthreadWait *)arg;
if (pthread_mutex_lock(wait->mutex))
__builtin_trap();
atomic_fetch_sub_explicit(&wait->cond->_waiters, 1, memory_order_acq_rel);
}
static errno_t pthread_cond_timedwait_impl(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime) {
// this is a cancelation point
// check the cancelation status before we begin waiting
if (pthread_testcancel_np() == ECANCELED)
return ECANCELED;
// get original monotonic sequence while lock is held
uint32_t seq1 = atomic_load_explicit(&cond->_sequence, memory_order_relaxed);
// start waiting on condition variable
atomic_fetch_add_explicit(&cond->_waiters, 1, memory_order_acq_rel);
if (pthread_mutex_unlock(mutex))
__builtin_trap();
// wait for sequence change, timeout, or cancelation
int rc;
struct PthreadWait waiter = {cond, mutex};
pthread_cleanup_push(pthread_cond_leave, &waiter);
rc = nsync_futex_wait_((atomic_int *)&cond->_sequence, seq1, cond->_pshared,
abstime);
pthread_cleanup_pop(true);
if (rc == -EAGAIN)
rc = 0;
// turn linux syscall status into posix errno
return -rc;
}
/** /**
* Waits for condition with optional time limit, e.g. * Waits for condition with optional time limit, e.g.
* *
@ -49,11 +96,39 @@
*/ */
errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime) { const struct timespec *abstime) {
// validate arguments
struct PosixThread *pt;
if (!(pt = _pthread_self()))
return EINVAL;
if (abstime && !(0 <= abstime->tv_nsec && abstime->tv_nsec < 1000000000)) if (abstime && !(0 <= abstime->tv_nsec && abstime->tv_nsec < 1000000000))
return EINVAL; return EINVAL;
if (MUTEX_TYPE(mutex->_word) != PTHREAD_MUTEX_NORMAL)
nsync_panic_("pthread cond needs normal mutex\n"); // look at the mutex argument
uint64_t muword = atomic_load_explicit(&mutex->_word, memory_order_relaxed);
// check that mutex is held by caller
if (MUTEX_TYPE(muword) == PTHREAD_MUTEX_ERRORCHECK &&
MUTEX_OWNER(muword) != gettid())
return EPERM;
// if condition variable is shared then mutex must be too
if (cond->_pshared)
if (MUTEX_PSHARED(muword) != PTHREAD_PROCESS_SHARED)
return EINVAL;
#if PTHREAD_USE_NSYNC
// favor *NSYNC if this is a process private condition variable
// if using Mike Burrows' code isn't possible, use a naive impl
if (!cond->_pshared)
return nsync_cv_wait_with_deadline( return nsync_cv_wait_with_deadline(
(nsync_cv *)cond, (nsync_mu *)mutex, (nsync_cv *)cond, (nsync_mu *)mutex,
abstime ? *abstime : nsync_time_no_deadline, 0); abstime ? *abstime : nsync_time_no_deadline, 0);
#endif
errno_t err;
BEGIN_CANCELATION_POINT;
err = pthread_cond_timedwait_impl(cond, mutex, abstime);
END_CANCELATION_POINT;
return err;
} }

View file

@ -183,7 +183,8 @@ sem_t *sem_open(const char *name, int oflag, ...) {
#if 0 #if 0
if (IsXnuSilicon()) { if (IsXnuSilicon()) {
long kernel; long kernel;
if (!(sem = calloc(1, sizeof(sem_t)))) return SEM_FAILED; if (!(sem = calloc(1, sizeof(sem_t))))
return SEM_FAILED;
sem->sem_magic = SEM_MAGIC_KERNEL; sem->sem_magic = SEM_MAGIC_KERNEL;
kernel = _sysret(__syslib->__sem_open(name, oflag, mode, value)); kernel = _sysret(__syslib->__sem_open(name, oflag, mode, value));
if (kernel == -1) { if (kernel == -1) {

View file

@ -81,13 +81,15 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) {
return ecanceled(); return ecanceled();
} }
rc = _sysret(__syslib->__sem_trywait(sem->sem_kernel)); rc = _sysret(__syslib->__sem_trywait(sem->sem_kernel));
if (!rc) return 0; if (!rc)
return 0;
if (errno == EINTR && // if (errno == EINTR && //
_weaken(pthread_testcancel_np) && // _weaken(pthread_testcancel_np) && //
_weaken(pthread_testcancel_np)()) { _weaken(pthread_testcancel_np)()) {
return ecanceled(); return ecanceled();
} }
if (errno != EAGAIN) return -1; if (errno != EAGAIN)
return -1;
errno = e; errno = e;
struct timespec now = timespec_real(); struct timespec now = timespec_real();
if (timespec_cmp(*abstime, now) >= 0) { if (timespec_cmp(*abstime, now) >= 0) {

View file

@ -3,6 +3,7 @@
#define PTHREAD_KEYS_MAX 46 #define PTHREAD_KEYS_MAX 46
#define PTHREAD_STACK_MIN 65536 #define PTHREAD_STACK_MIN 65536
#define PTHREAD_USE_NSYNC 1
#define PTHREAD_DESTRUCTOR_ITERATIONS 4 #define PTHREAD_DESTRUCTOR_ITERATIONS 4
#define PTHREAD_BARRIER_SERIAL_THREAD 31337 #define PTHREAD_BARRIER_SERIAL_THREAD 31337
@ -74,7 +75,15 @@ typedef struct pthread_mutexattr_s {
} pthread_mutexattr_t; } pthread_mutexattr_t;
typedef struct pthread_cond_s { typedef struct pthread_cond_s {
void *_nsync[2]; union {
void *_align;
struct {
uint32_t _nsync;
char _pshared;
};
};
_Atomic(uint32_t) _sequence;
_Atomic(uint32_t) _waiters;
} pthread_cond_t; } pthread_cond_t;
typedef struct pthread_rwlock_s { typedef struct pthread_rwlock_s {

View file

@ -66,7 +66,7 @@ int main() {
if (ready) if (ready)
break; break;
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 100; ++i) {
if (pthread_kill(th, SIGUSR1)) if (pthread_kill(th, SIGUSR1))
_Exit(11); _Exit(11);
if (pthread_kill(th, SIGUSR1)) if (pthread_kill(th, SIGUSR1))