blob: a271955611aa0f8268cc1e55845c17bc64e1a616 [file] [log] [blame]
/* Copyright (c) 2016 Google, Inc.
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details. */
#include <utest/utest.h>
#include <parlib/uthread.h>
#include <pthread.h>
#include <time.h>
TEST_SUITE("CV");
/* <--- Begin definition of test cases ---> */
struct common_args {
uth_cond_var_t *cv;
uth_mutex_t *mtx;
bool flag;
unsigned int wait_sleep;
unsigned int sig_sleep;
};
#define PTH_TEST_TRUE (void*)1
bool test_signal_no_wait(void)
{
uth_cond_var_t *cv = uth_cond_var_alloc();
uth_cond_var_broadcast(cv);
uth_cond_var_signal(cv);
pthread_yield();
uth_cond_var_free(cv);
return TRUE;
}
void *__cv_signaller(void *arg)
{
struct common_args *args = (struct common_args*)arg;
uthread_usleep(args->sig_sleep);
uth_mutex_lock(args->mtx);
args->flag = TRUE;
uth_mutex_unlock(args->mtx);
/* We can actually signal outside the mutex if we want. Remember the
* invariant: whenever the flag is set from FALSE to TRUE, all waiters
* that saw FALSE are on the CV's waitqueue. That's true even after we
* unlock the mutex. */
uth_cond_var_signal(args->cv);
return PTH_TEST_TRUE;
}
void *__cv_broadcaster(void *arg)
{
struct common_args *args = (struct common_args*)arg;
uthread_usleep(args->sig_sleep);
uth_mutex_lock(args->mtx);
args->flag = TRUE;
uth_mutex_unlock(args->mtx);
/* We can actually signal outside the mutex if we want. Remember the
* invariant: whenever the flag is set from FALSE to TRUE, all waiters
* that saw FALSE are on the CV's waitqueue. That's true even after we
* unlock the mutex. */
uth_cond_var_broadcast(args->cv);
return PTH_TEST_TRUE;
}
void *__cv_waiter(void *arg)
{
struct common_args *args = (struct common_args*)arg;
uthread_usleep(args->wait_sleep);
uth_mutex_lock(args->mtx);
while (!args->flag)
uth_cond_var_wait(args->cv, args->mtx);
UT_ASSERT(args->flag);
uth_mutex_unlock(args->mtx);
return PTH_TEST_TRUE;
}
/* Basic one signaller, one receiver. We want to vary the amount of time the
* sender and receiver delays, starting with (1ms, 0ms) and ending with (0ms,
* 1ms). At each extreme, such as with the sender waiting 1ms, the
* receiver/waiter should hit the "check and wait" point well before the
* sender/signaller hits the "change state and signal" point. */
bool test_signal(void)
{
struct common_args local_a, *args = &local_a;
pthread_t signaller, waiter;
void *sig_join, *wait_join;
int ret;
static uth_mutex_t static_mtx = UTH_MUTEX_INIT;
static uth_cond_var_t static_cv = UTH_COND_VAR_INIT;
/* Also testing the static initializers. Note we never free these. */
args->cv = &static_cv;
args->mtx = &static_mtx;
for (int i = 0; i < 1000; i += 10) {
args->flag = FALSE;
args->wait_sleep = i;
args->sig_sleep = 1000 - i;
ret = pthread_create(&waiter, 0, __cv_waiter, args);
UT_ASSERT(!ret);
ret = pthread_create(&signaller, 0, __cv_signaller, args);
UT_ASSERT(!ret);
ret = pthread_join(waiter, &wait_join);
UT_ASSERT(!ret);
ret = pthread_join(signaller, &sig_join);
UT_ASSERT(!ret);
UT_ASSERT_M("Waiter Failed", wait_join == PTH_TEST_TRUE);
UT_ASSERT_M("Signaller Failed", sig_join == PTH_TEST_TRUE);
}
return TRUE;
}
bool test_broadcast(void)
{
#define NR_WAITERS 20
struct common_args local_a, *args = &local_a;
pthread_t bcaster, waiters[NR_WAITERS];
void *bcast_join, *wait_joins[NR_WAITERS];
int ret;
args->cv = uth_cond_var_alloc();
args->mtx = uth_mutex_alloc();
args->flag = FALSE;
args->wait_sleep = 0;
args->sig_sleep = 1000;
for (int i = 0; i < NR_WAITERS; i++) {
ret = pthread_create(&waiters[i], 0, __cv_waiter, args);
UT_ASSERT(!ret);
}
ret = pthread_create(&bcaster, 0, __cv_broadcaster, args);
UT_ASSERT(!ret);
ret = pthread_join(bcaster, &bcast_join);
UT_ASSERT(!ret);
UT_ASSERT_M("Broadcaster Failed", bcast_join == PTH_TEST_TRUE);
for (int i = 0; i < NR_WAITERS; i++) {
ret = pthread_join(waiters[i], &wait_joins[i]);
UT_ASSERT(!ret);
UT_ASSERT_M("Waiter Failed", wait_joins[i] == PTH_TEST_TRUE);
}
uth_cond_var_free(args->cv);
uth_mutex_free(args->mtx);
return TRUE;
}
static void *__cv_signaller_no_mutex(void *arg)
{
struct common_args *args = (struct common_args*)arg;
uthread_usleep(args->sig_sleep);
uth_cond_var_lock(args->cv);
args->flag = TRUE;
__uth_cond_var_signal_and_unlock(args->cv);
return PTH_TEST_TRUE;
}
static void *__cv_broadcaster_no_mutex(void *arg)
{
struct common_args *args = (struct common_args*)arg;
uthread_usleep(args->sig_sleep);
uth_cond_var_lock(args->cv);
args->flag = TRUE;
__uth_cond_var_broadcast_and_unlock(args->cv);
return PTH_TEST_TRUE;
}
static void *__cv_waiter_no_mutex(void *arg)
{
struct common_args *args = (struct common_args*)arg;
uthread_usleep(args->wait_sleep);
uth_cond_var_lock(args->cv);
while (!args->flag)
uth_cond_var_wait(args->cv, NULL);
UT_ASSERT(args->flag);
uth_cond_var_unlock(args->cv);
return PTH_TEST_TRUE;
}
bool test_signal_no_mutex(void)
{
struct common_args local_a, *args = &local_a;
pthread_t signaller, waiter;
void *sig_join, *wait_join;
int ret;
static uth_cond_var_t static_cv = UTH_COND_VAR_INIT;
args->cv = &static_cv;
args->mtx = NULL;
for (int i = 0; i < 1000; i += 10) {
args->flag = FALSE;
args->wait_sleep = i;
args->sig_sleep = 1000 - i;
ret = pthread_create(&waiter, 0, __cv_waiter_no_mutex, args);
UT_ASSERT(!ret);
ret = pthread_create(&signaller, 0, __cv_signaller_no_mutex,
args);
UT_ASSERT(!ret);
ret = pthread_join(waiter, &wait_join);
UT_ASSERT(!ret);
ret = pthread_join(signaller, &sig_join);
UT_ASSERT(!ret);
UT_ASSERT_M("Waiter Failed", wait_join == PTH_TEST_TRUE);
UT_ASSERT_M("Signaller Failed", sig_join == PTH_TEST_TRUE);
}
return TRUE;
}
bool test_broadcast_no_mutex(void)
{
#define NR_WAITERS 20
struct common_args local_a, *args = &local_a;
pthread_t bcaster, waiters[NR_WAITERS];
void *bcast_join, *wait_joins[NR_WAITERS];
int ret;
args->cv = uth_cond_var_alloc();
args->mtx = NULL;
args->flag = FALSE;
args->wait_sleep = 0;
args->sig_sleep = 1000;
for (int i = 0; i < NR_WAITERS; i++) {
ret = pthread_create(&waiters[i], 0, __cv_waiter_no_mutex,
args);
UT_ASSERT(!ret);
}
ret = pthread_create(&bcaster, 0, __cv_broadcaster_no_mutex, args);
UT_ASSERT(!ret);
ret = pthread_join(bcaster, &bcast_join);
UT_ASSERT(!ret);
UT_ASSERT_M("Broadcaster Failed", bcast_join == PTH_TEST_TRUE);
for (int i = 0; i < NR_WAITERS; i++) {
ret = pthread_join(waiters[i], &wait_joins[i]);
UT_ASSERT(!ret);
UT_ASSERT_M("Waiter Failed", wait_joins[i] == PTH_TEST_TRUE);
}
uth_cond_var_free(args->cv);
return TRUE;
}
static bool __test_recurse(struct uth_recurse_mutex *r_mtx)
{
bool test;
uth_recurse_mutex_lock(r_mtx);
/* should be one lock deep */
UT_ASSERT(r_mtx->count == 1);
UT_ASSERT(r_mtx->lockholder == current_uthread);
test = uth_recurse_mutex_trylock(r_mtx);
UT_ASSERT_M("Failed to re(try)lock our owned mutex!", test);
/* should be two locks deep */
UT_ASSERT(r_mtx->count == 2);
UT_ASSERT(r_mtx->lockholder == current_uthread);
uth_recurse_mutex_lock(r_mtx);
/* should be three locks deep */
UT_ASSERT(r_mtx->count == 3);
UT_ASSERT(r_mtx->lockholder == current_uthread);
uth_recurse_mutex_unlock(r_mtx);
/* should be two locks deep */
UT_ASSERT(r_mtx->count == 2);
UT_ASSERT(r_mtx->lockholder == current_uthread);
uth_recurse_mutex_unlock(r_mtx);
/* should be one lock deep */
UT_ASSERT(r_mtx->count == 1);
UT_ASSERT(r_mtx->lockholder == current_uthread);
uth_recurse_mutex_unlock(r_mtx);
/* should be unlocked */
UT_ASSERT(r_mtx->count == 0);
UT_ASSERT(!r_mtx->lockholder);
return TRUE;
}
bool test_recurse(void)
{
uth_recurse_mutex_t *r_mtx;
r_mtx = uth_recurse_mutex_alloc();
UT_ASSERT(r_mtx);
if (!__test_recurse(r_mtx))
return FALSE;
uth_recurse_mutex_free(r_mtx);
return TRUE;
}
bool test_recurse_static(void)
{
static uth_recurse_mutex_t static_recurse_mtx = UTH_RECURSE_MUTEX_INIT;
return __test_recurse(&static_recurse_mtx);
}
static bool __test_semaphore(struct uth_semaphore *sem, int count)
{
bool can_down;
UT_ASSERT(count > 2); /* for our own use */
UT_ASSERT(sem->count == count);
/* should be able to down it count times without blocking */
for (int i = 0; i < count; i++) {
uth_semaphore_down(sem);
UT_ASSERT(sem->count == count - (i + 1));
}
/* shouldn't be able to down, since we grabbed them all */
can_down = uth_semaphore_trydown(sem);
UT_ASSERT(!can_down);
UT_ASSERT(sem->count == 0);
uth_semaphore_up(sem);
UT_ASSERT(sem->count == 1);
can_down = uth_semaphore_trydown(sem);
UT_ASSERT(can_down);
UT_ASSERT(sem->count == 0);
for (int i = 0; i < count; i++) {
uth_semaphore_up(sem);
UT_ASSERT(sem->count == i + 1);
}
return TRUE;
}
bool test_semaphore(void)
{
uth_semaphore_t *sem;
sem = uth_semaphore_alloc(5);
UT_ASSERT(sem);
if (!__test_semaphore(sem, 5))
return FALSE;
uth_semaphore_free(sem);
return TRUE;
}
bool test_semaphore_static(void)
{
static uth_semaphore_t static_semaphore = UTH_SEMAPHORE_INIT(5);
return __test_semaphore(&static_semaphore, 5);
}
bool test_semaphore_timeout(void)
{
static uth_semaphore_t sem = UTH_SEMAPHORE_INIT(1);
struct timespec timeout[1];
int ret;
bool got_it;
ret = clock_gettime(CLOCK_REALTIME, timeout);
UT_ASSERT(!ret);
timeout->tv_nsec += 500000000;
got_it = uth_semaphore_timed_down(&sem, timeout);
UT_ASSERT(got_it);
/* Second time we still hold the sem and would block and should time
* out. */
UT_ASSERT(sem.count == 0);
ret = clock_gettime(CLOCK_REALTIME, timeout);
UT_ASSERT(!ret);
timeout->tv_nsec += 500000000;
got_it = uth_semaphore_timed_down(&sem, timeout);
UT_ASSERT(!got_it);
return TRUE;
}
bool test_cv_timeout(void)
{
static uth_mutex_t mtx = UTH_MUTEX_INIT;
static uth_cond_var_t cv = UTH_COND_VAR_INIT;
struct timespec timeout[1];
int ret;
bool was_signalled;
uth_mutex_lock(&mtx);
ret = clock_gettime(CLOCK_REALTIME, timeout);
UT_ASSERT(!ret);
timeout->tv_nsec += 500000000;
was_signalled = uth_cond_var_timed_wait(&cv, &mtx, timeout);
UT_ASSERT(!was_signalled);
UT_ASSERT(mtx.count == 0); /* semaphore's count variable */
uth_mutex_unlock(&mtx);
UT_ASSERT(mtx.count == 1);
return TRUE;
}
bool test_cv_recurse_timeout(void)
{
static uth_recurse_mutex_t r_mtx = UTH_RECURSE_MUTEX_INIT;
static uth_cond_var_t cv = UTH_COND_VAR_INIT;
struct timespec timeout[1];
int ret;
bool was_signalled;
/* Get three-deep locks, make sure the bookkeeping is right */
uth_recurse_mutex_lock(&r_mtx);
uth_recurse_mutex_lock(&r_mtx);
uth_recurse_mutex_lock(&r_mtx);
UT_ASSERT(r_mtx.count == 3);
UT_ASSERT(r_mtx.mtx.count == 0);
ret = clock_gettime(CLOCK_REALTIME, timeout);
UT_ASSERT(!ret);
timeout->tv_nsec += 500000000;
was_signalled = uth_cond_var_timed_wait_recurse(&cv, &r_mtx, timeout);
UT_ASSERT(!was_signalled);
UT_ASSERT(r_mtx.count == 3);
UT_ASSERT(r_mtx.mtx.count == 0);
/* Unlock our three locks, then make sure the semaphore/mtx is unlocked.
*/
uth_recurse_mutex_unlock(&r_mtx);
uth_recurse_mutex_unlock(&r_mtx);
uth_recurse_mutex_unlock(&r_mtx);
UT_ASSERT(r_mtx.count == 0);
UT_ASSERT(r_mtx.mtx.count == 1);
return TRUE;
}
bool test_cv_timeout_no_mutex(void)
{
static uth_cond_var_t cv = UTH_COND_VAR_INIT;
struct timespec timeout[1];
int ret;
bool was_signalled;
uth_cond_var_lock(&cv);
ret = clock_gettime(CLOCK_REALTIME, timeout);
UT_ASSERT(!ret);
timeout->tv_nsec += 500000000;
was_signalled = uth_cond_var_timed_wait(&cv, NULL, timeout);
UT_ASSERT(!was_signalled);
uth_cond_var_unlock(&cv);
return TRUE;
}
static uth_rwlock_t rwl = UTH_RWLOCK_INIT;
static int rw_value;
static void *rw_reader(void *arg)
{
int val;
uth_rwlock_rdlock(&rwl);
if (!rw_value) {
/* We won, let's wait a while to let the writer block (racy) */
uthread_usleep(5000);
}
uth_rwlock_unlock(&rwl);
while (1) {
uth_rwlock_rdlock(&rwl);
val = rw_value;
uth_rwlock_unlock(&rwl);
if (val)
break;
uthread_usleep(1000);
}
return 0;
}
static void *rw_writer(void *arg)
{
/* Let the readers get a head start */
uthread_usleep(1000);
/* This might fail, but can't hurt to try it */
if (uth_rwlock_try_wrlock(&rwl))
uth_rwlock_unlock(&rwl);
/* Eventually we'll get the lock */
uth_rwlock_wrlock(&rwl);
rw_value = 1;
uth_rwlock_unlock(&rwl);
return 0;
}
bool test_rwlock(void)
{
#define NR_THREADS 3
struct uth_join_request joinees[NR_THREADS];
void *retvals[NR_THREADS];
joinees[0].uth = uthread_create(rw_reader, NULL);
joinees[1].uth = uthread_create(rw_reader, NULL);
joinees[2].uth = uthread_create(rw_writer, NULL);
for (int i = 0; i < NR_THREADS; i++)
joinees[i].retval_loc = &retvals[i];
uthread_join_arr(joinees, NR_THREADS);
for (int i = 0; i < NR_THREADS; i++)
UT_ASSERT(retvals[i] == 0);
return TRUE;
}
/* <--- End definition of test cases ---> */
struct utest utests[] = {
UTEST_REG(signal_no_wait),
UTEST_REG(signal),
UTEST_REG(broadcast),
UTEST_REG(signal_no_mutex),
UTEST_REG(broadcast_no_mutex),
UTEST_REG(recurse),
UTEST_REG(recurse_static),
UTEST_REG(semaphore),
UTEST_REG(semaphore_static),
UTEST_REG(semaphore_timeout),
UTEST_REG(cv_timeout),
UTEST_REG(cv_timeout_no_mutex),
UTEST_REG(cv_recurse_timeout),
UTEST_REG(rwlock),
};
int num_utests = sizeof(utests) / sizeof(struct utest);
int main(int argc, char *argv[])
{
char **whitelist = &argv[1];
int whitelist_len = argc - 1;
RUN_TEST_SUITE(utests, num_utests, whitelist, whitelist_len);
}