/*
 * Copyright (C) 1998-2001, 2004, 2005, 2007, 2009, 2011, 2013-2016  Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/* $Id: t_tasks.c,v 1.49 2011/07/27 07:45:55 marka Exp $ */

#include <config.h>

#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_INTTYPES_H
#include <inttypes.h> 	/* uintptr_t */
#endif
#include <isc/condition.h>
#include <isc/mem.h>
#include <isc/platform.h>
#include <isc/task.h>
#include <isc/time.h>
#include <isc/timer.h>
#include <isc/util.h>

#include <tests/t_api.h>


#ifdef ISC_PLATFORM_USETHREADS
isc_boolean_t threaded = ISC_TRUE;
#else
isc_boolean_t threaded = ISC_FALSE;
#endif

static int senders[4];

static void
require_threads(void) {
	t_info("This test requires threads\n");
	t_result(T_THREADONLY);
	return;
}

static void
t1_callback(isc_task_t *task, isc_event_t *event) {
	int	i;
	int	j;

	UNUSED(task);

	j = 0;

	for (i = 0; i < 1000000; i++)
		j += 100;

	t_info("task %s\n", (char *)event->ev_arg);
	isc_event_free(&event);
}

static void
t1_shutdown(isc_task_t *task, isc_event_t *event) {

	UNUSED(task);

	t_info("shutdown %s\n", (char *)event->ev_arg);
	isc_event_free(&event);
}

static void
my_tick(isc_task_t *task, isc_event_t *event) {

	UNUSED(task);

	t_info("%s\n", (char *)event->ev_arg);
	isc_event_free(&event);
}

/*
 * Adapted from RTH's original task_test program
 */

static char one[] = "1";
static char two[] = "2";
static char three[] = "3";
static char four[] = "4";
static char tick[] = "tick";
static char tock[] = "tock";

static int
t_tasks1(void) {
	char			*p;
	isc_mem_t		*mctx;
	isc_taskmgr_t		*manager;
	isc_task_t		*task1;
	isc_task_t		*task2;
	isc_task_t		*task3;
	isc_task_t		*task4;
	isc_event_t		*event;
	unsigned int		workers;
	isc_timermgr_t		*timgr;
	isc_timer_t		*ti1;
	isc_timer_t		*ti2;
	isc_result_t		isc_result;
	isc_time_t		absolute;
	isc_interval_t		interval;

	manager = NULL;
	task1 = NULL;
	task2 = NULL;
	task3 = NULL;
	task4 = NULL;
	mctx = NULL;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);
	if (workers < 1) {
		t_info("Bad config value for ISC_TASK_WORKERS, %d\n", workers);
		return(T_UNRESOLVED);
	}

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	isc_result = isc_taskmgr_create(mctx, workers, 0, &manager);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_create(manager, 0, &task1);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_create(manager, 0, &task2);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_create(manager, 0, &task3);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_create(manager, 0, &task4);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_onshutdown(task1, t1_shutdown, one);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_onshutdown(task2, t1_shutdown, two);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_onshutdown(task3, t1_shutdown, three);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown failed %d\n", isc_result);
		return(T_FAIL);
	}

	isc_result = isc_task_onshutdown(task4, t1_shutdown, four);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown failed %d\n", isc_result);
		return(T_FAIL);
	}

	timgr = NULL;
	isc_result = isc_timermgr_create(mctx, &timgr);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_timermgr_create %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	ti1 = NULL;
	isc_time_settoepoch(&absolute);
	isc_interval_set(&interval, 1, 0);
	isc_result = isc_timer_create(timgr, isc_timertype_ticker,
				&absolute, &interval,
				task1, my_tick, tick, &ti1);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_timer_create %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	ti2 = NULL;
	isc_time_settoepoch(&absolute);
	isc_interval_set(&interval, 1, 0);
	isc_result = isc_timer_create(timgr, isc_timertype_ticker,
				       &absolute, &interval,
				       task2, my_tick, tock, &ti2);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_timer_create %d\n", isc_result);
		return(T_UNRESOLVED);
	}


#ifndef WIN32
	sleep(2);
#else
	Sleep(2000);
#endif

	/*
	 * Note:  (void *)1 is used as a sender here, since some compilers
	 * don't like casting a function pointer to a (void *).
	 *
	 * In a real use, it is more likely the sender would be a
	 * structure (socket, timer, task, etc) but this is just a test
	 * program.
	 */
	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, one,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task1, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, two,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task2, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, three,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task3, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, four,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task4, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, two,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task2, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, three,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task3, &event);

	event = isc_event_allocate(mctx, (void *)1, 1, t1_callback, four,
				   sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_task_send(task4, &event);

	(void)isc_task_purge(task3, NULL, 0, 0);

	isc_task_detach(&task1);
	isc_task_detach(&task2);
	isc_task_detach(&task3);
	isc_task_detach(&task4);

#ifndef WIN32
	sleep(10);
#else
	Sleep(10000);
#endif
	isc_timer_detach(&ti1);
	isc_timer_detach(&ti2);
	isc_timermgr_destroy(&timgr);
	isc_taskmgr_destroy(&manager);

	isc_mem_destroy(&mctx);
	return(T_PASS);
}

static const char *a1 =	"The task subsystem can create and manage tasks";

static void
t1(void) {
	int	result;

	t_assert("tasks", 1, T_REQUIRED, "%s", a1);
	result = t_tasks1();
	t_result(result);
}

#define			T2_NTASKS	10000

static isc_event_t	*T2_event;
static isc_taskmgr_t	*T2_manager;
static isc_mem_t	*T2_mctx;
static isc_condition_t	T2_cv;
static isc_mutex_t	T2_mx;
static int		T2_done;
static int		T2_nprobs;
static int		T2_nfails;
static int		T2_ntasks;

static void
t2_shutdown(isc_task_t *task, isc_event_t *event) {

	isc_result_t	isc_result;

	UNUSED(task);

	if (event->ev_arg != NULL) {
		isc_task_destroy((isc_task_t**) &event->ev_arg);
	}
	else {
		isc_result = isc_mutex_lock(&T2_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_mutex_lock failed %d\n", isc_result);
			++T2_nprobs;
		}

		T2_done = 1;

		isc_result = isc_condition_signal(&T2_cv);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_condition_signal failed %d\n", isc_result);
			++T2_nprobs;
		}

		isc_result = isc_mutex_unlock(&T2_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_mutex_unlock failed %d\n", isc_result);
			++T2_nprobs;
		}

		isc_event_free(&T2_event);
		isc_taskmgr_destroy(&T2_manager);
		isc_mem_destroy(&T2_mctx);
	}
}

