#include <stdint.h>
#include "CUnit/Theory.h"
#include "dds/ddsrt/atomics.h"
#include "dds/ddsrt/cdtors.h"
#include "dds/ddsrt/sync.h"
#include "dds/ddsrt/threads.h"
#include "dds/ddsrt/time.h"
CU_Init(ddsrt_sync)
{
ddsrt_init();
return 0;
}
CU_Clean(ddsrt_sync)
{
ddsrt_fini();
return 0;
}
typedef struct {
ddsrt_atomic_uint32_t cnt;
ddsrt_mutex_t lock;
ddsrt_rwlock_t rwlock;
ddsrt_cond_wctime_t cond;
ddsrt_wctime_t abstime;
} thread_arg_t;
static uint32_t mutex_lock_routine(void *ptr)
{
int res;
thread_arg_t *arg = (thread_arg_t *)ptr;
ddsrt_atomic_inc32(&arg->cnt);
ddsrt_mutex_lock(&arg->lock);
res = ddsrt_atomic_cas32(&arg->cnt, 2UL, 4UL);
ddsrt_mutex_unlock(&arg->lock);
return (uint32_t)res;
}
CU_Test(ddsrt_sync, mutex_lock_conc)
{
dds_return_t ret;
ddsrt_thread_t thr;
ddsrt_threadattr_t attr;
thread_arg_t arg = { .cnt = DDSRT_ATOMIC_UINT32_INIT(0) };
uint32_t res = 0;
ddsrt_mutex_init(&arg.lock);
ddsrt_mutex_lock(&arg.lock);
ddsrt_threadattr_init(&attr);
ret = ddsrt_thread_create(&thr, "mutex_lock_conc", &attr, &mutex_lock_routine, &arg);
CU_ASSERT_EQ (ret, DDS_RETCODE_OK);
while (ddsrt_atomic_ld32(&arg.cnt) == 0)
;
ddsrt_atomic_inc32(&arg.cnt);
ddsrt_mutex_unlock(&arg.lock);
ret = ddsrt_thread_join(thr, &res);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
CU_ASSERT_EQ (res, 1);
CU_ASSERT_EQ (ddsrt_atomic_ld32(&arg.cnt), 4UL);
ddsrt_mutex_destroy(&arg.lock);
}
static uint32_t mutex_trylock_routine(void *ptr)
{
thread_arg_t *arg = (thread_arg_t *)ptr;
if (ddsrt_mutex_trylock(&arg->lock)) {
ddsrt_atomic_inc32(&arg->cnt);
}
return ddsrt_atomic_ld32(&arg->cnt);
}
CU_Test(ddsrt_sync, mutex_trylock)
{
bool locked;
ddsrt_mutex_t lock;
ddsrt_mutex_init(&lock);
locked = ddsrt_mutex_trylock(&lock);
CU_ASSERT_EQ (locked, true);
locked = ddsrt_mutex_trylock (&lock);
CU_ASSERT_EQ (locked, false);
ddsrt_mutex_unlock(&lock);
ddsrt_mutex_destroy(&lock);
}
static uint32_t rwlock_tryread_routine(void *ptr)
{
thread_arg_t *arg = (thread_arg_t *)ptr;
if (ddsrt_rwlock_tryread(&arg->rwlock)) {
ddsrt_atomic_inc32(&arg->cnt);
ddsrt_rwlock_unlock(&arg->rwlock);
}
return ddsrt_atomic_ld32(&arg->cnt);
}
static uint32_t rwlock_trywrite_routine(void *ptr)
{
thread_arg_t *arg = (thread_arg_t *)ptr;
if (ddsrt_rwlock_trywrite(&arg->rwlock)) {
ddsrt_atomic_inc32(&arg->cnt);
ddsrt_rwlock_unlock(&arg->rwlock);
}
return ddsrt_atomic_ld32(&arg->cnt);
}
CU_Test(ddsrt_sync, mutex_trylock_conc)
{
dds_return_t ret;
ddsrt_thread_t thr;
ddsrt_threadattr_t attr;
thread_arg_t arg = { .cnt = DDSRT_ATOMIC_UINT32_INIT(1) };
uint32_t res = 0;
ddsrt_mutex_init(&arg.lock);
ddsrt_mutex_lock(&arg.lock);
ddsrt_threadattr_init(&attr);
ret = ddsrt_thread_create(&thr, "mutex_trylock_conc", &attr, &mutex_trylock_routine, &arg);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
ret = ddsrt_thread_join(thr, &res);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
CU_ASSERT_EQ (res, 1);
ddsrt_mutex_unlock(&arg.lock);
ddsrt_mutex_destroy(&arg.lock);
}
#define READ (1)
#define TRYREAD (2)
#define WRITE (3)
#define TRYWRITE (4)
CU_TheoryDataPoints(ddsrt_sync, rwlock_trylock_conc) = {
CU_DataPoints(uint32_t, READ, READ, WRITE, WRITE),
CU_DataPoints(uint32_t, TRYREAD, TRYWRITE, TRYREAD, TRYWRITE),
CU_DataPoints(uint32_t, 2, 1, 1, 1)
};
CU_Theory((uint32_t lock, uint32_t trylock, uint32_t exp), ddsrt_sync, rwlock_trylock_conc)
{
dds_return_t ret;
ddsrt_thread_t thr;
ddsrt_threadattr_t attr;
ddsrt_thread_routine_t func;
thread_arg_t arg = { .cnt = DDSRT_ATOMIC_UINT32_INIT(1) };
uint32_t res = 0;
ddsrt_rwlock_init(&arg.rwlock);
if (lock == READ) {
ddsrt_rwlock_read(&arg.rwlock);
} else {
ddsrt_rwlock_write(&arg.rwlock);
}
if (trylock == TRYREAD) {
func = &rwlock_tryread_routine;
} else {
func = &rwlock_trywrite_routine;
}
ddsrt_threadattr_init(&attr);
ret = ddsrt_thread_create(&thr, "rwlock_trylock_conc", &attr, func, &arg);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
ret = ddsrt_thread_join(thr, &res);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
ddsrt_rwlock_unlock(&arg.rwlock);
CU_ASSERT_EQ (res, exp);
ddsrt_rwlock_destroy(&arg.rwlock);
}
static ddsrt_atomic_uint32_t once_count = DDSRT_ATOMIC_UINT32_INIT(0);
static ddsrt_once_t once_control = DDSRT_ONCE_INIT;
#define ONCE_THREADS (8)
static void do_once(void)
{
ddsrt_atomic_inc32(&once_count);
}
static uint32_t once_routine(void *ptr)
{
(void)ptr;
while (ddsrt_atomic_ld32(&once_count) == 0)
;
ddsrt_once(&once_control, &do_once);
return ddsrt_atomic_ld32(&once_count);
}
CU_Test(ddsrt_sync, once_conc)
{
dds_return_t ret;
ddsrt_thread_t thrs[ONCE_THREADS];
ddsrt_threadattr_t attr;
uint32_t res;
char buf[32];
ddsrt_threadattr_init(&attr);
for (int i = 0; i < ONCE_THREADS; i++) {
(void)snprintf(buf, sizeof(buf), "once_conc%d", i + 1);
ret = ddsrt_thread_create(&thrs[i], buf, &attr, &once_routine, NULL);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
}
ddsrt_atomic_st32(&once_count, 1);
for (int i = 0; i < ONCE_THREADS; i++) {
res = 0;
ret = ddsrt_thread_join(thrs[i], &res);
CU_ASSERT_EQ_FATAL (ret, DDS_RETCODE_OK);
CU_ASSERT_EQ (res, 2);
}
ddsrt_once(&once_control, &do_once);
CU_ASSERT_EQ (ddsrt_atomic_ld32(&once_count), 2);
}
#define WAITUNTIL_ARG(tb_) \
struct waituntil_##tb_##_arg { \
ddsrt_atomic_uint32_t flag; \
ddsrt_mutex_t lock; \
ddsrt_cond_t sync_cond; \
ddsrt_cond_##tb_##_t cond; \
ddsrt_##tb_##_t abstimeout; \
}
WAITUNTIL_ARG(wctime);
WAITUNTIL_ARG(mtime);
WAITUNTIL_ARG(etime);
static uint32_t waituntil_check (const char *tname, int64_t after, int64_t abstimeout, uint32_t cnt)
{
fprintf (stderr, "%s: expected to wait until %"PRId64" ns\n", tname, abstimeout);
fprintf (stderr, "%s: waited until %"PRId64" ns = exp + %"PRId64" ns\n", tname, after, after - abstimeout);
fprintf (stderr, "%s: woke up %"PRIu32" times\n", tname, cnt);
uint32_t res = 0;
if (after > abstimeout - DDS_MSECS (10))
{
res = cnt < 3;
}
return res;
}
#define WAITUNTIL_THREAD(tb_, tname_) \
static uint32_t waituntil_##tb_##_routine (void *ptr) \
{ \
struct waituntil_##tb_##_arg * const arg = ptr; \
uint32_t cnt = 0; \
ddsrt_mutex_lock (&arg->lock); \
ddsrt_atomic_st32 (&arg->flag, 1); \
ddsrt_cond_signal (&arg->sync_cond); \
while (ddsrt_cond_##tb_##_waituntil (&arg->cond, &arg->lock, arg->abstimeout)) \
cnt++; \
ddsrt_##tb_##_t after = ddsrt_time_##tname_ (); \
ddsrt_mutex_unlock (&arg->lock); \
return waituntil_check (#tname_, after.v, arg->abstimeout.v, cnt); \
}
WAITUNTIL_THREAD(wctime, wallclock)
WAITUNTIL_THREAD(mtime, monotonic)
WAITUNTIL_THREAD(etime, elapsed)
#define COND_WAITUNTIL_DELAY DDS_MSECS(100)
#define WAITUNTIL_TEST(tb_, tname_) \
static void do_test_cond_waituntil_##tb_ (void) \
{ \
ddsrt_thread_t thr; \
ddsrt_threadattr_t tattr; \
ddsrt_threadattr_init (&tattr); \
struct waituntil_##tb_##_arg arg = { .flag = DDSRT_ATOMIC_UINT32_INIT (0) }; \
ddsrt_mutex_init (&arg.lock); \
ddsrt_cond_init (&arg.sync_cond); \
ddsrt_cond_##tb_##_init (&arg.cond); \
arg.abstimeout = ddsrt_##tb_##_add_duration (ddsrt_time_##tname_ (), COND_WAITUNTIL_DELAY); \
ddsrt_mutex_lock (&arg.lock); \
dds_return_t rc = ddsrt_thread_create (&thr, "cond_waituntil", &tattr, &waituntil_##tb_##_routine, &arg); \
CU_ASSERT_EQ_FATAL (rc, DDS_RETCODE_OK); \
while (ddsrt_atomic_ld32 (&arg.flag) == 0) \
ddsrt_cond_wait (&arg.sync_cond, &arg.lock); \
ddsrt_mutex_unlock (&arg.lock); \
dds_sleepfor (2 * COND_WAITUNTIL_DELAY); \
ddsrt_cond_##tb_##_signal (&arg.cond); \
uint32_t res = 0; \
rc = ddsrt_thread_join (thr, &res); \
CU_ASSERT_EQ_FATAL (rc, DDS_RETCODE_OK); \
CU_ASSERT_EQ (res, 1); \
}
WAITUNTIL_TEST(wctime, wallclock)
WAITUNTIL_TEST(mtime, monotonic)
WAITUNTIL_TEST(etime, elapsed)
CU_Test(ddsrt_sync, cond_waituntil_wctime)
{
do_test_cond_waituntil_wctime ();
}
CU_Test(ddsrt_sync, cond_waituntil_mtime)
{
do_test_cond_waituntil_mtime ();
}
CU_Test(ddsrt_sync, cond_waituntil_etime)
{
do_test_cond_waituntil_etime ();
}