From 5fb10958249a89b2e7f3711fe0eed624086c5336 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 16 Aug 2010 22:55:45 -0400 Subject: [PATCH] Add a unit test for conditions --- test/regress.h | 5 +- test/regress_pthread.c | 162 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 7 deletions(-) diff --git a/test/regress.h b/test/regress.h index 210b79a7..f66b4ce0 100644 --- a/test/regress.h +++ b/test/regress.h @@ -110,8 +110,11 @@ int _test_ai_eq(const struct evutil_addrinfo *ai, const char *sockaddr_port, goto end; \ } while (0) +#define test_timeval_diff_leq(tv1, tv2, diff, tolerance) \ + tt_int_op(abs(timeval_msec_diff((tv1), (tv2)) - diff), <=, tolerance) + #define test_timeval_diff_eq(tv1, tv2, diff) \ - tt_int_op(abs(timeval_msec_diff((tv1), (tv2)) - diff), <=, 50) + test_timeval_diff_leq((tv1), (tv2), (diff), 50) long timeval_msec_diff(const struct timeval *start, const struct timeval *end); diff --git a/test/regress_pthread.c b/test/regress_pthread.c index 4eeef8fe..c918312c 100644 --- a/test/regress_pthread.c +++ b/test/regress_pthread.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef _EVENT_HAVE_PTHREADS #include @@ -69,7 +70,7 @@ struct cond_wait { }; static void -basic_timeout(evutil_socket_t fd, short what, void *arg) +wake_all_timeout(evutil_socket_t fd, short what, void *arg) { struct cond_wait *cw = arg; EVLOCK_LOCK(cw->lock, 0); @@ -78,7 +79,17 @@ basic_timeout(evutil_socket_t fd, short what, void *arg) } +static void +wake_one_timeout(evutil_socket_t fd, short what, void *arg) +{ + struct cond_wait *cw = arg; + EVLOCK_LOCK(cw->lock, 0); + EVTHREAD_COND_SIGNAL(cw->cond); + EVLOCK_UNLOCK(cw->lock, 0); +} + #define NUM_THREADS 100 +#define NUM_ITERATIONS 100 void *count_lock; static int count; @@ -95,13 +106,15 @@ basic_thread(void *arg) assert(cw.lock); assert(cw.cond); - evtimer_assign(&ev, base, basic_timeout, &cw); - for (i = 0; i < 100; i++) { + evtimer_assign(&ev, base, wake_all_timeout, &cw); + for (i = 0; i < NUM_ITERATIONS; i++) { struct timeval tv; evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 3000; EVLOCK_LOCK(cw.lock, 0); - /* we need to make sure that even does not happen before + /* we need to make sure that event does not happen before * we get to wait on the conditional variable */ assert(evtimer_add(&ev, &tv) == 0); @@ -157,14 +170,151 @@ thread_basic(void *arg) THREAD_JOIN(threads[i]); event_del(&ev); + + tt_int_op(count, ==, NUM_THREADS * NUM_ITERATIONS); + EVTHREAD_FREE_LOCK(count_lock, 0); end: ; } +#undef NUM_THREADS +#define NUM_THREADS 10 + +struct alerted_record { + struct cond_wait *cond; + struct timeval delay; + struct timeval alerted_at; + int timed_out; +}; + +static THREAD_FN +wait_for_condition(void *arg) +{ + struct alerted_record *rec = arg; + int r; + + EVLOCK_LOCK(rec->cond->lock, 0); + if (rec->delay.tv_sec || rec->delay.tv_usec) { + r = EVTHREAD_COND_WAIT_TIMED(rec->cond->cond, rec->cond->lock, + &rec->delay); + } else { + r = EVTHREAD_COND_WAIT(rec->cond->cond, rec->cond->lock); + } + EVLOCK_UNLOCK(rec->cond->lock, 0); + + evutil_gettimeofday(&rec->alerted_at, NULL); + if (r == 1) + rec->timed_out = 1; + + THREAD_RETURN(); +} + +static void +thread_conditions_simple(void *arg) +{ + struct timeval tv_signal, tv_timeout, tv_broadcast; + struct alerted_record alerted[NUM_THREADS]; + THREAD_T threads[NUM_THREADS]; + struct cond_wait cond; + int i; + struct timeval launched_at; + struct event wake_one; + struct event wake_all; + struct basic_test_data *data = arg; + struct event_base *base = data->base; + int n_timed_out=0, n_signal=0, n_broadcast=0; + + tv_signal.tv_sec = tv_timeout.tv_sec = tv_broadcast.tv_sec = 0; + tv_signal.tv_usec = 30*1000; + tv_timeout.tv_usec = 150*1000; + tv_broadcast.tv_usec = 500*1000; + + EVTHREAD_ALLOC_LOCK(cond.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + EVTHREAD_ALLOC_COND(cond.cond); + tt_assert(cond.lock); + tt_assert(cond.cond); + for (i = 0; i < NUM_THREADS; ++i) { + memset(&alerted[i], 0, sizeof(struct alerted_record)); + alerted[i].cond = &cond; + } + + /* Threads 5 and 6 will be allowed to time out */ + memcpy(&alerted[5].delay, &tv_timeout, sizeof(tv_timeout)); + memcpy(&alerted[6].delay, &tv_timeout, sizeof(tv_timeout)); + + evtimer_assign(&wake_one, base, wake_one_timeout, &cond); + evtimer_assign(&wake_all, base, wake_all_timeout, &cond); + + evutil_gettimeofday(&launched_at, NULL); + + /* Launch the threads... */ + for (i = 0; i < NUM_THREADS; ++i) { + THREAD_START(threads[i], wait_for_condition, &alerted[i]); + } + + /* Start the timers... */ + tt_int_op(event_add(&wake_one, &tv_signal), ==, 0); + tt_int_op(event_add(&wake_all, &tv_broadcast), ==, 0); + + /* And run for a bit... */ + event_base_dispatch(base); + + /* And wait till the threads are done. */ + for (i = 0; i < NUM_THREADS; ++i) + THREAD_JOIN(threads[i]); + + /* Now, let's see what happened. At least one of 5 or 6 should + * have timed out. */ + n_timed_out = alerted[5].timed_out + alerted[6].timed_out; + tt_int_op(n_timed_out, >=, 1); + tt_int_op(n_timed_out, <=, 2); + + for (i = 0; i < NUM_THREADS; ++i) { + const struct timeval *target_delay; + struct timeval target_time, actual_delay; + if (alerted[i].timed_out) { + TT_BLATHER(("%d looks like a timeout\n", i)); + target_delay = &tv_timeout; + tt_assert(i == 5 || i == 6); + } else if (evutil_timerisset(&alerted[i].alerted_at)) { + long diff1,diff2; + evutil_timersub(&alerted[i].alerted_at, + &launched_at, &actual_delay); + diff1 = timeval_msec_diff(&actual_delay, + &tv_signal); + diff2 = timeval_msec_diff(&actual_delay, + &tv_broadcast); + if (abs(diff1) < abs(diff2)) { + TT_BLATHER(("%d looks like a signal\n", i)); + target_delay = &tv_signal; + ++n_signal; + } else { + TT_BLATHER(("%d looks like a broadcast\n", i)); + target_delay = &tv_broadcast; + ++n_broadcast; + } + } else { + TT_FAIL(("Thread %d never got woken", i)); + continue; + } + evutil_timeradd(target_delay, &launched_at, &target_time); + test_timeval_diff_leq(&target_time, &alerted[i].alerted_at, + 0, 50); + } + tt_int_op(n_broadcast + n_signal + n_timed_out, ==, NUM_THREADS); + tt_int_op(n_signal, ==, 1); + +end: + ; +} + +#define TEST(name) \ + { #name, thread_##name, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE, \ + &basic_setup, NULL } struct testcase_t thread_testcases[] = { - { "basic", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE, - &basic_setup, NULL }, + TEST(basic), + TEST(conditions_simple), END_OF_TESTCASES }; -- 2.40.0