static void
t2_callback(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;
	isc_task_t	*newtask;

	++T2_ntasks;

	if (T_debug && ((T2_ntasks % 100) == 0)) {
		t_info("T2_ntasks %d\n", T2_ntasks);
	}

	if (event->ev_arg) {

		event->ev_arg = (void *)(((uintptr_t) event->ev_arg) - 1);

		/*
		 * Create a new task and forward the message.
		 */
		newtask = NULL;
		isc_result = isc_task_create(T2_manager, 0, &newtask);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_task_create failed %d\n", isc_result);
			++T2_nfails;
			return;
		}

		isc_result = isc_task_onshutdown(newtask, t2_shutdown,
						 (void *)task);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_task_onshutdown failed %d\n",
						isc_result);
			++T2_nfails;
			return;
		}

		isc_task_send(newtask, &event);
	} else {
		/*
		 * Time to unwind, shutdown should perc back up.
		 */
		isc_task_destroy(&task);
	}
}

static int
t_tasks2(void) {
	uintptr_t		ntasks;
	int			result;
	char			*p;
	isc_event_t		*event;
	unsigned int		workers;
	isc_result_t		isc_result;

	T2_manager = NULL;
	T2_done = 0;
	T2_nprobs = 0;
	T2_nfails = 0;
	T2_ntasks = 0;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);
	if (workers < 1) {
		t_info("Bad config value for ISC_TASK_WORKERS, %d\n", workers);
		return(T_UNRESOLVED);
	}

	p = t_getenv("ISC_TASKS_MIN");
	if (p != NULL)
		ntasks = atoi(p);
	else
		ntasks = T2_NTASKS;
	if (ntasks == 0U) {
		t_info("Bad config value for ISC_TASKS_MIN, %lu\n",
		       (unsigned long)ntasks);
		return(T_UNRESOLVED);
	}

	t_info("Testing with %lu tasks\n", (unsigned long)ntasks);

	isc_result = isc_mutex_init(&T2_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_init failed %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	isc_result = isc_condition_init(&T2_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_init failed %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	isc_result = isc_mem_create(0, 0, &T2_mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	isc_result = isc_taskmgr_create(T2_mctx, workers, 0, &T2_manager);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	T2_event = isc_event_allocate(T2_mctx, (void *)1, 1, t2_callback,
					(void *)ntasks, sizeof(*event));
	if (T2_event == NULL) {
		t_info("isc_event_allocate failed\n");
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_lock(&T2_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	t2_callback(NULL, T2_event);

	while (T2_done == 0) {
		isc_result = isc_condition_wait(&T2_cv, &T2_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_condition_wait failed %d\n", isc_result);
			return(T_UNRESOLVED);
		}
	}

	result = T_UNRESOLVED;

	if ((T2_nfails == 0) && (T2_nprobs == 0))
		result = T_PASS;
	else if (T2_nfails != 0)
		result = T_FAIL;

	return(result);
}

static const char *a2 = "The task subsystem can create ISC_TASKS_MIN tasks";

static void
t2(void) {
	t_assert("tasks", 2, T_REQUIRED, "%s", a2);

	if (threaded)
		t_result(t_tasks2());
	else
		require_threads();
}

#define	T3_NEVENTS	256

static	int		T3_flag;
static	int		T3_nevents;
static	int		T3_nsdevents;
static	isc_mutex_t	T3_mx;
static	isc_condition_t	T3_cv;
static	int		T3_nfails;
static	int		T3_nprobs;

static void
t3_sde1(isc_task_t *task, isc_event_t *event) {

	UNUSED(task);

	if (T3_nevents != T3_NEVENTS) {
		t_info("Some events were not processed\n");
		++T3_nprobs;
	}
	if (T3_nsdevents == 1) {
		++T3_nsdevents;
	} else {
		t_info("Shutdown events not processed in LIFO order\n");
		++T3_nfails;
	}
	isc_event_free(&event);
}

static void
t3_sde2(isc_task_t *task, isc_event_t *event) {

	UNUSED(task);

	if (T3_nevents != T3_NEVENTS) {
		t_info("Some events were not processed\n");
		++T3_nprobs;
	}
	if (T3_nsdevents == 0) {
		++T3_nsdevents;
	} else {
		t_info("Shutdown events not processed in LIFO order\n");
		++T3_nfails;
	}
	isc_event_free(&event);
}

static void
t3_event1(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T3_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T3_nprobs;
	}
	while (T3_flag != 1) {
		(void) isc_condition_wait(&T3_cv, &T3_mx);
	}

	isc_result = isc_mutex_unlock(&T3_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T3_nprobs;
	}
	isc_event_free(&event);
}

static void
t3_event2(isc_task_t *task, isc_event_t *event) {
	UNUSED(task);

	++T3_nevents;
	isc_event_free(&event);
}

static int
t_tasks3(void) {
	int		cnt;
	int		result;
	char		*p;
	isc_mem_t	*mctx;
	isc_taskmgr_t	*tmgr;
	isc_task_t	*task;
	unsigned int	workers;
	isc_event_t	*event;
	isc_result_t	isc_result;
	isc_eventtype_t	event_type;

	T3_flag = 0;
	T3_nevents = 0;
	T3_nsdevents = 0;
	T3_nfails = 0;
	T3_nprobs = 0;

	event_type = 3;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);

	mctx = NULL;
	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_init(&T3_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_condition_init(&T3_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	tmgr = NULL;
	isc_result = isc_taskmgr_create(mctx, workers, 0, &tmgr);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_lock(&T3_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	task = NULL;
	isc_result = isc_task_create(tmgr, 0, &task);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %s\n",
		       isc_result_totext(isc_result));
		(void) isc_mutex_unlock(&T3_mx);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	/*
	 * This event causes the task to wait on T3_cv.
	 */
	event = isc_event_allocate(mctx, &senders[1], event_type, t3_event1,
				   NULL, sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		(void) isc_mutex_unlock(&T3_mx);
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}
	isc_task_send(task, &event);

	/*
	 * Now we fill up the task's event queue with some events.
	 */
	for (cnt = 0; cnt < T3_NEVENTS; ++cnt) {
		event = isc_event_allocate(mctx, &senders[1], event_type,
					   t3_event2, NULL, sizeof(*event));
		if (event == NULL) {
			t_info("isc_event_allocate failed\n");
			(void) isc_mutex_unlock(&T3_mx);
			isc_task_destroy(&task);
			isc_taskmgr_destroy(&tmgr);
			isc_mem_destroy(&mctx);
			return(T_UNRESOLVED);
		}
		isc_task_send(task, &event);
	}

	/*
	 * Now we register two shutdown events.
	 */
	isc_result = isc_task_onshutdown(task, t3_sde1, NULL);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_send failed %s\n",
				isc_result_totext(isc_result));
		(void) isc_mutex_unlock(&T3_mx);
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_task_onshutdown(task, t3_sde2, NULL);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_send failed %s\n",
				isc_result_totext(isc_result));
		(void) isc_mutex_unlock(&T3_mx);
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_task_shutdown(task);

	/*
	 * Now we free the task by signaling T3_cv.
	 */
	T3_flag = 1;
	isc_result = isc_condition_signal(&T3_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
				isc_result_totext(isc_result));
		++T3_nprobs;
	}

	isc_result = isc_mutex_unlock(&T3_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
				isc_result_totext(isc_result));
		++T3_nprobs;
	}


	isc_task_detach(&task);
	isc_taskmgr_destroy(&tmgr);
	isc_mem_destroy(&mctx);

	if (T3_nsdevents != 2) {
		t_info("T3_nsdevents == %d, expected 2\n", T3_nsdevents);
		++T3_nfails;
	}

	result = T_UNRESOLVED;

	if (T3_nfails != 0)
		result = T_FAIL;
	else if ((T3_nfails == 0) && (T3_nprobs == 0))
		result = T_PASS;

	return(result);
}

