#include "namespace.h"
#include <machine/tls.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <limits.h>
#include "un-namespace.h"
#include "thr_private.h"
#ifdef _PTHREADS_DEBUGGING
#include <stdio.h>
#include <stdarg.h>
#include <sys/file.h>
#endif
#define cpu_ccfence() __asm __volatile("" : : : "memory")
umtx_t _cond_static_lock;
#ifdef _PTHREADS_DEBUGGING
static
void
cond_log(const char *ctl, ...)
{
char buf[256];
va_list va;
size_t len;
va_start(va, ctl);
len = vsnprintf(buf, sizeof(buf), ctl, va);
va_end(va);
_thr_log(buf, len);
}
#else
static __inline
void
cond_log(const char *ctl __unused, ...)
{
}
#endif
int __pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int __pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
static int cond_wait_common(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime, int cancel);
static int cond_signal_common(pthread_cond_t *cond, int broadcast);
static int
cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr)
{
pthread_cond_t pcond;
int rval = 0;
pcond = __malloc(sizeof(struct __pthread_cond_s));
if (pcond == NULL) {
rval = ENOMEM;
} else {
_thr_umtx_init(&pcond->c_lock);
if (cond_attr == NULL || *cond_attr == NULL) {
pcond->c_pshared = 0;
pcond->c_clockid = CLOCK_REALTIME;
} else {
pcond->c_pshared = (*cond_attr)->c_pshared;
pcond->c_clockid = (*cond_attr)->c_clockid;
}
TAILQ_INIT(&pcond->c_waitlist);
*cond = pcond;
}
return (rval);
}
#if 0
void
_cond_reinit(pthread_cond_t cond)
{
if (cond) {
_thr_umtx_init(&cond->c_lock);
#if 0
cond->c_pshared = 0;
cond->c_clockid = CLOCK_REALTIME;
#endif
TAILQ_INIT(&cond->c_waitlist);
}
}
#endif
static int
init_static(pthread_t thread, pthread_cond_t *cond)
{
int ret;
THR_LOCK_ACQUIRE(thread, &_cond_static_lock);
if (*cond == NULL)
ret = cond_init(cond, NULL);
else
ret = 0;
THR_LOCK_RELEASE(thread, &_cond_static_lock);
return (ret);
}
int
_pthread_cond_init(pthread_cond_t * __restrict cond,
const pthread_condattr_t * __restrict cond_attr)
{
*cond = NULL;
return cond_init(cond, cond_attr);
}
int
_pthread_cond_destroy(pthread_cond_t *cond)
{
pthread_cond_t cv;
pthread_t curthread = tls_get_curthread();
int rval = 0;
if (cond == NULL) {
rval = EINVAL;
} else if (*cond == NULL) {
rval = 0;
} else {
THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock);
if (TAILQ_FIRST(&(*cond)->c_waitlist)) {
THR_LOCK_RELEASE(curthread, &(*cond)->c_lock);
return (EBUSY);
}
cv = *cond;
*cond = NULL;
THR_LOCK_RELEASE(curthread, &cv->c_lock);
__free(cv);
}
return (rval);
}
struct cond_cancel_info {
TAILQ_ENTRY(cond_cancel_info) entry;
pthread_mutex_t *mutex;
pthread_cond_t *cond;
int count;
int queued;
};
static void
cond_cancel_handler(void *arg)
{
pthread_t curthread = tls_get_curthread();
struct cond_cancel_info *info = (struct cond_cancel_info *)arg;
pthread_cond_t cv;
cv = *info->cond;
THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
cond_log("cond_cancel %p\n", cv);
if (info->queued) {
info->queued = 0;
cond_log("cond_cancel %p: info %p\n", cv, info);
TAILQ_REMOVE(&cv->c_waitlist, info, entry);
_thr_umtx_wake(&info->queued, 0);
}
THR_LOCK_RELEASE(curthread, &cv->c_lock);
}
static int
cond_wait_common(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime, int cancel)
{
pthread_t curthread = tls_get_curthread();
struct timespec ts, ts2, *tsp;
struct cond_cancel_info info;
pthread_cond_t cv;
int oldcancel;
int ret;
cond_log("cond_wait_common %p on mutex %p info %p\n",
*cond, *mutex, &info);
if (__predict_false(*cond == NULL &&
(ret = init_static(curthread, cond)) != 0)) {
cond_log("cond_wait_common %p (failedA %d)\n", *cond, ret);
return (ret);
}
cv = *cond;
THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
ret = _mutex_cv_unlock(mutex, &info.count);
if (ret) {
cond_log("cond_wait_common %p (failedB %d)\n", cv, ret);
THR_LOCK_RELEASE(curthread, &cv->c_lock);
return ret;
}
cpu_ccfence();
info.mutex = mutex;
info.cond = cond;
info.queued = 1;
TAILQ_INSERT_TAIL(&cv->c_waitlist, &info, entry);
while (info.queued) {
THR_LOCK_RELEASE(curthread, &cv->c_lock);
if (abstime != NULL) {
clock_gettime(cv->c_clockid, &ts);
timespecsub(abstime, &ts, &ts2);
tsp = &ts2;
} else {
tsp = NULL;
}
if (cancel) {
THR_CLEANUP_PUSH(curthread, cond_cancel_handler, &info);
oldcancel = _thr_cancel_enter(curthread);
ret = _thr_umtx_wait(&info.queued, 1, tsp,
cv->c_clockid);
_thr_cancel_leave(curthread, oldcancel);
THR_CLEANUP_POP(curthread, 0);
} else {
ret = _thr_umtx_wait(&info.queued, 1, tsp,
cv->c_clockid);
}
THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
if (abstime != NULL && ret == ETIMEDOUT)
break;
cpu_ccfence();
}
if (info.queued) {
info.queued = 0;
TAILQ_REMOVE(&cv->c_waitlist, &info, entry);
ret = ETIMEDOUT;
} else {
ret = 0;
}
THR_LOCK_RELEASE(curthread, &cv->c_lock);
cond_log("cond_wait_common %p (doneA)\n", cv);
_mutex_cv_lock(mutex, info.count);
if (ret)
cond_log("cond_wait_common %p (failed %d)\n", cv, ret);
else
cond_log("cond_wait_common %p (doneB)\n", cv);
return (ret);
}
int
_pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
return (cond_wait_common(cond, mutex, NULL, 0));
}
int
__pthread_cond_wait(pthread_cond_t * __restrict cond,
pthread_mutex_t * __restrict mutex)
{
return (cond_wait_common(cond, mutex, NULL, 1));
}
int
_pthread_cond_timedwait(pthread_cond_t * __restrict cond,
pthread_mutex_t * __restrict mutex,
const struct timespec * __restrict abstime)
{
if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
abstime->tv_nsec >= 1000000000)
return (EINVAL);
return (cond_wait_common(cond, mutex, abstime, 0));
}
int
__pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime)
{
if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
abstime->tv_nsec >= 1000000000)
return (EINVAL);
return (cond_wait_common(cond, mutex, abstime, 1));
}
static int
cond_signal_common(pthread_cond_t *cond, int broadcast)
{
pthread_t curthread = tls_get_curthread();
struct cond_cancel_info *info;
pthread_cond_t cv;
int ret = 0;
cond_log("cond_signal_common %p broad=%d\n", *cond, broadcast);
if (__predict_false(*cond == NULL &&
(ret = init_static(curthread, cond)) != 0)) {
cond_log("cond_signal_common %p (failedA %d)\n", *cond, ret);
return (ret);
}
cv = *cond;
THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
while ((info = TAILQ_FIRST(&cv->c_waitlist)) != NULL) {
info->queued = 0;
TAILQ_REMOVE(&cv->c_waitlist, info, entry);
cond_log("cond_signal_common %p: wakeup %p\n", *cond, info);
_thr_umtx_wake(&info->queued, 0);
if (broadcast == 0)
break;
}
THR_LOCK_RELEASE(curthread, &cv->c_lock);
if (ret)
cond_log("cond_signal_common %p (failedB %d)\n", *cond, ret);
else
cond_log("cond_signal_common %p (done)\n", *cond);
return (ret);
}
int
_pthread_cond_signal(pthread_cond_t * cond)
{
return (cond_signal_common(cond, 0));
}
int
_pthread_cond_broadcast(pthread_cond_t * cond)
{
return (cond_signal_common(cond, 1));
}
__strong_reference(__pthread_cond_wait, pthread_cond_wait);
__strong_reference(__pthread_cond_timedwait, pthread_cond_timedwait);
__strong_reference(_pthread_cond_init, pthread_cond_init);
__strong_reference(_pthread_cond_destroy, pthread_cond_destroy);
__strong_reference(_pthread_cond_signal, pthread_cond_signal);
__strong_reference(_pthread_cond_broadcast, pthread_cond_broadcast);