xref: /netbsd-src/external/bsd/ntp/dist/sntp/libevent/test/regress_thread.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: regress_thread.c,v 1.7 2024/08/18 20:47:23 christos Exp $	*/
28585484eSchristos 
38585484eSchristos /*
48585484eSchristos  * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
58585484eSchristos  *
68585484eSchristos  * Redistribution and use in source and binary forms, with or without
78585484eSchristos  * modification, are permitted provided that the following conditions
88585484eSchristos  * are met:
98585484eSchristos  * 1. Redistributions of source code must retain the above copyright
108585484eSchristos  *    notice, this list of conditions and the following disclaimer.
118585484eSchristos  * 2. Redistributions in binary form must reproduce the above copyright
128585484eSchristos  *    notice, this list of conditions and the following disclaimer in the
138585484eSchristos  *    documentation and/or other materials provided with the distribution.
148585484eSchristos  * 3. The name of the author may not be used to endorse or promote products
158585484eSchristos  *    derived from this software without specific prior written permission.
168585484eSchristos  *
178585484eSchristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
188585484eSchristos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
198585484eSchristos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
208585484eSchristos  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
218585484eSchristos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
228585484eSchristos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
238585484eSchristos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
248585484eSchristos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
258585484eSchristos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
268585484eSchristos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
278585484eSchristos  */
288585484eSchristos #include "util-internal.h"
298585484eSchristos 
308585484eSchristos /* The old tests here need assertions to work. */
318585484eSchristos #undef NDEBUG
328585484eSchristos 
338585484eSchristos #include "event2/event-config.h"
348585484eSchristos 
358585484eSchristos #include <sys/types.h>
368585484eSchristos #include <stdio.h>
378585484eSchristos #include <stdlib.h>
388585484eSchristos #include <string.h>
398585484eSchristos #ifdef EVENT__HAVE_UNISTD_H
408585484eSchristos #include <unistd.h>
418585484eSchristos #endif
428585484eSchristos #ifdef EVENT__HAVE_SYS_WAIT_H
438585484eSchristos #include <sys/wait.h>
448585484eSchristos #endif
458585484eSchristos 
468585484eSchristos #ifdef EVENT__HAVE_PTHREADS
478585484eSchristos #include <pthread.h>
488585484eSchristos #elif defined(_WIN32)
498585484eSchristos #include <process.h>
508585484eSchristos #endif
518585484eSchristos #include <assert.h>
528585484eSchristos #ifdef EVENT__HAVE_UNISTD_H
538585484eSchristos #include <unistd.h>
548585484eSchristos #endif
558585484eSchristos #include <time.h>
568585484eSchristos 
578585484eSchristos #include "sys/queue.h"
588585484eSchristos 
598585484eSchristos #include "event2/event.h"
608585484eSchristos #include "event2/event_struct.h"
618585484eSchristos #include "event2/thread.h"
628585484eSchristos #include "event2/util.h"
638585484eSchristos #include "evthread-internal.h"
648585484eSchristos #include "event-internal.h"
658585484eSchristos #include "defer-internal.h"
668585484eSchristos #include "regress.h"
678585484eSchristos #include "tinytest_macros.h"
688585484eSchristos #include "time-internal.h"
69b8ecfcfeSchristos #include "regress_thread.h"
708585484eSchristos 
718585484eSchristos struct cond_wait {
728585484eSchristos 	void *lock;
738585484eSchristos 	void *cond;
748585484eSchristos };
758585484eSchristos 
768585484eSchristos static void
778585484eSchristos wake_all_timeout(evutil_socket_t fd, short what, void *arg)
788585484eSchristos {
798585484eSchristos 	struct cond_wait *cw = arg;
808585484eSchristos 	EVLOCK_LOCK(cw->lock, 0);
818585484eSchristos 	EVTHREAD_COND_BROADCAST(cw->cond);
828585484eSchristos 	EVLOCK_UNLOCK(cw->lock, 0);
838585484eSchristos 
848585484eSchristos }
858585484eSchristos 
868585484eSchristos static void
878585484eSchristos wake_one_timeout(evutil_socket_t fd, short what, void *arg)
888585484eSchristos {
898585484eSchristos 	struct cond_wait *cw = arg;
908585484eSchristos 	EVLOCK_LOCK(cw->lock, 0);
918585484eSchristos 	EVTHREAD_COND_SIGNAL(cw->cond);
928585484eSchristos 	EVLOCK_UNLOCK(cw->lock, 0);
938585484eSchristos }
948585484eSchristos 
958585484eSchristos #define NUM_THREADS	100
968585484eSchristos #define NUM_ITERATIONS  100
978585484eSchristos void *count_lock;
988585484eSchristos static int count;
998585484eSchristos 
1008585484eSchristos static THREAD_FN
1018585484eSchristos basic_thread(void *arg)
1028585484eSchristos {
1038585484eSchristos 	struct cond_wait cw;
1048585484eSchristos 	struct event_base *base = arg;
1058585484eSchristos 	struct event ev;
1068585484eSchristos 	int i = 0;
1078585484eSchristos 
1088585484eSchristos 	EVTHREAD_ALLOC_LOCK(cw.lock, 0);
1098585484eSchristos 	EVTHREAD_ALLOC_COND(cw.cond);
1108585484eSchristos 	assert(cw.lock);
1118585484eSchristos 	assert(cw.cond);
1128585484eSchristos 
1138585484eSchristos 	evtimer_assign(&ev, base, wake_all_timeout, &cw);
1148585484eSchristos 	for (i = 0; i < NUM_ITERATIONS; i++) {
1158585484eSchristos 		struct timeval tv;
1168585484eSchristos 		evutil_timerclear(&tv);
1178585484eSchristos 		tv.tv_sec = 0;
1188585484eSchristos 		tv.tv_usec = 3000;
1198585484eSchristos 
1208585484eSchristos 		EVLOCK_LOCK(cw.lock, 0);
1218585484eSchristos 		/* we need to make sure that event does not happen before
1228585484eSchristos 		 * we get to wait on the conditional variable */
1238585484eSchristos 		assert(evtimer_add(&ev, &tv) == 0);
1248585484eSchristos 
1258585484eSchristos 		assert(EVTHREAD_COND_WAIT(cw.cond, cw.lock) == 0);
1268585484eSchristos 		EVLOCK_UNLOCK(cw.lock, 0);
1278585484eSchristos 
1288585484eSchristos 		EVLOCK_LOCK(count_lock, 0);
1298585484eSchristos 		++count;
1308585484eSchristos 		EVLOCK_UNLOCK(count_lock, 0);
1318585484eSchristos 	}
1328585484eSchristos 
1338585484eSchristos 	/* exit the loop only if all threads fired all timeouts */
1348585484eSchristos 	EVLOCK_LOCK(count_lock, 0);
1358585484eSchristos 	if (count >= NUM_THREADS * NUM_ITERATIONS)
1368585484eSchristos 		event_base_loopexit(base, NULL);
1378585484eSchristos 	EVLOCK_UNLOCK(count_lock, 0);
1388585484eSchristos 
1398585484eSchristos 	EVTHREAD_FREE_LOCK(cw.lock, 0);
1408585484eSchristos 	EVTHREAD_FREE_COND(cw.cond);
1418585484eSchristos 
1428585484eSchristos 	THREAD_RETURN();
1438585484eSchristos }
1448585484eSchristos 
1458585484eSchristos static int notification_fd_used = 0;
1468585484eSchristos #ifndef _WIN32
1478585484eSchristos static int got_sigchld = 0;
1488585484eSchristos static void
1498585484eSchristos sigchld_cb(evutil_socket_t fd, short event, void *arg)
1508585484eSchristos {
1518585484eSchristos 	struct timeval tv;
1528585484eSchristos 	struct event_base *base = arg;
1538585484eSchristos 
1548585484eSchristos 	got_sigchld++;
1558585484eSchristos 	tv.tv_usec = 100000;
1568585484eSchristos 	tv.tv_sec = 0;
1578585484eSchristos 	event_base_loopexit(base, &tv);
1588585484eSchristos }
1598585484eSchristos 
1608585484eSchristos 
1618585484eSchristos static void
1628585484eSchristos notify_fd_cb(evutil_socket_t fd, short event, void *arg)
1638585484eSchristos {
1648585484eSchristos 	++notification_fd_used;
1658585484eSchristos }
1668585484eSchristos #endif
1678585484eSchristos 
1688585484eSchristos static void
1698585484eSchristos thread_basic(void *arg)
1708585484eSchristos {
1718585484eSchristos 	THREAD_T threads[NUM_THREADS];
1728585484eSchristos 	struct event ev;
1738585484eSchristos 	struct timeval tv;
1748585484eSchristos 	int i;
1758585484eSchristos 	struct basic_test_data *data = arg;
1768585484eSchristos 	struct event_base *base = data->base;
1778585484eSchristos 
1788585484eSchristos 	struct event *notification_event = NULL;
1798585484eSchristos 	struct event *sigchld_event = NULL;
1808585484eSchristos 
1818585484eSchristos 	EVTHREAD_ALLOC_LOCK(count_lock, 0);
1828585484eSchristos 	tt_assert(count_lock);
1838585484eSchristos 
1848585484eSchristos 	tt_assert(base);
1858585484eSchristos 	if (evthread_make_base_notifiable(base)<0) {
1868585484eSchristos 		tt_abort_msg("Couldn't make base notifiable!");
1878585484eSchristos 	}
1888585484eSchristos 
1898585484eSchristos #ifndef _WIN32
1908585484eSchristos 	if (data->setup_data && !strcmp(data->setup_data, "forking")) {
1918585484eSchristos 		pid_t pid;
1928585484eSchristos 		int status;
1938585484eSchristos 		sigchld_event = evsignal_new(base, SIGCHLD, sigchld_cb, base);
1948585484eSchristos 		/* This piggybacks on the th_notify_fd weirdly, and looks
1958585484eSchristos 		 * inside libevent internals.  Not a good idea in non-testing
1968585484eSchristos 		 * code! */
1978585484eSchristos 		notification_event = event_new(base,
1988585484eSchristos 		    base->th_notify_fd[0], EV_READ|EV_PERSIST, notify_fd_cb,
1998585484eSchristos 		    NULL);
2008585484eSchristos 		event_add(sigchld_event, NULL);
2018585484eSchristos 		event_add(notification_event, NULL);
2028585484eSchristos 
2038585484eSchristos 		if ((pid = fork()) == 0) {
2048585484eSchristos 			event_del(notification_event);
2058585484eSchristos 			if (event_reinit(base) < 0) {
2068585484eSchristos 				TT_FAIL(("reinit"));
2078585484eSchristos 				exit(1);
2088585484eSchristos 			}
2098585484eSchristos 			event_assign(notification_event, base,
2108585484eSchristos 			    base->th_notify_fd[0], EV_READ|EV_PERSIST,
2118585484eSchristos 			    notify_fd_cb, NULL);
2128585484eSchristos 			event_add(notification_event, NULL);
2138585484eSchristos 	 		goto child;
2148585484eSchristos 		}
2158585484eSchristos 
2168585484eSchristos 		event_base_dispatch(base);
2178585484eSchristos 
2188585484eSchristos 		if (waitpid(pid, &status, 0) == -1)
2198585484eSchristos 			tt_abort_perror("waitpid");
2208585484eSchristos 		TT_BLATHER(("Waitpid okay\n"));
2218585484eSchristos 
2228585484eSchristos 		tt_assert(got_sigchld);
2238585484eSchristos 		tt_int_op(notification_fd_used, ==, 0);
2248585484eSchristos 
2258585484eSchristos 		goto end;
2268585484eSchristos 	}
2278585484eSchristos 
2288585484eSchristos child:
2298585484eSchristos #endif
2308585484eSchristos 	for (i = 0; i < NUM_THREADS; ++i)
2318585484eSchristos 		THREAD_START(threads[i], basic_thread, base);
2328585484eSchristos 
2338585484eSchristos 	evtimer_assign(&ev, base, NULL, NULL);
2348585484eSchristos 	evutil_timerclear(&tv);
2358585484eSchristos 	tv.tv_sec = 1000;
2368585484eSchristos 	event_add(&ev, &tv);
2378585484eSchristos 
2388585484eSchristos 	event_base_dispatch(base);
2398585484eSchristos 
2408585484eSchristos 	for (i = 0; i < NUM_THREADS; ++i)
2418585484eSchristos 		THREAD_JOIN(threads[i]);
2428585484eSchristos 
2438585484eSchristos 	event_del(&ev);
2448585484eSchristos 
2458585484eSchristos 	tt_int_op(count, ==, NUM_THREADS * NUM_ITERATIONS);
2468585484eSchristos 
2478585484eSchristos 	EVTHREAD_FREE_LOCK(count_lock, 0);
2488585484eSchristos 
2498585484eSchristos 	TT_BLATHER(("notifiations==%d", notification_fd_used));
2508585484eSchristos 
2518585484eSchristos end:
2528585484eSchristos 
2538585484eSchristos 	if (notification_event)
2548585484eSchristos 		event_free(notification_event);
2558585484eSchristos 	if (sigchld_event)
2568585484eSchristos 		event_free(sigchld_event);
2578585484eSchristos }
2588585484eSchristos 
2598585484eSchristos #undef NUM_THREADS
2608585484eSchristos #define NUM_THREADS 10
2618585484eSchristos 
2628585484eSchristos struct alerted_record {
2638585484eSchristos 	struct cond_wait *cond;
2648585484eSchristos 	struct timeval delay;
2658585484eSchristos 	struct timeval alerted_at;
2668585484eSchristos 	int timed_out;
2678585484eSchristos };
2688585484eSchristos 
2698585484eSchristos static THREAD_FN
2708585484eSchristos wait_for_condition(void *arg)
2718585484eSchristos {
2728585484eSchristos 	struct alerted_record *rec = arg;
2738585484eSchristos 	int r;
2748585484eSchristos 
2758585484eSchristos 	EVLOCK_LOCK(rec->cond->lock, 0);
2768585484eSchristos 	if (rec->delay.tv_sec || rec->delay.tv_usec) {
2778585484eSchristos 		r = EVTHREAD_COND_WAIT_TIMED(rec->cond->cond, rec->cond->lock,
2788585484eSchristos 		    &rec->delay);
2798585484eSchristos 	} else {
2808585484eSchristos 		r = EVTHREAD_COND_WAIT(rec->cond->cond, rec->cond->lock);
2818585484eSchristos 	}
2828585484eSchristos 	EVLOCK_UNLOCK(rec->cond->lock, 0);
2838585484eSchristos 
2848585484eSchristos 	evutil_gettimeofday(&rec->alerted_at, NULL);
2858585484eSchristos 	if (r == 1)
2868585484eSchristos 		rec->timed_out = 1;
2878585484eSchristos 
2888585484eSchristos 	THREAD_RETURN();
2898585484eSchristos }
2908585484eSchristos 
2918585484eSchristos static void
2928585484eSchristos thread_conditions_simple(void *arg)
2938585484eSchristos {
2948585484eSchristos 	struct timeval tv_signal, tv_timeout, tv_broadcast;
2958585484eSchristos 	struct alerted_record alerted[NUM_THREADS];
2968585484eSchristos 	THREAD_T threads[NUM_THREADS];
2978585484eSchristos 	struct cond_wait cond;
2988585484eSchristos 	int i;
2998585484eSchristos 	struct timeval launched_at;
3008585484eSchristos 	struct event wake_one;
3018585484eSchristos 	struct event wake_all;
3028585484eSchristos 	struct basic_test_data *data = arg;
3038585484eSchristos 	struct event_base *base = data->base;
3048585484eSchristos 	int n_timed_out=0, n_signal=0, n_broadcast=0;
3058585484eSchristos 
3068585484eSchristos 	tv_signal.tv_sec = tv_timeout.tv_sec = tv_broadcast.tv_sec = 0;
3078585484eSchristos 	tv_signal.tv_usec = 30*1000;
3088585484eSchristos 	tv_timeout.tv_usec = 150*1000;
3098585484eSchristos 	tv_broadcast.tv_usec = 500*1000;
3108585484eSchristos 
3118585484eSchristos 	EVTHREAD_ALLOC_LOCK(cond.lock, EVTHREAD_LOCKTYPE_RECURSIVE);
3128585484eSchristos 	EVTHREAD_ALLOC_COND(cond.cond);
3138585484eSchristos 	tt_assert(cond.lock);
3148585484eSchristos 	tt_assert(cond.cond);
3158585484eSchristos 	for (i = 0; i < NUM_THREADS; ++i) {
3168585484eSchristos 		memset(&alerted[i], 0, sizeof(struct alerted_record));
3178585484eSchristos 		alerted[i].cond = &cond;
3188585484eSchristos 	}
3198585484eSchristos 
3208585484eSchristos 	/* Threads 5 and 6 will be allowed to time out */
3218585484eSchristos 	memcpy(&alerted[5].delay, &tv_timeout, sizeof(tv_timeout));
3228585484eSchristos 	memcpy(&alerted[6].delay, &tv_timeout, sizeof(tv_timeout));
3238585484eSchristos 
3248585484eSchristos 	evtimer_assign(&wake_one, base, wake_one_timeout, &cond);
3258585484eSchristos 	evtimer_assign(&wake_all, base, wake_all_timeout, &cond);
3268585484eSchristos 
3278585484eSchristos 	evutil_gettimeofday(&launched_at, NULL);
3288585484eSchristos 
3298585484eSchristos 	/* Launch the threads... */
3308585484eSchristos 	for (i = 0; i < NUM_THREADS; ++i) {
3318585484eSchristos 		THREAD_START(threads[i], wait_for_condition, &alerted[i]);
3328585484eSchristos 	}
3338585484eSchristos 
3348585484eSchristos 	/* Start the timers... */
3358585484eSchristos 	tt_int_op(event_add(&wake_one, &tv_signal), ==, 0);
3368585484eSchristos 	tt_int_op(event_add(&wake_all, &tv_broadcast), ==, 0);
3378585484eSchristos 
3388585484eSchristos 	/* And run for a bit... */
3398585484eSchristos 	event_base_dispatch(base);
3408585484eSchristos 
3418585484eSchristos 	/* And wait till the threads are done. */
3428585484eSchristos 	for (i = 0; i < NUM_THREADS; ++i)
3438585484eSchristos 		THREAD_JOIN(threads[i]);
3448585484eSchristos 
3458585484eSchristos 	/* Now, let's see what happened. At least one of 5 or 6 should
3468585484eSchristos 	 * have timed out. */
3478585484eSchristos 	n_timed_out = alerted[5].timed_out + alerted[6].timed_out;
3488585484eSchristos 	tt_int_op(n_timed_out, >=, 1);
3498585484eSchristos 	tt_int_op(n_timed_out, <=, 2);
3508585484eSchristos 
3518585484eSchristos 	for (i = 0; i < NUM_THREADS; ++i) {
3528585484eSchristos 		const struct timeval *target_delay;
3538585484eSchristos 		struct timeval target_time, actual_delay;
3548585484eSchristos 		if (alerted[i].timed_out) {
3558585484eSchristos 			TT_BLATHER(("%d looks like a timeout\n", i));
3568585484eSchristos 			target_delay = &tv_timeout;
3578585484eSchristos 			tt_assert(i == 5 || i == 6);
3588585484eSchristos 		} else if (evutil_timerisset(&alerted[i].alerted_at)) {
3598585484eSchristos 			long diff1,diff2;
3608585484eSchristos 			evutil_timersub(&alerted[i].alerted_at,
3618585484eSchristos 			    &launched_at, &actual_delay);
3628585484eSchristos 			diff1 = timeval_msec_diff(&actual_delay,
3638585484eSchristos 			    &tv_signal);
3648585484eSchristos 			diff2 = timeval_msec_diff(&actual_delay,
3658585484eSchristos 			    &tv_broadcast);
3667476e6e4Schristos 			if (labs(diff1) < labs(diff2)) {
3678585484eSchristos 				TT_BLATHER(("%d looks like a signal\n", i));
3688585484eSchristos 				target_delay = &tv_signal;
3698585484eSchristos 				++n_signal;
3708585484eSchristos 			} else {
3718585484eSchristos 				TT_BLATHER(("%d looks like a broadcast\n", i));
3728585484eSchristos 				target_delay = &tv_broadcast;
3738585484eSchristos 				++n_broadcast;
3748585484eSchristos 			}
3758585484eSchristos 		} else {
3768585484eSchristos 			TT_FAIL(("Thread %d never got woken", i));
3778585484eSchristos 			continue;
3788585484eSchristos 		}
3798585484eSchristos 		evutil_timeradd(target_delay, &launched_at, &target_time);
3808585484eSchristos 		test_timeval_diff_leq(&target_time, &alerted[i].alerted_at,
381*eabc0478Schristos 		    0, 200);
3828585484eSchristos 	}
3838585484eSchristos 	tt_int_op(n_broadcast + n_signal + n_timed_out, ==, NUM_THREADS);
3848585484eSchristos 	tt_int_op(n_signal, ==, 1);
3858585484eSchristos 
3868585484eSchristos end:
3877476e6e4Schristos 	EVTHREAD_FREE_LOCK(cond.lock, EVTHREAD_LOCKTYPE_RECURSIVE);
3887476e6e4Schristos 	EVTHREAD_FREE_COND(cond.cond);
3898585484eSchristos }
3908585484eSchristos 
3918585484eSchristos #define CB_COUNT 128
3928585484eSchristos #define QUEUE_THREAD_COUNT 8
3938585484eSchristos 
3948585484eSchristos static void
3958585484eSchristos SLEEP_MS(int ms)
3968585484eSchristos {
3978585484eSchristos 	struct timeval tv;
3988585484eSchristos 	tv.tv_sec = ms/1000;
3998585484eSchristos 	tv.tv_usec = (ms%1000)*1000;
4008585484eSchristos 	evutil_usleep_(&tv);
4018585484eSchristos }
4028585484eSchristos 
4038585484eSchristos struct deferred_test_data {
4048585484eSchristos 	struct event_callback cbs[CB_COUNT];
4058585484eSchristos 	struct event_base *queue;
4068585484eSchristos };
4078585484eSchristos 
4088585484eSchristos static struct timeval timer_start = {0,0};
4098585484eSchristos static struct timeval timer_end = {0,0};
4108585484eSchristos static unsigned callback_count = 0;
4118585484eSchristos static THREAD_T load_threads[QUEUE_THREAD_COUNT];
4128585484eSchristos static struct deferred_test_data deferred_data[QUEUE_THREAD_COUNT];
4138585484eSchristos 
4148585484eSchristos static void
4158585484eSchristos deferred_callback(struct event_callback *cb, void *arg)
4168585484eSchristos {
4178585484eSchristos 	SLEEP_MS(1);
4188585484eSchristos 	callback_count += 1;
4198585484eSchristos }
4208585484eSchristos 
4218585484eSchristos static THREAD_FN
4228585484eSchristos load_deferred_queue(void *arg)
4238585484eSchristos {
4248585484eSchristos 	struct deferred_test_data *data = arg;
4258585484eSchristos 	size_t i;
4268585484eSchristos 
4278585484eSchristos 	for (i = 0; i < CB_COUNT; ++i) {
4288585484eSchristos 		event_deferred_cb_init_(&data->cbs[i], 0, deferred_callback,
4298585484eSchristos 		    NULL);
4308585484eSchristos 		event_deferred_cb_schedule_(data->queue, &data->cbs[i]);
4318585484eSchristos 		SLEEP_MS(1);
4328585484eSchristos 	}
4338585484eSchristos 
4348585484eSchristos 	THREAD_RETURN();
4358585484eSchristos }
4368585484eSchristos 
4378585484eSchristos static void
4388585484eSchristos timer_callback(evutil_socket_t fd, short what, void *arg)
4398585484eSchristos {
4408585484eSchristos 	evutil_gettimeofday(&timer_end, NULL);
4418585484eSchristos }
4428585484eSchristos 
4438585484eSchristos static void
4448585484eSchristos start_threads_callback(evutil_socket_t fd, short what, void *arg)
4458585484eSchristos {
4468585484eSchristos 	int i;
4478585484eSchristos 
4488585484eSchristos 	for (i = 0; i < QUEUE_THREAD_COUNT; ++i) {
4498585484eSchristos 		THREAD_START(load_threads[i], load_deferred_queue,
4508585484eSchristos 				&deferred_data[i]);
4518585484eSchristos 	}
4528585484eSchristos }
4538585484eSchristos 
4548585484eSchristos static void
4558585484eSchristos thread_deferred_cb_skew(void *arg)
4568585484eSchristos {
4578585484eSchristos 	struct timeval tv_timer = {1, 0};
4588585484eSchristos 	struct event_base *base = NULL;
4598585484eSchristos 	struct event_config *cfg = NULL;
4608585484eSchristos 	struct timeval elapsed;
4618585484eSchristos 	int elapsed_usec;
4628585484eSchristos 	int i;
4638585484eSchristos 
4648585484eSchristos 	cfg = event_config_new();
4658585484eSchristos 	tt_assert(cfg);
4668585484eSchristos 	event_config_set_max_dispatch_interval(cfg, NULL, 16, 0);
4678585484eSchristos 
4688585484eSchristos 	base = event_base_new_with_config(cfg);
4698585484eSchristos 	tt_assert(base);
4708585484eSchristos 
4718585484eSchristos 	for (i = 0; i < QUEUE_THREAD_COUNT; ++i)
4728585484eSchristos 		deferred_data[i].queue = base;
4738585484eSchristos 
4748585484eSchristos 	evutil_gettimeofday(&timer_start, NULL);
4758585484eSchristos 	event_base_once(base, -1, EV_TIMEOUT, timer_callback, NULL,
4768585484eSchristos 			&tv_timer);
4778585484eSchristos 	event_base_once(base, -1, EV_TIMEOUT, start_threads_callback,
4788585484eSchristos 			NULL, NULL);
4798585484eSchristos 	event_base_dispatch(base);
4808585484eSchristos 
4818585484eSchristos 	evutil_timersub(&timer_end, &timer_start, &elapsed);
4828585484eSchristos 	TT_BLATHER(("callback count, %u", callback_count));
4838585484eSchristos 	elapsed_usec =
4848585484eSchristos 	    (unsigned)(elapsed.tv_sec*1000000 + elapsed.tv_usec);
4858585484eSchristos 	TT_BLATHER(("elapsed time, %u usec", elapsed_usec));
4868585484eSchristos 
4878585484eSchristos 	/* XXX be more intelligent here.  just make sure skew is
4888585484eSchristos 	 * within .4 seconds for now. */
4898585484eSchristos 	tt_assert(elapsed_usec >= 600000 && elapsed_usec <= 1400000);
4908585484eSchristos 
4918585484eSchristos end:
4928585484eSchristos 	for (i = 0; i < QUEUE_THREAD_COUNT; ++i)
4938585484eSchristos 		THREAD_JOIN(load_threads[i]);
4948585484eSchristos 	if (base)
4958585484eSchristos 		event_base_free(base);
4968585484eSchristos 	if (cfg)
4978585484eSchristos 		event_config_free(cfg);
4988585484eSchristos }
4998585484eSchristos 
5008585484eSchristos static struct event time_events[5];
5018585484eSchristos static struct timeval times[5];
5028585484eSchristos static struct event_base *exit_base = NULL;
5038585484eSchristos static void
5048585484eSchristos note_time_cb(evutil_socket_t fd, short what, void *arg)
5058585484eSchristos {
5068585484eSchristos 	evutil_gettimeofday(arg, NULL);
5078585484eSchristos 	if (arg == &times[4]) {
5088585484eSchristos 		event_base_loopbreak(exit_base);
5098585484eSchristos 	}
5108585484eSchristos }
5118585484eSchristos static THREAD_FN
5128585484eSchristos register_events_subthread(void *arg)
5138585484eSchristos {
5148585484eSchristos 	struct timeval tv = {0,0};
5158585484eSchristos 	SLEEP_MS(100);
5168585484eSchristos 	event_active(&time_events[0], EV_TIMEOUT, 1);
5178585484eSchristos 	SLEEP_MS(100);
5188585484eSchristos 	event_active(&time_events[1], EV_TIMEOUT, 1);
5198585484eSchristos 	SLEEP_MS(100);
5208585484eSchristos 	tv.tv_usec = 100*1000;
5218585484eSchristos 	event_add(&time_events[2], &tv);
5228585484eSchristos 	tv.tv_usec = 150*1000;
5238585484eSchristos 	event_add(&time_events[3], &tv);
5248585484eSchristos 	SLEEP_MS(200);
5258585484eSchristos 	event_active(&time_events[4], EV_TIMEOUT, 1);
5268585484eSchristos 
5278585484eSchristos 	THREAD_RETURN();
5288585484eSchristos }
5298585484eSchristos 
5308585484eSchristos static void
5318585484eSchristos thread_no_events(void *arg)
5328585484eSchristos {
5338585484eSchristos 	THREAD_T thread;
5348585484eSchristos 	struct basic_test_data *data = arg;
5358585484eSchristos 	struct timeval starttime, endtime;
5368585484eSchristos 	int i;
5378585484eSchristos 	exit_base = data->base;
5388585484eSchristos 
5398585484eSchristos 	memset(times,0,sizeof(times));
5408585484eSchristos 	for (i=0;i<5;++i) {
5418585484eSchristos 		event_assign(&time_events[i], data->base,
5428585484eSchristos 		    -1, 0, note_time_cb, &times[i]);
5438585484eSchristos 	}
5448585484eSchristos 
5458585484eSchristos 	evutil_gettimeofday(&starttime, NULL);
5468585484eSchristos 	THREAD_START(thread, register_events_subthread, data->base);
5478585484eSchristos 	event_base_loop(data->base, EVLOOP_NO_EXIT_ON_EMPTY);
5488585484eSchristos 	evutil_gettimeofday(&endtime, NULL);
5498585484eSchristos 	tt_assert(event_base_got_break(data->base));
5508585484eSchristos 	THREAD_JOIN(thread);
5518585484eSchristos 	for (i=0; i<5; ++i) {
5528585484eSchristos 		struct timeval diff;
5538585484eSchristos 		double sec;
5548585484eSchristos 		evutil_timersub(&times[i], &starttime, &diff);
5558585484eSchristos 		sec = diff.tv_sec + diff.tv_usec/1.0e6;
5568585484eSchristos 		TT_BLATHER(("event %d at %.4f seconds", i, sec));
5578585484eSchristos 	}
5588585484eSchristos 	test_timeval_diff_eq(&starttime, &times[0], 100);
5598585484eSchristos 	test_timeval_diff_eq(&starttime, &times[1], 200);
5608585484eSchristos 	test_timeval_diff_eq(&starttime, &times[2], 400);
5618585484eSchristos 	test_timeval_diff_eq(&starttime, &times[3], 450);
5628585484eSchristos 	test_timeval_diff_eq(&starttime, &times[4], 500);
5638585484eSchristos 	test_timeval_diff_eq(&starttime, &endtime,  500);
5648585484eSchristos 
5658585484eSchristos end:
5668585484eSchristos 	;
5678585484eSchristos }
5688585484eSchristos 
569*eabc0478Schristos #define TEST(name, f)							\
570*eabc0478Schristos 	{ #name, thread_##name, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE|(f),	\
5718585484eSchristos 	  &basic_setup, NULL }
5728585484eSchristos 
5738585484eSchristos struct testcase_t thread_testcases[] = {
5748585484eSchristos 	{ "basic", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE,
5758585484eSchristos 	  &basic_setup, NULL },
5768585484eSchristos #ifndef _WIN32
5778585484eSchristos 	{ "forking", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE,
5788585484eSchristos 	  &basic_setup, (char*)"forking" },
5798585484eSchristos #endif
580*eabc0478Schristos 	TEST(conditions_simple, TT_RETRIABLE),
5818585484eSchristos 	{ "deferred_cb_skew", thread_deferred_cb_skew,
5828585484eSchristos 	  TT_FORK|TT_NEED_THREADS|TT_OFF_BY_DEFAULT,
5838585484eSchristos 	  &basic_setup, NULL },
584*eabc0478Schristos #ifndef _WIN32
585*eabc0478Schristos 	/****** XXX TODO FIXME windows seems to be having some timing trouble,
586*eabc0478Schristos 	 * looking into it now. / ellzey
587*eabc0478Schristos 	 ******/
588*eabc0478Schristos 	TEST(no_events, TT_RETRIABLE),
589*eabc0478Schristos #endif
5908585484eSchristos 	END_OF_TESTCASES
5918585484eSchristos };
5928585484eSchristos 
593