static const char *a3 =	"When isc_task_shutdown() is called, any shutdown "
			"events that have been requested via prior "
			"isc_task_onshutdown() calls are posted in "
			"LIFO order.";
static void
t3(void) {
	t_assert("tasks", 3, T_REQUIRED, "%s", a3);

	if (threaded)
		t_result(t_tasks3());
	else
		require_threads();
}

static isc_mutex_t	T4_mx;
static isc_condition_t	T4_cv;
static int		T4_flag;
static int		T4_nprobs;
static int		T4_nfails;

static void
t4_event1(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T4_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T4_nprobs;
	}
	while (T4_flag != 1) {
		(void) isc_condition_wait(&T4_cv, &T4_mx);
	}

	isc_result = isc_mutex_unlock(&T4_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T4_nprobs;
	}
	isc_event_free(&event);
}

static void
t4_sde(isc_task_t *task, isc_event_t *event) {
	UNUSED(task);

	/*
	 * No-op.
	 */

	isc_event_free(&event);
}

static int
t_tasks4(void) {
	int		result;
	char		*p;
	isc_mem_t	*mctx;
	isc_taskmgr_t	*tmgr;
	isc_task_t	*task;
	unsigned int	workers;
	isc_result_t	isc_result;
	isc_eventtype_t	event_type;
	isc_event_t	*event;

	T4_nprobs = 0;
	T4_nfails = 0;
	T4_flag = 0;

	event_type = 4;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);

	mctx = NULL;
	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_init(&T4_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_condition_init(&T4_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_init failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T4_mx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	tmgr = NULL;
	isc_result = isc_taskmgr_create(mctx, workers, 0, &tmgr);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T4_mx);
		(void) isc_condition_destroy(&T4_cv);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_lock(&T4_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T4_mx);
		(void) isc_condition_destroy(&T4_cv);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	task = NULL;
	isc_result = isc_task_create(tmgr, 0, &task);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %s\n",
		       isc_result_totext(isc_result));
		isc_result = isc_mutex_unlock(&T4_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_mutex_unlock failed %s\n",
			       isc_result_totext(isc_result));
			++T4_nprobs;
		}
		DESTROYLOCK(&T4_mx);
		(void) isc_condition_destroy(&T4_cv);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	/*
	 * This event causes the task to wait on T4_cv.
	 */
	event = isc_event_allocate(mctx, &senders[1], event_type, t4_event1,
				   NULL, sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		isc_result = isc_mutex_unlock(&T4_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_mutex_unlock failed %s\n",
			       isc_result_totext(isc_result));
			++T4_nprobs;
		}
		DESTROYLOCK(&T4_mx);
		isc_task_destroy(&task);
		(void) isc_condition_destroy(&T4_cv);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}
	isc_task_send(task, &event);

	isc_task_shutdown(task);

	isc_result = isc_task_onshutdown(task, t4_sde, NULL);
	if (isc_result != ISC_R_SHUTTINGDOWN) {
		t_info("isc_task_onshutdown returned %s\n",
		       isc_result_totext(isc_result));
		++T4_nfails;
	}

	/*
	 * Release the task.
	 */
	T4_flag = 1;

	isc_result = isc_condition_signal(&T4_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
				isc_result_totext(isc_result));
		++T4_nprobs;
	}

	isc_result = isc_mutex_unlock(&T4_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
				isc_result_totext(isc_result));
		++T4_nprobs;
	}

	isc_task_detach(&task);
	isc_taskmgr_destroy(&tmgr);
	isc_mem_destroy(&mctx);
	(void) isc_condition_destroy(&T4_cv);
	DESTROYLOCK(&T4_mx);

	result = T_UNRESOLVED;

	if (T4_nfails != 0)
		result = T_FAIL;
	else if ((T4_nfails == 0) && (T4_nprobs == 0))
		result = T_PASS;

	return(result);
}

static const char *a4 =
		"After isc_task_shutdown() has been called, any call to "
		"isc_task_onshutdown() will return ISC_R_SHUTTINGDOWN.";

static void
t4(void) {
	t_assert("tasks", 4, T_REQUIRED, "%s", a4);

	if (threaded)
		t_result(t_tasks4());
	else
		require_threads();
}

static int		T7_nprobs;
static int		T7_eflag;
static int		T7_sdflag;
static isc_mutex_t	T7_mx;
static isc_condition_t	T7_cv;

