diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:02:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:02:34 +0000 |
commit | fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb (patch) | |
tree | a7bde6111c84ea64619656a38fba50909fa0bf60 /libevent/test/regress_thread.c | |
parent | Initial commit. (diff) | |
download | lldpd-fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb.tar.xz lldpd-fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb.zip |
Adding upstream version 1.0.18.upstream/1.0.18upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libevent/test/regress_thread.c')
-rw-r--r-- | libevent/test/regress_thread.c | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/libevent/test/regress_thread.c b/libevent/test/regress_thread.c new file mode 100644 index 0000000..1e0ce41 --- /dev/null +++ b/libevent/test/regress_thread.c @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +/* The old tests here need assertions to work. */ +#undef NDEBUG + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef EVENT__HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef EVENT__HAVE_PTHREADS +#include <pthread.h> +#elif defined(_WIN32) +#include <process.h> +#endif +#include <assert.h> +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <time.h> + +#include "sys/queue.h" + +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/thread.h" +#include "event2/util.h" +#include "evthread-internal.h" +#include "event-internal.h" +#include "defer-internal.h" +#include "regress.h" +#include "tinytest_macros.h" +#include "time-internal.h" +#include "regress_thread.h" + +struct cond_wait { + void *lock; + void *cond; +}; + +static void +wake_all_timeout(evutil_socket_t fd, short what, void *arg) +{ + struct cond_wait *cw = arg; + EVLOCK_LOCK(cw->lock, 0); + EVTHREAD_COND_BROADCAST(cw->cond); + EVLOCK_UNLOCK(cw->lock, 0); + +} + +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; + +static THREAD_FN +basic_thread(void *arg) +{ + struct cond_wait cw; + struct event_base *base = arg; + struct event ev; + int i = 0; + + EVTHREAD_ALLOC_LOCK(cw.lock, 0); + EVTHREAD_ALLOC_COND(cw.cond); + assert(cw.lock); + assert(cw.cond); + + 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 event does not happen before + * we get to wait on the conditional variable */ + assert(evtimer_add(&ev, &tv) == 0); + + assert(EVTHREAD_COND_WAIT(cw.cond, cw.lock) == 0); + EVLOCK_UNLOCK(cw.lock, 0); + + EVLOCK_LOCK(count_lock, 0); + ++count; + EVLOCK_UNLOCK(count_lock, 0); + } + + /* exit the loop only if all threads fired all timeouts */ + EVLOCK_LOCK(count_lock, 0); + if (count >= NUM_THREADS * NUM_ITERATIONS) + event_base_loopexit(base, NULL); + EVLOCK_UNLOCK(count_lock, 0); + + EVTHREAD_FREE_LOCK(cw.lock, 0); + EVTHREAD_FREE_COND(cw.cond); + + THREAD_RETURN(); +} + +static int notification_fd_used = 0; +#ifndef _WIN32 +static int got_sigchld = 0; +static void +sigchld_cb(evutil_socket_t fd, short event, void *arg) +{ + struct timeval tv; + struct event_base *base = arg; + + got_sigchld++; + tv.tv_usec = 100000; + tv.tv_sec = 0; + event_base_loopexit(base, &tv); +} + + +static void +notify_fd_cb(evutil_socket_t fd, short event, void *arg) +{ + ++notification_fd_used; +} +#endif + +static void +thread_basic(void *arg) +{ + THREAD_T threads[NUM_THREADS]; + struct event ev; + struct timeval tv; + int i; + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *notification_event = NULL; + struct event *sigchld_event = NULL; + + EVTHREAD_ALLOC_LOCK(count_lock, 0); + tt_assert(count_lock); + + tt_assert(base); + if (evthread_make_base_notifiable(base)<0) { + tt_abort_msg("Couldn't make base notifiable!"); + } + +#ifndef _WIN32 + if (data->setup_data && !strcmp(data->setup_data, "forking")) { + pid_t pid; + int status; + sigchld_event = evsignal_new(base, SIGCHLD, sigchld_cb, base); + /* This piggybacks on the th_notify_fd weirdly, and looks + * inside libevent internals. Not a good idea in non-testing + * code! */ + notification_event = event_new(base, + base->th_notify_fd[0], EV_READ|EV_PERSIST, notify_fd_cb, + NULL); + event_add(sigchld_event, NULL); + event_add(notification_event, NULL); + + if ((pid = fork()) == 0) { + event_del(notification_event); + if (event_reinit(base) < 0) { + TT_FAIL(("reinit")); + exit(1); + } + event_assign(notification_event, base, + base->th_notify_fd[0], EV_READ|EV_PERSIST, + notify_fd_cb, NULL); + event_add(notification_event, NULL); + goto child; + } + + event_base_dispatch(base); + + if (waitpid(pid, &status, 0) == -1) + tt_abort_perror("waitpid"); + TT_BLATHER(("Waitpid okay\n")); + + tt_assert(got_sigchld); + tt_int_op(notification_fd_used, ==, 0); + + goto end; + } + +child: +#endif + for (i = 0; i < NUM_THREADS; ++i) + THREAD_START(threads[i], basic_thread, base); + + evtimer_assign(&ev, base, NULL, NULL); + evutil_timerclear(&tv); + tv.tv_sec = 1000; + event_add(&ev, &tv); + + event_base_dispatch(base); + + for (i = 0; i < NUM_THREADS; ++i) + THREAD_JOIN(threads[i]); + + event_del(&ev); + + tt_int_op(count, ==, NUM_THREADS * NUM_ITERATIONS); + + EVTHREAD_FREE_LOCK(count_lock, 0); + + TT_BLATHER(("notifiations==%d", notification_fd_used)); + +end: + + if (notification_event) + event_free(notification_event); + if (sigchld_event) + event_free(sigchld_event); +} + +#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 (labs(diff1) < labs(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, 200); + } + tt_int_op(n_broadcast + n_signal + n_timed_out, ==, NUM_THREADS); + tt_int_op(n_signal, ==, 1); + +end: + EVTHREAD_FREE_LOCK(cond.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + EVTHREAD_FREE_COND(cond.cond); +} + +#define CB_COUNT 128 +#define QUEUE_THREAD_COUNT 8 + +static void +SLEEP_MS(int ms) +{ + struct timeval tv; + tv.tv_sec = ms/1000; + tv.tv_usec = (ms%1000)*1000; + evutil_usleep_(&tv); +} + +struct deferred_test_data { + struct event_callback cbs[CB_COUNT]; + struct event_base *queue; +}; + +static struct timeval timer_start = {0,0}; +static struct timeval timer_end = {0,0}; +static unsigned callback_count = 0; +static THREAD_T load_threads[QUEUE_THREAD_COUNT]; +static struct deferred_test_data deferred_data[QUEUE_THREAD_COUNT]; + +static void +deferred_callback(struct event_callback *cb, void *arg) +{ + SLEEP_MS(1); + callback_count += 1; +} + +static THREAD_FN +load_deferred_queue(void *arg) +{ + struct deferred_test_data *data = arg; + size_t i; + + for (i = 0; i < CB_COUNT; ++i) { + event_deferred_cb_init_(&data->cbs[i], 0, deferred_callback, + NULL); + event_deferred_cb_schedule_(data->queue, &data->cbs[i]); + SLEEP_MS(1); + } + + THREAD_RETURN(); +} + +static void +timer_callback(evutil_socket_t fd, short what, void *arg) +{ + evutil_gettimeofday(&timer_end, NULL); +} + +static void +start_threads_callback(evutil_socket_t fd, short what, void *arg) +{ + int i; + + for (i = 0; i < QUEUE_THREAD_COUNT; ++i) { + THREAD_START(load_threads[i], load_deferred_queue, + &deferred_data[i]); + } +} + +static void +thread_deferred_cb_skew(void *arg) +{ + struct timeval tv_timer = {1, 0}; + struct event_base *base = NULL; + struct event_config *cfg = NULL; + struct timeval elapsed; + int elapsed_usec; + int i; + + cfg = event_config_new(); + tt_assert(cfg); + event_config_set_max_dispatch_interval(cfg, NULL, 16, 0); + + base = event_base_new_with_config(cfg); + tt_assert(base); + + for (i = 0; i < QUEUE_THREAD_COUNT; ++i) + deferred_data[i].queue = base; + + evutil_gettimeofday(&timer_start, NULL); + event_base_once(base, -1, EV_TIMEOUT, timer_callback, NULL, + &tv_timer); + event_base_once(base, -1, EV_TIMEOUT, start_threads_callback, + NULL, NULL); + event_base_dispatch(base); + + evutil_timersub(&timer_end, &timer_start, &elapsed); + TT_BLATHER(("callback count, %u", callback_count)); + elapsed_usec = + (unsigned)(elapsed.tv_sec*1000000 + elapsed.tv_usec); + TT_BLATHER(("elapsed time, %u usec", elapsed_usec)); + + /* XXX be more intelligent here. just make sure skew is + * within .4 seconds for now. */ + tt_assert(elapsed_usec >= 600000 && elapsed_usec <= 1400000); + +end: + for (i = 0; i < QUEUE_THREAD_COUNT; ++i) + THREAD_JOIN(load_threads[i]); + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static struct event time_events[5]; +static struct timeval times[5]; +static struct event_base *exit_base = NULL; +static void +note_time_cb(evutil_socket_t fd, short what, void *arg) +{ + evutil_gettimeofday(arg, NULL); + if (arg == ×[4]) { + event_base_loopbreak(exit_base); + } +} +static THREAD_FN +register_events_subthread(void *arg) +{ + struct timeval tv = {0,0}; + SLEEP_MS(100); + event_active(&time_events[0], EV_TIMEOUT, 1); + SLEEP_MS(100); + event_active(&time_events[1], EV_TIMEOUT, 1); + SLEEP_MS(100); + tv.tv_usec = 100*1000; + event_add(&time_events[2], &tv); + tv.tv_usec = 150*1000; + event_add(&time_events[3], &tv); + SLEEP_MS(200); + event_active(&time_events[4], EV_TIMEOUT, 1); + + THREAD_RETURN(); +} + +static void +thread_no_events(void *arg) +{ + THREAD_T thread; + struct basic_test_data *data = arg; + struct timeval starttime, endtime; + int i; + exit_base = data->base; + + memset(times,0,sizeof(times)); + for (i=0;i<5;++i) { + event_assign(&time_events[i], data->base, + -1, 0, note_time_cb, ×[i]); + } + + evutil_gettimeofday(&starttime, NULL); + THREAD_START(thread, register_events_subthread, data->base); + event_base_loop(data->base, EVLOOP_NO_EXIT_ON_EMPTY); + evutil_gettimeofday(&endtime, NULL); + tt_assert(event_base_got_break(data->base)); + THREAD_JOIN(thread); + for (i=0; i<5; ++i) { + struct timeval diff; + double sec; + evutil_timersub(×[i], &starttime, &diff); + sec = diff.tv_sec + diff.tv_usec/1.0e6; + TT_BLATHER(("event %d at %.4f seconds", i, sec)); + } + test_timeval_diff_eq(&starttime, ×[0], 100); + test_timeval_diff_eq(&starttime, ×[1], 200); + test_timeval_diff_eq(&starttime, ×[2], 400); + test_timeval_diff_eq(&starttime, ×[3], 450); + test_timeval_diff_eq(&starttime, ×[4], 500); + test_timeval_diff_eq(&starttime, &endtime, 500); + +end: + ; +} + +#define TEST(name, f) \ + { #name, thread_##name, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE|(f), \ + &basic_setup, NULL } + +struct testcase_t thread_testcases[] = { + { "basic", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE, + &basic_setup, NULL }, +#ifndef _WIN32 + { "forking", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE, + &basic_setup, (char*)"forking" }, +#endif + TEST(conditions_simple, TT_RETRIABLE), + { "deferred_cb_skew", thread_deferred_cb_skew, + TT_FORK|TT_NEED_THREADS|TT_OFF_BY_DEFAULT, + &basic_setup, NULL }, +#ifndef _WIN32 + /****** XXX TODO FIXME windows seems to be having some timing trouble, + * looking into it now. / ellzey + ******/ + TEST(no_events, TT_RETRIABLE), +#endif + END_OF_TESTCASES +}; + |