static int		T7_nfails;

static void
t7_event1(isc_task_t *task, isc_event_t *event) {
	UNUSED(task);

	++T7_eflag;

	isc_event_free(&event);
}

static void
t7_sde(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T7_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T7_nprobs;
	}

	++T7_sdflag;

	isc_result = isc_condition_signal(&T7_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
		       isc_result_totext(isc_result));
		++T7_nprobs;
	}

	isc_result = isc_mutex_unlock(&T7_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T7_nprobs;
	}

	isc_event_free(&event);
}

static int
t_tasks7(void) {
	int		result;
	char		*p;
	isc_mem_t	*mctx;
	isc_taskmgr_t	*tmgr;
	isc_task_t	*task;
	unsigned int	workers;
	isc_result_t	isc_result;
	isc_eventtype_t	event_type;
	isc_event_t	*event;
	isc_time_t	now;
	isc_interval_t	interval;

	T7_nprobs = 0;
	T7_nfails = 0;
	T7_sdflag = 0;
	T7_eflag = 0;

	event_type = 7;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);

	mctx = NULL;
	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_init(&T7_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_condition_init(&T7_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_init failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T7_mx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	tmgr = NULL;
	isc_result = isc_taskmgr_create(mctx, workers, 0, &tmgr);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T7_mx);
		(void) isc_condition_destroy(&T7_cv);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_lock(&T7_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T7_mx);
		(void) isc_condition_destroy(&T7_cv);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_FAIL);
	}

	task = NULL;
	isc_result = isc_task_create(tmgr, 0, &task);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T7_mx);
		(void) isc_condition_destroy(&T7_cv);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_FAIL);
	}

	isc_result = isc_task_onshutdown(task, t7_sde, NULL);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown returned %s\n",
		       isc_result_totext(isc_result));
		DESTROYLOCK(&T7_mx);
		(void) isc_condition_destroy(&T7_cv);
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	event = isc_event_allocate(mctx, &senders[1], event_type, t7_event1,
				   NULL, sizeof(*event));
	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		DESTROYLOCK(&T7_mx);
		(void) isc_condition_destroy(&T7_cv);
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}
	isc_task_send(task, &event);

	isc_task_shutdown(task);

	isc_interval_set(&interval, 5, 0);

	while (T7_sdflag == 0) {
		isc_result = isc_time_nowplusinterval(&now, &interval);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_time_nowplusinterval failed %s\n",
			       isc_result_totext(isc_result));
			DESTROYLOCK(&T7_mx);
			(void) isc_condition_destroy(&T7_cv);
			isc_task_destroy(&task);
			isc_taskmgr_destroy(&tmgr);
			isc_mem_destroy(&mctx);
			return(T_UNRESOLVED);
		}

		isc_result = isc_condition_waituntil(&T7_cv, &T7_mx, &now);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_condition_waituntil returned %s\n",
			       isc_result_totext(isc_result));
			DESTROYLOCK(&T7_mx);
			(void) isc_condition_destroy(&T7_cv);
			isc_task_destroy(&task);
			isc_taskmgr_destroy(&tmgr);
			isc_mem_destroy(&mctx);
			return(T_FAIL);
		}
	}

	isc_result = isc_mutex_unlock(&T7_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T7_nprobs;
	}

	isc_task_detach(&task);
	isc_taskmgr_destroy(&tmgr);
	isc_mem_destroy(&mctx);
	(void) isc_condition_destroy(&T7_cv);
	DESTROYLOCK(&T7_mx);

	result = T_UNRESOLVED;

	if (T7_eflag == 0)
		++T7_nfails;

	if (T7_nfails != 0)
		result = T_FAIL;
	else if ((T7_nfails == 0) && (T7_nprobs == 0))
		result = T_PASS;

	return(result);
}

static const char *a7 =	"A call to isc_task_create() creates a task that can "
			"receive events.";

static void
t7(void) {
	t_assert("tasks", 7, T_REQUIRED, "%s", a7);

	if (threaded)
		t_result(t_tasks7());
	else
		require_threads();
}

#define	T10_SENDERCNT	3
#define	T10_TYPECNT	4
#define	T10_TAGCNT	5
#define	T10_NEVENTS	(T10_SENDERCNT*T10_TYPECNT*T10_TAGCNT)
#define	T_CONTROL	99999

static int		T10_nprobs;
static int		T10_nfails;
static int		T10_startflag;
static int		T10_shutdownflag;
static int		T10_eventcnt;
static isc_mutex_t	T10_mx;
static isc_condition_t	T10_cv;

static void		*T10_purge_sender;
static isc_eventtype_t	T10_purge_type_first;
static isc_eventtype_t	T10_purge_type_last;
static void		*T10_purge_tag;
static int		T10_testrange;

static void
t10_event1(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T10_nprobs;
	}

	while (T10_startflag == 0) {
		isc_result = isc_condition_wait(&T10_cv, &T10_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_mutex_lock failed %s\n",
			       isc_result_totext(isc_result));
			++T10_nprobs;
		}
	}

	isc_result = isc_mutex_unlock(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T10_nprobs;
	}

	isc_event_free(&event);
}

static void
t10_event2(isc_task_t *task, isc_event_t *event) {

	int	sender_match;
	int	type_match;
	int	tag_match;

	UNUSED(task);

	sender_match = 0;
	type_match = 0;
	tag_match = 0;

	if (T_debug) {
		t_info("Event %p,%d,%p,%s\n",
		       event->ev_sender,
		       (int)event->ev_type,
		       event->ev_tag,
		       event->ev_attributes & ISC_EVENTATTR_NOPURGE ?
		       "NP" : "P");
	}

	if ((T10_purge_sender == NULL) ||
	    (T10_purge_sender == event->ev_sender)) {
		sender_match = 1;
	}
	if (T10_testrange == 0) {
		if (T10_purge_type_first == event->ev_type) {
			type_match = 1;
		}
	} else {
		if ((T10_purge_type_first <= event->ev_type) &&
		    (event->ev_type <= T10_purge_type_last)) {
			type_match = 1;
		}
	}
	if ((T10_purge_tag == NULL) ||
	    (T10_purge_tag == event->ev_tag)) {
		tag_match = 1;
	}

	if (sender_match && type_match && tag_match) {
		if (event->ev_attributes & ISC_EVENTATTR_NOPURGE) {
			t_info("event %p,%d,%p matched but was not purgable\n",
				event->ev_sender, (int)event->ev_type,
			       event->ev_tag);
			++T10_eventcnt;
		} else {
			t_info("*** event %p,%d,%p not purged\n",
			       event->ev_sender, (int)event->ev_type,
			       event->ev_tag);
		}
	} else {
		++T10_eventcnt;
	}
	isc_event_free(&event);
}


static void
t10_sde(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T10_nprobs;
	}

	++T10_shutdownflag;

	isc_result = isc_condition_signal(&T10_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
		       isc_result_totext(isc_result));
		++T10_nprobs;
	}

	isc_result = isc_mutex_unlock(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T10_nprobs;
	}

	isc_event_free(&event);
}

static void
t_taskpurge_x(int sender, int type, int tag, void *purge_sender,
	      int purge_type_first, int purge_type_last, void *purge_tag,
	      int exp_nevents, int *nfails, int *nprobs, int testrange)
{
	char		*p;
	isc_mem_t	*mctx;
	isc_taskmgr_t	*tmgr;
	isc_task_t	*task;
	unsigned int	workers;
	isc_result_t	isc_result;
	isc_event_t	*event;
	isc_time_t	now;
	isc_interval_t	interval;
	int		sender_cnt;
	int		type_cnt;
	int		tag_cnt;
	int		event_cnt;
	int		cnt;
	int		nevents;
	isc_event_t	*eventtab[T10_NEVENTS];


	T10_startflag = 0;
	T10_shutdownflag = 0;
	T10_eventcnt = 0;
	T10_purge_sender = purge_sender;
	T10_purge_type_first = (isc_eventtype_t) purge_type_first;
	T10_purge_type_last = (isc_eventtype_t) purge_type_last;
	T10_purge_tag = purge_tag;
	T10_testrange = testrange;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);

	mctx = NULL;
	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		++*nprobs;
		return;
	}

	isc_result = isc_mutex_init(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		++*nprobs;
		return;
	}

	isc_result = isc_condition_init(&T10_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T10_mx);
		++*nprobs;
		return;
	}

	tmgr = NULL;
	isc_result = isc_taskmgr_create(mctx, workers, 0, &tmgr);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T10_mx);
		(void) isc_condition_destroy(&T10_cv);
		++*nprobs;
		return;
	}

	task = NULL;
	isc_result = isc_task_create(tmgr, 0, &task);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %s\n",
		       isc_result_totext(isc_result));
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T10_mx);
		(void) isc_condition_destroy(&T10_cv);
		++*nprobs;
		return;
	}

	isc_result = isc_task_onshutdown(task, t10_sde, NULL);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown returned %s\n",
		       isc_result_totext(isc_result));
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T10_mx);
		(void) isc_condition_destroy(&T10_cv);
		++*nprobs;
		return;
	}

	/*
	 * Block the task on T10_cv.
	 */
	event = isc_event_allocate(mctx, (void *)1, (isc_eventtype_t)T_CONTROL,
				   t10_event1, NULL, sizeof(*event));

	if (event == NULL) {
		t_info("isc_event_allocate failed\n");
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T10_mx);
		(void) isc_condition_destroy(&T10_cv);
		++*nprobs;
		return;
	}
	isc_task_send(task, &event);

	/*
	 * Fill the task's queue with some messages with varying
	 * sender, type, tag, and purgable attribute values.
	 */

	event_cnt = 0;
	for (sender_cnt = 0; sender_cnt < T10_SENDERCNT; ++sender_cnt) {
		for (type_cnt = 0; type_cnt < T10_TYPECNT; ++type_cnt) {
			for (tag_cnt = 0; tag_cnt < T10_TAGCNT; ++tag_cnt) {
				eventtab[event_cnt] =
					isc_event_allocate(mctx,
					    &senders[sender + sender_cnt],
					    (isc_eventtype_t)(type + type_cnt),
					    t10_event2, NULL, sizeof(*event));

				if (eventtab[event_cnt] == NULL) {
					t_info("isc_event_allocate failed\n");
					isc_task_destroy(&task);
					isc_taskmgr_destroy(&tmgr);
					isc_mem_destroy(&mctx);
					DESTROYLOCK(&T10_mx);
					(void) isc_condition_destroy(&T10_cv);
					++*nprobs;
					return;
				}

				eventtab[event_cnt]->ev_tag =
					(void *)((uintptr_t)tag + tag_cnt);

				/*
				 * Make all odd message non-purgable.
				 */
				if ((sender_cnt % 2) && (type_cnt %2) &&
				    (tag_cnt %2))
					eventtab[event_cnt]->ev_attributes |=
						ISC_EVENTATTR_NOPURGE;
				++event_cnt;
			}
		}
	}

	for (cnt = 0; cnt < event_cnt; ++cnt)
		isc_task_send(task, &eventtab[cnt]);

	if (T_debug)
		t_info("%d events queued\n", cnt);

	if (testrange == 0) {
		/*
		 * We're testing isc_task_purge.
		 */
		nevents = isc_task_purge(task, purge_sender,
					(isc_eventtype_t)purge_type_first,
					purge_tag);
		if (nevents != exp_nevents) {
			t_info("*** isc_task_purge returned %d, expected %d\n",
				nevents, exp_nevents);
			++*nfails;
		} else if (T_debug)
			t_info("isc_task_purge returned %d\n", nevents);
	} else {
		/*
		 * We're testing isc_task_purgerange.
		 */
		nevents = isc_task_purgerange(task, purge_sender,
					     (isc_eventtype_t)purge_type_first,
					     (isc_eventtype_t)purge_type_last,
					     purge_tag);
		if (nevents != exp_nevents) {
			t_info("*** isc_task_purgerange returned %d, "
			       "expected %d\n", nevents, exp_nevents);
			++*nfails;
		} else if (T_debug)
			t_info("isc_task_purgerange returned %d\n", nevents);
	}

	isc_result = isc_mutex_lock(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T10_mx);
		(void) isc_condition_destroy(&T10_cv);
		++*nprobs;
		return;
	}

	/*
	 * Unblock the task, allowing event processing.
	 */
	T10_startflag = 1;
	isc_result = isc_condition_signal(&T10_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
		       isc_result_totext(isc_result));
		++*nprobs;
	}

	isc_task_shutdown(task);

	isc_interval_set(&interval, 5, 0);

	/*
	 * Wait for shutdown processing to complete.
	 */
	while (T10_shutdownflag == 0) {
		isc_result = isc_time_nowplusinterval(&now, &interval);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_time_nowplusinterval failed %s\n",
			       isc_result_totext(isc_result));
			isc_task_detach(&task);
			isc_taskmgr_destroy(&tmgr);
			isc_mem_destroy(&mctx);
			DESTROYLOCK(&T10_mx);
			(void) isc_condition_destroy(&T10_cv);
			++*nprobs;
			return;
		}

		isc_result = isc_condition_waituntil(&T10_cv, &T10_mx, &now);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_condition_waituntil returned %s\n",
			       isc_result_totext(isc_result));
			isc_task_detach(&task);
			isc_taskmgr_destroy(&tmgr);
			isc_mem_destroy(&mctx);
			DESTROYLOCK(&T10_mx);
			(void) isc_condition_destroy(&T10_cv);
			++*nfails;
			return;
		}
	}

	isc_result = isc_mutex_unlock(&T10_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++*nprobs;
	}

	isc_task_detach(&task);
	isc_taskmgr_destroy(&tmgr);
	isc_mem_destroy(&mctx);
	DESTROYLOCK(&T10_mx);
	(void) isc_condition_destroy(&T10_cv);

	if (T_debug)
		t_info("task processed %d events\n", T10_eventcnt);

	if ((T10_eventcnt + nevents) != event_cnt) {
		t_info("*** processed %d, purged %d, total %d\n",
		       T10_eventcnt, nevents, event_cnt);
		++*nfails;
	}
}

static int
t_tasks10(void) {
	int	result;

	T10_nprobs = 0;
	T10_nfails = 0;

	/*
	 * Try purging on a specific sender.
	 */
	t_info("testing purge on 2,4,8 expecting 1\n");
	t_taskpurge_x(1, 4, 7, &senders[2], 4, 4, (void *)8, 1, &T10_nfails,
		      &T10_nprobs, 0);

	/*
	 * Try purging on all senders.
	 */
	t_info("testing purge on 0,4,8 expecting 3\n");
	t_taskpurge_x(1, 4, 7, NULL, 4, 4, (void *)8, 3, &T10_nfails,
		      &T10_nprobs, 0);

	/*
	 * Try purging on all senders, specified type, all tags.
	 */
	t_info("testing purge on 0,4,0 expecting 15\n");
	t_taskpurge_x(1, 4, 7, NULL, 4, 4, NULL, 15, &T10_nfails,
		      &T10_nprobs, 0);

	/*
	 * Try purging on a specified tag, no such type.
	 */
	t_info("testing purge on 0,99,8 expecting 0\n");
	t_taskpurge_x(1, 4, 7, NULL, 99, 99, (void *)8, 0, &T10_nfails,
		      &T10_nprobs, 0);

	/*
	 * Try purging on specified sender, type, all tags.
	 */
	t_info("testing purge on 0,5,0 expecting 5\n");
	t_taskpurge_x(1, 4, 7, &senders[3], 5, 5, NULL, 5, &T10_nfails,
		      &T10_nprobs, 0);

	result = T_UNRESOLVED;

	if ((T10_nfails == 0) && (T10_nprobs == 0))
		result = T_PASS;
	else if (T10_nfails != 0)
		result = T_FAIL;

	return(result);
}

static const char *a10 =
			"A call to isc_task_purge(task, sender, type, tag) "
			"purges all events of type 'type' and with tag 'tag' "
			"not marked as unpurgable from sender from the task's "
			"queue and returns the number of events purged.";

static void
t10(void) {
	t_assert("tasks", 10, T_REQUIRED, "%s", a10);

	if (threaded)
		t_result(t_tasks10());
	else
		require_threads();
}

static int		T11_nprobs;
static int		T11_nfails;
static int		T11_startflag;
static int		T11_shutdownflag;
static int		T11_eventcnt;
static isc_mutex_t	T11_mx;
static isc_condition_t	T11_cv;

static void
t11_event1(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	while (T11_startflag == 0) {
		isc_result = isc_condition_wait(&T11_cv, &T11_mx);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_mutex_lock failed %s\n",
			       isc_result_totext(isc_result));
			++T11_nprobs;
		}
	}

	isc_result = isc_mutex_unlock(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	isc_event_free(&event);
}

static void
t11_event2(isc_task_t *task, isc_event_t *event) {
	UNUSED(task);

	++T11_eventcnt;
	isc_event_free(&event);
}


static void
t11_sde(isc_task_t *task, isc_event_t *event) {
	isc_result_t	isc_result;

	UNUSED(task);

	isc_result = isc_mutex_lock(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	++T11_shutdownflag;

	isc_result = isc_condition_signal(&T11_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	isc_result = isc_mutex_unlock(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	isc_event_free(&event);
}

static int
t_tasks11(int purgable) {
	char		*p;
	isc_mem_t	*mctx;
	isc_taskmgr_t	*tmgr;
	isc_task_t	*task;
	isc_boolean_t	rval;
	unsigned int	workers;
	isc_result_t	isc_result;
	isc_event_t	*event1;
	isc_event_t	*event2, *event2_clone;
	isc_time_t	now;
	isc_interval_t	interval;
	int		result;

	T11_startflag = 0;
	T11_shutdownflag = 0;
	T11_eventcnt = 0;

	workers = 2;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);

	mctx = NULL;
	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(T_UNRESOLVED);
	}

	isc_result = isc_mutex_init(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_condition_init(&T11_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_init failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T11_mx);
		return(T_UNRESOLVED);
	}

	tmgr = NULL;
	isc_result = isc_taskmgr_create(mctx, workers, 0, &tmgr);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %s\n",
		       isc_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T11_mx);
		(void) isc_condition_destroy(&T11_cv);
		return(T_UNRESOLVED);
	}

	task = NULL;
	isc_result = isc_task_create(tmgr, 0, &task);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_create failed %s\n",
		       isc_result_totext(isc_result));
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T11_mx);
		(void) isc_condition_destroy(&T11_cv);
		return(T_UNRESOLVED);
	}

	isc_result = isc_task_onshutdown(task, t11_sde, NULL);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_task_onshutdown returned %s\n",
		       isc_result_totext(isc_result));
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T11_mx);
		(void) isc_condition_destroy(&T11_cv);
		return(T_UNRESOLVED);
	}

	/*
	 * Block the task on T11_cv.
	 */
	event1 = isc_event_allocate(mctx, (void *)1, (isc_eventtype_t)1,
				    t11_event1, NULL, sizeof(*event1));
	if (event1 == NULL) {
		t_info("isc_event_allocate failed\n");
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T11_mx);
		(void) isc_condition_destroy(&T11_cv);
		return(T_UNRESOLVED);
	}
	isc_task_send(task, &event1);

	event2 = isc_event_allocate(mctx, (void *)1, (isc_eventtype_t)1,
				    t11_event2, NULL, sizeof(*event2));
	if (event2 == NULL) {
		t_info("isc_event_allocate failed\n");
		isc_task_destroy(&task);
		isc_taskmgr_destroy(&tmgr);
		isc_mem_destroy(&mctx);
		DESTROYLOCK(&T11_mx);
		(void) isc_condition_destroy(&T11_cv);
		return(T_UNRESOLVED);
	}
	event2_clone = event2;
	if (purgable)
		event2->ev_attributes &= ~ISC_EVENTATTR_NOPURGE;
	else
		event2->ev_attributes |= ISC_EVENTATTR_NOPURGE;

	isc_task_send(task, &event2);

	rval = isc_task_purgeevent(task, event2_clone);
	if (rval != (purgable ? ISC_TRUE : ISC_FALSE)) {
		t_info("isc_task_purgeevent returned %s, expected %s\n",
		       (rval ? "ISC_TRUE" : "ISC_FALSE"),
		       (purgable ? "ISC_TRUE" : "ISC_FALSE"));
		++T11_nfails;
	}

	isc_result = isc_mutex_lock(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_lock failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	/*
	 * Unblock the task, allowing event processing.
	 */
	T11_startflag = 1;
	isc_result = isc_condition_signal(&T11_cv);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_condition_signal failed %s\n",
				isc_result_totext(isc_result));
		++T11_nprobs;
	}

	isc_task_shutdown(task);

	isc_interval_set(&interval, 5, 0);

	/*
	 * Wait for shutdown processing to complete.
	 */
	while (T11_shutdownflag == 0) {
		isc_result = isc_time_nowplusinterval(&now, &interval);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_time_nowplusinterval failed %s\n",
			       isc_result_totext(isc_result));
			++T11_nprobs;
		}

		isc_result = isc_condition_waituntil(&T11_cv, &T11_mx, &now);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_condition_waituntil returned %s\n",
			       isc_result_totext(isc_result));
			++T11_nprobs;
		}
	}

	isc_result = isc_mutex_unlock(&T11_mx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mutex_unlock failed %s\n",
		       isc_result_totext(isc_result));
		++T11_nprobs;
	}

	isc_task_detach(&task);
	isc_taskmgr_destroy(&tmgr);
	isc_mem_destroy(&mctx);
	DESTROYLOCK(&T11_mx);
	(void) isc_condition_destroy(&T11_cv);

	if (T11_eventcnt != (purgable ? 0 : 1)) {
		t_info("Event was %s purged\n",
		       (purgable ? "not" : "unexpectedly"));
		++T11_nfails;
	}

	result = T_UNRESOLVED;

	if ((T11_nfails == 0) && (T11_nprobs == 0))
		result = T_PASS;
	else if (T11_nfails)
		result = T_FAIL;

	return(result);
}

static const char *a11 =
		"When the event is marked as purgable, a call to "
		"isc_task_purgeevent(task, event) purges the event 'event' "
		"from the task's queue and returns ISC_TRUE.";

static void
t11(void) {
	t_assert("tasks", 11, T_REQUIRED, "%s", a11);

	if (threaded)
		t_result(t_tasks11(1));
	else
		require_threads();
}

static const char *a12 =
			"When the event is not marked as purgable, a call to "
			"isc_task_purgeevent(task, event) does not purge the "
			"event 'event' from the task's queue and returns "
			"ISC_FALSE.";

static int
t_tasks12(void) {
	return(t_tasks11(0));
}

static void
t12(void) {
	t_assert("tasks", 12, T_REQUIRED, "%s", a12);

	if (threaded)
		t_result(t_tasks12());
	else
		require_threads();
}

static int	T13_nfails;
static int	T13_nprobs;

static const char *a13 =
		"A call to "
		"isc_event_purgerange(task, sender, first, last, tag) "
		"purges all events not marked unpurgable from "
		"sender 'sender' and of type within the range 'first' "
		"to 'last' inclusive from the task's event queue and "
		"returns the number of tasks purged.";

static int
t_tasks13(void) {
	int	result;

	T13_nfails = 0;
	T13_nprobs = 0;

	/*
	 * First let's try the same cases we used in t10.
	 */

	/*
	 * Try purging on a specific sender.
	 */
	t_info("testing purge on 2,4,8 expecting 1\n");
	t_taskpurge_x(1, 4, 7, &senders[2], 4, 4, (void *)8, 1,
		      &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on all senders.
	 */
	t_info("testing purge on 0,4,8 expecting 3\n");
	t_taskpurge_x(1, 4, 7, NULL, 4, 4, (void *)8, 3,
		      &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on all senders, specified type, all tags.
	 */
	t_info("testing purge on 0,4,0 expecting 15\n");
	t_taskpurge_x(1, 4, 7, NULL, 4, 4, NULL, 15, &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on a specified tag, no such type.
	 */
	t_info("testing purge on 0,99,8 expecting 0\n");
	t_taskpurge_x(1, 4, 7, NULL, 99, 99, (void *)8, 0,
		      &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on specified sender, type, all tags.
	 */
	t_info("testing purge on 3,5,0 expecting 5\n");
	t_taskpurge_x(1, 4, 7, &senders[3], 5, 5, 0, 5, &T13_nfails, &T13_nprobs, 1);

	/*
	 * Now let's try some ranges.
	 */

	t_info("testing purgerange on 2,4-5,8 expecting 2\n");
	t_taskpurge_x(1, 4, 7, &senders[2], 4, 5, (void *)8, 1,
		      &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on all senders.
	 */
	t_info("testing purge on 0,4-5,8 expecting 5\n");
	t_taskpurge_x(1, 4, 7, NULL, 4, 5, (void *)8, 5,
		      &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on all senders, specified type, all tags.
	 */
	t_info("testing purge on 0,5-6,0 expecting 28\n");
	t_taskpurge_x(1, 4, 7, NULL, 5, 6, NULL, 28, &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on a specified tag, no such type.
	 */
	t_info("testing purge on 0,99-101,8 expecting 0\n");
	t_taskpurge_x(1, 4, 7, NULL, 99, 101, (void *)8, 0,
		      &T13_nfails, &T13_nprobs, 1);

	/*
	 * Try purging on specified sender, type, all tags.
	 */
	t_info("testing purge on 3,5-6,0 expecting 10\n");
	t_taskpurge_x(1, 4, 7, &senders[3], 5, 6, NULL, 10, &T13_nfails,
		      &T13_nprobs, 1);

	result = T_UNRESOLVED;

	if ((T13_nfails == 0) && (T13_nprobs == 0))
		result = T_PASS;
	else if (T13_nfails)
		result = T_FAIL;

	return (result);
}

static void
t13(void) {
	t_assert("tasks", 13, T_REQUIRED, "%s", a13);

	if (threaded)
		t_result(t_tasks13());
	else
		require_threads();
}

#define T14_NTASKS 10
#define T14_EXCLTASK 6

int t14_exclusiveerror = ISC_R_SUCCESS;
int t14_error = 0;
int t14_done = 0;

int spin(int n);

int t14_active[T14_NTASKS];

static void
t14_callback(isc_task_t *task, isc_event_t *event) {
	int taskno = *(int *)(event->ev_arg);


	t_info("task enter %d\n", taskno);
	if (taskno == T14_EXCLTASK) {
		int	i;
		t14_exclusiveerror = isc_task_beginexclusive(task);
		if (t14_exclusiveerror == ISC_R_SUCCESS)
			t_info("task %d got exclusive access\n", taskno);
		else
			t_info("task %d failed to got exclusive access: %d\n",
				taskno, t14_exclusiveerror);
		for (i = 0; i < T14_NTASKS; i++) {
			t_info("task %d state %d\n", i , t14_active[i]);
			if (t14_active[i])
				t14_error++;
		}
		isc_task_endexclusive(task);
		t14_done = 1;
	} else {
		t14_active[taskno]++;
		(void) spin(10000000);
		t14_active[taskno]--;
	}
	t_info("task exit %d\n", taskno);
	if (t14_done) {
		isc_mem_put(event->ev_destroy_arg, event->ev_arg, sizeof (int));
		isc_event_free(&event);
	} else {
		isc_task_send(task, &event);
	}
}

int spin(int n) {
	int i;
	int r = 0;
	for (i = 0; i < n; i++) {
		r += i;
		if (r > 1000000)
			r = 0;
	}
	return (r);
}

static int
t_tasks14(void) {
	char			*p;
	isc_mem_t		*mctx;
	isc_taskmgr_t		*manager;
	isc_task_t		*tasks[T14_NTASKS];
	unsigned int		workers;
	isc_result_t		isc_result;
	int 			i;

	manager = NULL;
	mctx = NULL;

	for (i = 0; i < T14_NTASKS; i++)
		tasks[i] = NULL;

	workers = 4;
	p = t_getenv("ISC_TASK_WORKERS");
	if (p != NULL)
		workers = atoi(p);
	if (workers < 1) {
		t_info("Bad config value for ISC_TASK_WORKERS, %d\n", workers);
		return(T_UNRESOLVED);
	}

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %d\n", isc_result);
		return(T_UNRESOLVED);
	}

	isc_result = isc_taskmgr_create(mctx, workers, 0, &manager);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_taskmgr_create failed %d\n", isc_result);
		return(T_FAIL);
	}

	for (i = 0; i < T14_NTASKS; i++) {
		isc_event_t *event;
		int *v;

		isc_result = isc_task_create(manager, 0, &tasks[i]);
		if (isc_result != ISC_R_SUCCESS) {
			t_info("isc_task_create failed %d\n", isc_result);
			return(T_FAIL);
		}

		v = isc_mem_get(mctx, sizeof *v);
		if (v == NULL) {
			isc_task_detach(&tasks[i]);
			t_info("isc_mem_get failed\n");
			return(T_FAIL);
		}
		*v = i;

		event = isc_event_allocate(mctx, NULL, 1, t14_callback,
					   v, sizeof(*event));
		if (event == NULL) {
			isc_mem_put(mctx, v, sizeof *v);
			t_info("isc_event_allocate failed\n");
			return(T_UNRESOLVED);
		}
		isc_task_send(tasks[i], &event);
	}

	for (i = 0; i < T14_NTASKS; i++) {
		isc_task_detach(&tasks[i]);
	}

	isc_taskmgr_destroy(&manager);

	if (t14_exclusiveerror != ISC_R_SUCCESS || t14_error) {
		if (t14_exclusiveerror != ISC_R_SUCCESS)
			t_info("isc_task_beginexclusive() failed\n");
		if (t14_error)
			t_info("mutual access occurred\n");
		return(T_FAIL);
	}

	isc_mem_destroy(&mctx);
	return(T_PASS);
}

static void
t14(void) {
	int	result;

	t_assert("tasks", 14, T_REQUIRED, "%s",
		 "isc_task_beginexclusive() gets exclusive access");
	result = t_tasks14();
	t_result(result);
}

testspec_t	T_testlist[] = {
	{	(PFV) t1,	"basic task subsystem"	},
	{	(PFV) t2,	"maxtasks"		},
	{	(PFV) t3,	"isc_task_shutdown"	},
	{	(PFV) t4,	"isc_task_shutdown"	},
	{	(PFV) t7,	"isc_task_create"	},
	{	(PFV) t10,	"isc_task_purge"	},
	{	(PFV) t11,	"isc_task_purgeevent"	},
	{	(PFV) t12,	"isc_task_purgeevent"	},
	{	(PFV) t13,	"isc_task_purgerange"	},
	{	(PFV) t14,	"isc_task_beginexclusive" },
	{	(PFV) 0,	NULL			}
};

#ifdef WIN32
int
main(int argc, char **argv) {
	t_settests(T_testlist);
	return (t_main(argc, argv));
}
#endif
