xref: /openbsd-src/usr.bin/dig/lib/isc/task.c (revision 1fb015a8af3a7e9b85db2510147a155826ef04d9)
15185a700Sflorian /*
25185a700Sflorian  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
35185a700Sflorian  *
45185a700Sflorian  * Permission to use, copy, modify, and/or distribute this software for any
55185a700Sflorian  * purpose with or without fee is hereby granted, provided that the above
65185a700Sflorian  * copyright notice and this permission notice appear in all copies.
75185a700Sflorian  *
85185a700Sflorian  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
95185a700Sflorian  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
105185a700Sflorian  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
115185a700Sflorian  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
125185a700Sflorian  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
135185a700Sflorian  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
145185a700Sflorian  * PERFORMANCE OF THIS SOFTWARE.
155185a700Sflorian  */
165185a700Sflorian 
175185a700Sflorian /*! \file
185185a700Sflorian  * \author Principal Author: Bob Halley
195185a700Sflorian  */
205185a700Sflorian 
215185a700Sflorian /*
225185a700Sflorian  * XXXRTH  Need to document the states a task can be in, and the rules
235185a700Sflorian  * for changing states.
245185a700Sflorian  */
255185a700Sflorian 
265185a700Sflorian #include <stdlib.h>
27c576c3baSflorian #include <string.h>
28c576c3baSflorian #include <time.h>
29c576c3baSflorian 
305185a700Sflorian #include <isc/event.h>
315185a700Sflorian #include <isc/task.h>
325185a700Sflorian #include <isc/util.h>
335185a700Sflorian 
345185a700Sflorian #include "task_p.h"
355185a700Sflorian 
365185a700Sflorian /***
375185a700Sflorian  *** Types.
385185a700Sflorian  ***/
395185a700Sflorian 
405185a700Sflorian typedef enum {
415185a700Sflorian 	task_state_idle, task_state_ready, task_state_running,
425185a700Sflorian 	task_state_done
435185a700Sflorian } task_state_t;
445185a700Sflorian 
458b553854Sflorian struct isc_task {
465185a700Sflorian 	/* Not locked. */
478b553854Sflorian 	isc_taskmgr_t *		manager;
485185a700Sflorian 	/* Locked by task lock. */
495185a700Sflorian 	task_state_t			state;
505185a700Sflorian 	unsigned int			references;
515185a700Sflorian 	isc_eventlist_t			events;
525185a700Sflorian 	isc_eventlist_t			on_shutdown;
535185a700Sflorian 	unsigned int			nevents;
545185a700Sflorian 	unsigned int			quantum;
555185a700Sflorian 	unsigned int			flags;
56c576c3baSflorian 	time_t			now;
575185a700Sflorian 	char				name[16];
585185a700Sflorian 	void *				tag;
595185a700Sflorian 	/* Locked by task manager lock. */
608b553854Sflorian 	LINK(isc_task_t)		link;
618b553854Sflorian 	LINK(isc_task_t)		ready_link;
628b553854Sflorian 	LINK(isc_task_t)		ready_priority_link;
635185a700Sflorian };
645185a700Sflorian 
655185a700Sflorian #define TASK_F_SHUTTINGDOWN		0x01
665185a700Sflorian #define TASK_F_PRIVILEGED		0x02
675185a700Sflorian 
685185a700Sflorian #define TASK_SHUTTINGDOWN(t)		(((t)->flags & TASK_F_SHUTTINGDOWN) \
695185a700Sflorian 					 != 0)
705185a700Sflorian 
718b553854Sflorian typedef ISC_LIST(isc_task_t)	isc_tasklist_t;
725185a700Sflorian 
738b553854Sflorian struct isc_taskmgr {
745185a700Sflorian 	/* Not locked. */
755185a700Sflorian 	/* Locked by task manager lock. */
765185a700Sflorian 	unsigned int			default_quantum;
778b553854Sflorian 	LIST(isc_task_t)		tasks;
788b553854Sflorian 	isc_tasklist_t			ready_tasks;
798b553854Sflorian 	isc_tasklist_t			ready_priority_tasks;
805185a700Sflorian 	isc_taskmgrmode_t		mode;
815185a700Sflorian 	unsigned int			tasks_running;
825185a700Sflorian 	unsigned int			tasks_ready;
83*1fb015a8Sflorian 	int			pause_requested;
84*1fb015a8Sflorian 	int			exclusive_requested;
85*1fb015a8Sflorian 	int			exiting;
865185a700Sflorian 
875185a700Sflorian 	/*
885185a700Sflorian 	 * Multiple threads can read/write 'excl' at the same time, so we need
895185a700Sflorian 	 * to protect the access.  We can't use 'lock' since isc_task_detach()
905185a700Sflorian 	 * will try to acquire it.
915185a700Sflorian 	 */
928b553854Sflorian 	isc_task_t			*excl;
935185a700Sflorian 	unsigned int			refs;
945185a700Sflorian };
955185a700Sflorian 
965185a700Sflorian #define DEFAULT_TASKMGR_QUANTUM		10
975185a700Sflorian #define DEFAULT_DEFAULT_QUANTUM		5
985185a700Sflorian #define FINISHED(m)			((m)->exiting && EMPTY((m)->tasks))
995185a700Sflorian 
1008b553854Sflorian static isc_taskmgr_t *taskmgr = NULL;
1015185a700Sflorian 
102*1fb015a8Sflorian static inline int
1038b553854Sflorian empty_readyq(isc_taskmgr_t *manager);
1045185a700Sflorian 
1058b553854Sflorian static inline isc_task_t *
1068b553854Sflorian pop_readyq(isc_taskmgr_t *manager);
1075185a700Sflorian 
1085185a700Sflorian static inline void
1098b553854Sflorian push_readyq(isc_taskmgr_t *manager, isc_task_t *task);
1108b553854Sflorian 
1115185a700Sflorian /***
1125185a700Sflorian  *** Tasks.
1135185a700Sflorian  ***/
1145185a700Sflorian 
1155185a700Sflorian static void
task_finished(isc_task_t * task)1168b553854Sflorian task_finished(isc_task_t *task) {
1178b553854Sflorian 	isc_taskmgr_t *manager = task->manager;
1185185a700Sflorian 
1195185a700Sflorian 	REQUIRE(EMPTY(task->events));
1205185a700Sflorian 	REQUIRE(task->nevents == 0);
1215185a700Sflorian 	REQUIRE(EMPTY(task->on_shutdown));
1225185a700Sflorian 	REQUIRE(task->references == 0);
1235185a700Sflorian 	REQUIRE(task->state == task_state_done);
1245185a700Sflorian 
1255185a700Sflorian 	UNLINK(manager->tasks, task, link);
1265185a700Sflorian 
1275185a700Sflorian 	free(task);
1285185a700Sflorian }
1295185a700Sflorian 
1305185a700Sflorian isc_result_t
isc_task_create(isc_taskmgr_t * manager,unsigned int quantum,isc_task_t ** taskp)1318b553854Sflorian isc_task_create(isc_taskmgr_t *manager, unsigned int quantum,
1325185a700Sflorian 		 isc_task_t **taskp)
1335185a700Sflorian {
1348b553854Sflorian 	isc_task_t *task;
135*1fb015a8Sflorian 	int exiting;
1365185a700Sflorian 
1375185a700Sflorian 	REQUIRE(taskp != NULL && *taskp == NULL);
1385185a700Sflorian 
1395185a700Sflorian 	task = malloc(sizeof(*task));
1405185a700Sflorian 	if (task == NULL)
1415185a700Sflorian 		return (ISC_R_NOMEMORY);
1425185a700Sflorian 	task->manager = manager;
1435185a700Sflorian 	task->state = task_state_idle;
1445185a700Sflorian 	task->references = 1;
1455185a700Sflorian 	INIT_LIST(task->events);
1465185a700Sflorian 	INIT_LIST(task->on_shutdown);
1475185a700Sflorian 	task->nevents = 0;
1485185a700Sflorian 	task->quantum = quantum;
1495185a700Sflorian 	task->flags = 0;
1505185a700Sflorian 	task->now = 0;
1515185a700Sflorian 	memset(task->name, 0, sizeof(task->name));
1525185a700Sflorian 	task->tag = NULL;
1535185a700Sflorian 	INIT_LINK(task, link);
1545185a700Sflorian 	INIT_LINK(task, ready_link);
1555185a700Sflorian 	INIT_LINK(task, ready_priority_link);
1565185a700Sflorian 
157*1fb015a8Sflorian 	exiting = 0;
1585185a700Sflorian 	if (!manager->exiting) {
1595185a700Sflorian 		if (task->quantum == 0)
1605185a700Sflorian 			task->quantum = manager->default_quantum;
1615185a700Sflorian 		APPEND(manager->tasks, task, link);
1625185a700Sflorian 	} else
163*1fb015a8Sflorian 		exiting = 1;
1645185a700Sflorian 
1655185a700Sflorian 	if (exiting) {
1665185a700Sflorian 		free(task);
1675185a700Sflorian 		return (ISC_R_SHUTTINGDOWN);
1685185a700Sflorian 	}
1695185a700Sflorian 
1705185a700Sflorian 	*taskp = (isc_task_t *)task;
1715185a700Sflorian 	return (ISC_R_SUCCESS);
1725185a700Sflorian }
1735185a700Sflorian 
1745185a700Sflorian void
isc_task_attach(isc_task_t * source0,isc_task_t ** targetp)1758b553854Sflorian isc_task_attach(isc_task_t *source0, isc_task_t **targetp) {
1768b553854Sflorian 	isc_task_t *source = (isc_task_t *)source0;
1775185a700Sflorian 
1785185a700Sflorian 	/*
1795185a700Sflorian 	 * Attach *targetp to source.
1805185a700Sflorian 	 */
1815185a700Sflorian 
1825185a700Sflorian 	REQUIRE(targetp != NULL && *targetp == NULL);
1835185a700Sflorian 
1845185a700Sflorian 	source->references++;
1855185a700Sflorian 
1865185a700Sflorian 	*targetp = (isc_task_t *)source;
1875185a700Sflorian }
1885185a700Sflorian 
189*1fb015a8Sflorian static inline int
task_shutdown(isc_task_t * task)1908b553854Sflorian task_shutdown(isc_task_t *task) {
191*1fb015a8Sflorian 	int was_idle = 0;
1925185a700Sflorian 	isc_event_t *event, *prev;
1935185a700Sflorian 
1945185a700Sflorian 	/*
1955185a700Sflorian 	 * Caller must be holding the task's lock.
1965185a700Sflorian 	 */
1975185a700Sflorian 
1985185a700Sflorian 	if (! TASK_SHUTTINGDOWN(task)) {
1995185a700Sflorian 		task->flags |= TASK_F_SHUTTINGDOWN;
2005185a700Sflorian 		if (task->state == task_state_idle) {
2015185a700Sflorian 			INSIST(EMPTY(task->events));
2025185a700Sflorian 			task->state = task_state_ready;
203*1fb015a8Sflorian 			was_idle = 1;
2045185a700Sflorian 		}
2055185a700Sflorian 		INSIST(task->state == task_state_ready ||
2065185a700Sflorian 		       task->state == task_state_running);
2075185a700Sflorian 
2085185a700Sflorian 		/*
2095185a700Sflorian 		 * Note that we post shutdown events LIFO.
2105185a700Sflorian 		 */
2115185a700Sflorian 		for (event = TAIL(task->on_shutdown);
2125185a700Sflorian 		     event != NULL;
2135185a700Sflorian 		     event = prev) {
2145185a700Sflorian 			prev = PREV(event, ev_link);
2155185a700Sflorian 			DEQUEUE(task->on_shutdown, event, ev_link);
2165185a700Sflorian 			ENQUEUE(task->events, event, ev_link);
2175185a700Sflorian 			task->nevents++;
2185185a700Sflorian 		}
2195185a700Sflorian 	}
2205185a700Sflorian 
2215185a700Sflorian 	return (was_idle);
2225185a700Sflorian }
2235185a700Sflorian 
2245185a700Sflorian /*
2255185a700Sflorian  * Moves a task onto the appropriate run queue.
2265185a700Sflorian  *
2275185a700Sflorian  * Caller must NOT hold manager lock.
2285185a700Sflorian  */
2295185a700Sflorian static inline void
task_ready(isc_task_t * task)2308b553854Sflorian task_ready(isc_task_t *task) {
2318b553854Sflorian 	isc_taskmgr_t *manager = task->manager;
2325185a700Sflorian 
2335185a700Sflorian 	REQUIRE(task->state == task_state_ready);
2345185a700Sflorian 
2355185a700Sflorian 	push_readyq(manager, task);
2365185a700Sflorian }
2375185a700Sflorian 
238*1fb015a8Sflorian static inline int
task_detach(isc_task_t * task)2398b553854Sflorian task_detach(isc_task_t *task) {
2405185a700Sflorian 
2415185a700Sflorian 	/*
2425185a700Sflorian 	 * Caller must be holding the task lock.
2435185a700Sflorian 	 */
2445185a700Sflorian 
2455185a700Sflorian 	REQUIRE(task->references > 0);
2465185a700Sflorian 
2475185a700Sflorian 	task->references--;
2485185a700Sflorian 	if (task->references == 0 && task->state == task_state_idle) {
2495185a700Sflorian 		INSIST(EMPTY(task->events));
2505185a700Sflorian 		/*
2515185a700Sflorian 		 * There are no references to this task, and no
2525185a700Sflorian 		 * pending events.  We could try to optimize and
2535185a700Sflorian 		 * either initiate shutdown or clean up the task,
2545185a700Sflorian 		 * depending on its state, but it's easier to just
2555185a700Sflorian 		 * make the task ready and allow run() or the event
2565185a700Sflorian 		 * loop to deal with shutting down and termination.
2575185a700Sflorian 		 */
2585185a700Sflorian 		task->state = task_state_ready;
259*1fb015a8Sflorian 		return (1);
2605185a700Sflorian 	}
2615185a700Sflorian 
262*1fb015a8Sflorian 	return (0);
2635185a700Sflorian }
2645185a700Sflorian 
2655185a700Sflorian void
isc_task_detach(isc_task_t ** taskp)2668b553854Sflorian isc_task_detach(isc_task_t **taskp) {
2678b553854Sflorian 	isc_task_t *task;
268*1fb015a8Sflorian 	int was_idle;
2695185a700Sflorian 
2705185a700Sflorian 	/*
2715185a700Sflorian 	 * Detach *taskp from its task.
2725185a700Sflorian 	 */
2735185a700Sflorian 
2745185a700Sflorian 	REQUIRE(taskp != NULL);
2758b553854Sflorian 	task = (isc_task_t *)*taskp;
2765185a700Sflorian 
2775185a700Sflorian 	was_idle = task_detach(task);
2785185a700Sflorian 
2795185a700Sflorian 	if (was_idle)
2805185a700Sflorian 		task_ready(task);
2815185a700Sflorian 
2825185a700Sflorian 	*taskp = NULL;
2835185a700Sflorian }
2845185a700Sflorian 
285*1fb015a8Sflorian static inline int
task_send(isc_task_t * task,isc_event_t ** eventp)2868b553854Sflorian task_send(isc_task_t *task, isc_event_t **eventp) {
287*1fb015a8Sflorian 	int was_idle = 0;
2885185a700Sflorian 	isc_event_t *event;
2895185a700Sflorian 
2905185a700Sflorian 	/*
2915185a700Sflorian 	 * Caller must be holding the task lock.
2925185a700Sflorian 	 */
2935185a700Sflorian 
2945185a700Sflorian 	REQUIRE(eventp != NULL);
2955185a700Sflorian 	event = *eventp;
2965185a700Sflorian 	REQUIRE(event != NULL);
2975185a700Sflorian 	REQUIRE(event->ev_type > 0);
2985185a700Sflorian 	REQUIRE(task->state != task_state_done);
2995185a700Sflorian 	REQUIRE(!ISC_LINK_LINKED(event, ev_ratelink));
3005185a700Sflorian 
3015185a700Sflorian 	if (task->state == task_state_idle) {
302*1fb015a8Sflorian 		was_idle = 1;
3035185a700Sflorian 		INSIST(EMPTY(task->events));
3045185a700Sflorian 		task->state = task_state_ready;
3055185a700Sflorian 	}
3065185a700Sflorian 	INSIST(task->state == task_state_ready ||
3075185a700Sflorian 	       task->state == task_state_running);
3085185a700Sflorian 	ENQUEUE(task->events, event, ev_link);
3095185a700Sflorian 	task->nevents++;
3105185a700Sflorian 	*eventp = NULL;
3115185a700Sflorian 
3125185a700Sflorian 	return (was_idle);
3135185a700Sflorian }
3145185a700Sflorian 
3155185a700Sflorian void
isc_task_send(isc_task_t * task,isc_event_t ** eventp)3168b553854Sflorian isc_task_send(isc_task_t *task, isc_event_t **eventp) {
317*1fb015a8Sflorian 	int was_idle;
3185185a700Sflorian 
3195185a700Sflorian 	/*
3205185a700Sflorian 	 * Send '*event' to 'task'.
3215185a700Sflorian 	 */
3225185a700Sflorian 
3235185a700Sflorian 	/*
3245185a700Sflorian 	 * We're trying hard to hold locks for as short a time as possible.
3255185a700Sflorian 	 * We're also trying to hold as few locks as possible.  This is why
3265185a700Sflorian 	 * some processing is deferred until after the lock is released.
3275185a700Sflorian 	 */
3285185a700Sflorian 	was_idle = task_send(task, eventp);
3295185a700Sflorian 
3305185a700Sflorian 	if (was_idle) {
3315185a700Sflorian 		/*
3325185a700Sflorian 		 * We need to add this task to the ready queue.
3335185a700Sflorian 		 *
3345185a700Sflorian 		 * We've waited until now to do it because making a task
3355185a700Sflorian 		 * ready requires locking the manager.  If we tried to do
3365185a700Sflorian 		 * this while holding the task lock, we could deadlock.
3375185a700Sflorian 		 *
3385185a700Sflorian 		 * We've changed the state to ready, so no one else will
3395185a700Sflorian 		 * be trying to add this task to the ready queue.  The
3405185a700Sflorian 		 * only way to leave the ready state is by executing the
3415185a700Sflorian 		 * task.  It thus doesn't matter if events are added,
3425185a700Sflorian 		 * removed, or a shutdown is started in the interval
3435185a700Sflorian 		 * between the time we released the task lock, and the time
3445185a700Sflorian 		 * we add the task to the ready queue.
3455185a700Sflorian 		 */
3465185a700Sflorian 		task_ready(task);
3475185a700Sflorian 	}
3485185a700Sflorian }
3495185a700Sflorian 
3505185a700Sflorian void
isc_task_sendanddetach(isc_task_t ** taskp,isc_event_t ** eventp)3518b553854Sflorian isc_task_sendanddetach(isc_task_t **taskp, isc_event_t **eventp) {
352*1fb015a8Sflorian 	int idle1, idle2;
3538b553854Sflorian 	isc_task_t *task;
3545185a700Sflorian 
3555185a700Sflorian 	/*
3565185a700Sflorian 	 * Send '*event' to '*taskp' and then detach '*taskp' from its
3575185a700Sflorian 	 * task.
3585185a700Sflorian 	 */
3595185a700Sflorian 
3605185a700Sflorian 	REQUIRE(taskp != NULL);
3618b553854Sflorian 	task = (isc_task_t *)*taskp;
3625185a700Sflorian 
3635185a700Sflorian 	idle1 = task_send(task, eventp);
3645185a700Sflorian 	idle2 = task_detach(task);
3655185a700Sflorian 
3665185a700Sflorian 	/*
3675185a700Sflorian 	 * If idle1, then idle2 shouldn't be true as well since we're holding
3685185a700Sflorian 	 * the task lock, and thus the task cannot switch from ready back to
3695185a700Sflorian 	 * idle.
3705185a700Sflorian 	 */
3715185a700Sflorian 	INSIST(!(idle1 && idle2));
3725185a700Sflorian 
3735185a700Sflorian 	if (idle1 || idle2)
3745185a700Sflorian 		task_ready(task);
3755185a700Sflorian 
3765185a700Sflorian 	*taskp = NULL;
3775185a700Sflorian }
3785185a700Sflorian 
3795185a700Sflorian #define PURGE_OK(event)	(((event)->ev_attributes & ISC_EVENTATTR_NOPURGE) == 0)
3805185a700Sflorian 
3815185a700Sflorian static unsigned int
dequeue_events(isc_task_t * task,void * sender,isc_eventtype_t first,isc_eventtype_t last,void * tag,isc_eventlist_t * events,int purging)3828b553854Sflorian dequeue_events(isc_task_t *task, void *sender, isc_eventtype_t first,
3835185a700Sflorian 	       isc_eventtype_t last, void *tag,
384*1fb015a8Sflorian 	       isc_eventlist_t *events, int purging)
3855185a700Sflorian {
3865185a700Sflorian 	isc_event_t *event, *next_event;
3875185a700Sflorian 	unsigned int count = 0;
3885185a700Sflorian 
3895185a700Sflorian 	REQUIRE(last >= first);
3905185a700Sflorian 
3915185a700Sflorian 	/*
3925185a700Sflorian 	 * Events matching 'sender', whose type is >= first and <= last, and
3935185a700Sflorian 	 * whose tag is 'tag' will be dequeued.  If 'purging', matching events
3945185a700Sflorian 	 * which are marked as unpurgable will not be dequeued.
3955185a700Sflorian 	 *
3965185a700Sflorian 	 * sender == NULL means "any sender", and tag == NULL means "any tag".
3975185a700Sflorian 	 */
3985185a700Sflorian 
3995185a700Sflorian 	for (event = HEAD(task->events); event != NULL; event = next_event) {
4005185a700Sflorian 		next_event = NEXT(event, ev_link);
4015185a700Sflorian 		if (event->ev_type >= first && event->ev_type <= last &&
4025185a700Sflorian 		    (sender == NULL || event->ev_sender == sender) &&
4035185a700Sflorian 		    (tag == NULL || event->ev_tag == tag) &&
4045185a700Sflorian 		    (!purging || PURGE_OK(event))) {
4055185a700Sflorian 			DEQUEUE(task->events, event, ev_link);
4065185a700Sflorian 			task->nevents--;
4075185a700Sflorian 			ENQUEUE(*events, event, ev_link);
4085185a700Sflorian 			count++;
4095185a700Sflorian 		}
4105185a700Sflorian 	}
4115185a700Sflorian 
4125185a700Sflorian 	return (count);
4135185a700Sflorian }
4145185a700Sflorian 
4155185a700Sflorian unsigned int
isc_task_purgerange(isc_task_t * task,void * sender,isc_eventtype_t first,isc_eventtype_t last,void * tag)4168b553854Sflorian isc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first,
4175185a700Sflorian 		     isc_eventtype_t last, void *tag)
4185185a700Sflorian {
4195185a700Sflorian 	unsigned int count;
4205185a700Sflorian 	isc_eventlist_t events;
4215185a700Sflorian 	isc_event_t *event, *next_event;
4225185a700Sflorian 
4235185a700Sflorian 	/*
4245185a700Sflorian 	 * Purge events from a task's event queue.
4255185a700Sflorian 	 */
4265185a700Sflorian 
4275185a700Sflorian 	ISC_LIST_INIT(events);
4285185a700Sflorian 
4295185a700Sflorian 	count = dequeue_events(task, sender, first, last, tag, &events,
430*1fb015a8Sflorian 			       1);
4315185a700Sflorian 
4325185a700Sflorian 	for (event = HEAD(events); event != NULL; event = next_event) {
4335185a700Sflorian 		next_event = NEXT(event, ev_link);
4345185a700Sflorian 		ISC_LIST_UNLINK(events, event, ev_link);
4355185a700Sflorian 		isc_event_free(&event);
4365185a700Sflorian 	}
4375185a700Sflorian 
4385185a700Sflorian 	/*
4395185a700Sflorian 	 * Note that purging never changes the state of the task.
4405185a700Sflorian 	 */
4415185a700Sflorian 
4425185a700Sflorian 	return (count);
4435185a700Sflorian }
4445185a700Sflorian 
4455185a700Sflorian void
isc_task_setname(isc_task_t * task,const char * name,void * tag)4468b553854Sflorian isc_task_setname(isc_task_t *task, const char *name, void *tag) {
4475185a700Sflorian 	/*
4485185a700Sflorian 	 * Name 'task'.
4495185a700Sflorian 	 */
4505185a700Sflorian 
4515185a700Sflorian 	strlcpy(task->name, name, sizeof(task->name));
4525185a700Sflorian 	task->tag = tag;
4535185a700Sflorian }
4545185a700Sflorian 
4555185a700Sflorian /***
4565185a700Sflorian  *** Task Manager.
4575185a700Sflorian  ***/
4585185a700Sflorian 
4595185a700Sflorian /*
460*1fb015a8Sflorian  * Return 1 if the current ready list for the manager, which is
4615185a700Sflorian  * either ready_tasks or the ready_priority_tasks, depending on whether
4625185a700Sflorian  * the manager is currently in normal or privileged execution mode.
4635185a700Sflorian  *
4645185a700Sflorian  * Caller must hold the task manager lock.
4655185a700Sflorian  */
466*1fb015a8Sflorian static inline int
empty_readyq(isc_taskmgr_t * manager)4678b553854Sflorian empty_readyq(isc_taskmgr_t *manager) {
4688b553854Sflorian 	isc_tasklist_t queue;
4695185a700Sflorian 
4705185a700Sflorian 	if (manager->mode == isc_taskmgrmode_normal)
4715185a700Sflorian 		queue = manager->ready_tasks;
4725185a700Sflorian 	else
4735185a700Sflorian 		queue = manager->ready_priority_tasks;
4745185a700Sflorian 
475*1fb015a8Sflorian 	return (EMPTY(queue));
4765185a700Sflorian }
4775185a700Sflorian 
4785185a700Sflorian /*
4795185a700Sflorian  * Dequeue and return a pointer to the first task on the current ready
4805185a700Sflorian  * list for the manager.
4815185a700Sflorian  * If the task is privileged, dequeue it from the other ready list
4825185a700Sflorian  * as well.
4835185a700Sflorian  *
4845185a700Sflorian  * Caller must hold the task manager lock.
4855185a700Sflorian  */
4868b553854Sflorian static inline isc_task_t *
pop_readyq(isc_taskmgr_t * manager)4878b553854Sflorian pop_readyq(isc_taskmgr_t *manager) {
4888b553854Sflorian 	isc_task_t *task;
4895185a700Sflorian 
4905185a700Sflorian 	if (manager->mode == isc_taskmgrmode_normal)
4915185a700Sflorian 		task = HEAD(manager->ready_tasks);
4925185a700Sflorian 	else
4935185a700Sflorian 		task = HEAD(manager->ready_priority_tasks);
4945185a700Sflorian 
4955185a700Sflorian 	if (task != NULL) {
4965185a700Sflorian 		DEQUEUE(manager->ready_tasks, task, ready_link);
4975185a700Sflorian 		if (ISC_LINK_LINKED(task, ready_priority_link))
4985185a700Sflorian 			DEQUEUE(manager->ready_priority_tasks, task,
4995185a700Sflorian 				ready_priority_link);
5005185a700Sflorian 	}
5015185a700Sflorian 
5025185a700Sflorian 	return (task);
5035185a700Sflorian }
5045185a700Sflorian 
5055185a700Sflorian /*
5065185a700Sflorian  * Push 'task' onto the ready_tasks queue.  If 'task' has the privilege
5075185a700Sflorian  * flag set, then also push it onto the ready_priority_tasks queue.
5085185a700Sflorian  *
5095185a700Sflorian  * Caller must hold the task manager lock.
5105185a700Sflorian  */
5115185a700Sflorian static inline void
push_readyq(isc_taskmgr_t * manager,isc_task_t * task)5128b553854Sflorian push_readyq(isc_taskmgr_t *manager, isc_task_t *task) {
5135185a700Sflorian 	ENQUEUE(manager->ready_tasks, task, ready_link);
5145185a700Sflorian 	if ((task->flags & TASK_F_PRIVILEGED) != 0)
5155185a700Sflorian 		ENQUEUE(manager->ready_priority_tasks, task,
5165185a700Sflorian 			ready_priority_link);
5175185a700Sflorian 	manager->tasks_ready++;
5185185a700Sflorian }
5195185a700Sflorian 
5205185a700Sflorian static void
dispatch(isc_taskmgr_t * manager)5218b553854Sflorian dispatch(isc_taskmgr_t *manager) {
5228b553854Sflorian 	isc_task_t *task;
5235185a700Sflorian 	unsigned int total_dispatch_count = 0;
5248b553854Sflorian 	isc_tasklist_t new_ready_tasks;
5258b553854Sflorian 	isc_tasklist_t new_priority_tasks;
5265185a700Sflorian 	unsigned int tasks_ready = 0;
5275185a700Sflorian 
5285185a700Sflorian 	ISC_LIST_INIT(new_ready_tasks);
5295185a700Sflorian 	ISC_LIST_INIT(new_priority_tasks);
5305185a700Sflorian 
5315185a700Sflorian 	while (!FINISHED(manager)) {
5325185a700Sflorian 		if (total_dispatch_count >= DEFAULT_TASKMGR_QUANTUM ||
5335185a700Sflorian 		    empty_readyq(manager))
5345185a700Sflorian 			break;
5355185a700Sflorian 
5365185a700Sflorian 		task = pop_readyq(manager);
5375185a700Sflorian 		if (task != NULL) {
5385185a700Sflorian 			unsigned int dispatch_count = 0;
539*1fb015a8Sflorian 			int done = 0;
540*1fb015a8Sflorian 			int requeue = 0;
541*1fb015a8Sflorian 			int finished = 0;
5425185a700Sflorian 			isc_event_t *event;
5435185a700Sflorian 
5445185a700Sflorian 			/*
5455185a700Sflorian 			 * Note we only unlock the manager lock if we actually
5465185a700Sflorian 			 * have a task to do.  We must reacquire the manager
5475185a700Sflorian 			 * lock before exiting the 'if (task != NULL)' block.
5485185a700Sflorian 			 */
5495185a700Sflorian 			manager->tasks_ready--;
5505185a700Sflorian 			manager->tasks_running++;
5515185a700Sflorian 
5525185a700Sflorian 			INSIST(task->state == task_state_ready);
5535185a700Sflorian 			task->state = task_state_running;
554c576c3baSflorian 			time(&task->now);
5555185a700Sflorian 			do {
5565185a700Sflorian 				if (!EMPTY(task->events)) {
5575185a700Sflorian 					event = HEAD(task->events);
5585185a700Sflorian 					DEQUEUE(task->events, event, ev_link);
5595185a700Sflorian 					task->nevents--;
5605185a700Sflorian 
5615185a700Sflorian 					/*
5625185a700Sflorian 					 * Execute the event action.
5635185a700Sflorian 					 */
5645185a700Sflorian 					if (event->ev_action != NULL) {
5655185a700Sflorian 						(event->ev_action)(
5665185a700Sflorian 							(isc_task_t *)task,
5675185a700Sflorian 							event);
5685185a700Sflorian 					}
5695185a700Sflorian 					dispatch_count++;
5705185a700Sflorian 					total_dispatch_count++;
5715185a700Sflorian 				}
5725185a700Sflorian 
5735185a700Sflorian 				if (task->references == 0 &&
5745185a700Sflorian 				    EMPTY(task->events) &&
5755185a700Sflorian 				    !TASK_SHUTTINGDOWN(task)) {
576*1fb015a8Sflorian 					int was_idle;
5775185a700Sflorian 
5785185a700Sflorian 					/*
5795185a700Sflorian 					 * There are no references and no
5805185a700Sflorian 					 * pending events for this task,
5815185a700Sflorian 					 * which means it will not become
5825185a700Sflorian 					 * runnable again via an external
5835185a700Sflorian 					 * action (such as sending an event
5845185a700Sflorian 					 * or detaching).
5855185a700Sflorian 					 *
5865185a700Sflorian 					 * We initiate shutdown to prevent
5875185a700Sflorian 					 * it from becoming a zombie.
5885185a700Sflorian 					 *
5895185a700Sflorian 					 * We do this here instead of in
5905185a700Sflorian 					 * the "if EMPTY(task->events)" block
5915185a700Sflorian 					 * below because:
5925185a700Sflorian 					 *
5935185a700Sflorian 					 *	If we post no shutdown events,
5945185a700Sflorian 					 *	we want the task to finish.
5955185a700Sflorian 					 *
5965185a700Sflorian 					 *	If we did post shutdown events,
5975185a700Sflorian 					 *	will still want the task's
5985185a700Sflorian 					 *	quantum to be applied.
5995185a700Sflorian 					 */
6005185a700Sflorian 					was_idle = task_shutdown(task);
6015185a700Sflorian 					INSIST(!was_idle);
6025185a700Sflorian 				}
6035185a700Sflorian 
6045185a700Sflorian 				if (EMPTY(task->events)) {
6055185a700Sflorian 					/*
6065185a700Sflorian 					 * Nothing else to do for this task
6075185a700Sflorian 					 * right now.
6085185a700Sflorian 					 */
6095185a700Sflorian 					if (task->references == 0 &&
6105185a700Sflorian 					    TASK_SHUTTINGDOWN(task)) {
6115185a700Sflorian 						/*
6125185a700Sflorian 						 * The task is done.
6135185a700Sflorian 						 */
614*1fb015a8Sflorian 						finished = 1;
6155185a700Sflorian 						task->state = task_state_done;
6165185a700Sflorian 					} else
6175185a700Sflorian 						task->state = task_state_idle;
618*1fb015a8Sflorian 					done = 1;
6195185a700Sflorian 				} else if (dispatch_count >= task->quantum) {
6205185a700Sflorian 					/*
6215185a700Sflorian 					 * Our quantum has expired, but
6225185a700Sflorian 					 * there is more work to be done.
6235185a700Sflorian 					 * We'll requeue it to the ready
6245185a700Sflorian 					 * queue later.
6255185a700Sflorian 					 *
6265185a700Sflorian 					 * We don't check quantum until
6275185a700Sflorian 					 * dispatching at least one event,
6285185a700Sflorian 					 * so the minimum quantum is one.
6295185a700Sflorian 					 */
6305185a700Sflorian 					task->state = task_state_ready;
631*1fb015a8Sflorian 					requeue = 1;
632*1fb015a8Sflorian 					done = 1;
6335185a700Sflorian 				}
6345185a700Sflorian 			} while (!done);
6355185a700Sflorian 
6365185a700Sflorian 			if (finished)
6375185a700Sflorian 				task_finished(task);
6385185a700Sflorian 
6395185a700Sflorian 			manager->tasks_running--;
6405185a700Sflorian 			if (requeue) {
6415185a700Sflorian 				/*
6425185a700Sflorian 				 * We know we're awake, so we don't have
6435185a700Sflorian 				 * to wakeup any sleeping threads if the
6445185a700Sflorian 				 * ready queue is empty before we requeue.
6455185a700Sflorian 				 *
6465185a700Sflorian 				 * A possible optimization if the queue is
6475185a700Sflorian 				 * empty is to 'goto' the 'if (task != NULL)'
6485185a700Sflorian 				 * block, avoiding the ENQUEUE of the task
6495185a700Sflorian 				 * and the subsequent immediate DEQUEUE
6505185a700Sflorian 				 * (since it is the only executable task).
6515185a700Sflorian 				 * We don't do this because then we'd be
6525185a700Sflorian 				 * skipping the exit_requested check.  The
6535185a700Sflorian 				 * cost of ENQUEUE is low anyway, especially
6545185a700Sflorian 				 * when you consider that we'd have to do
6555185a700Sflorian 				 * an extra EMPTY check to see if we could
6565185a700Sflorian 				 * do the optimization.  If the ready queue
6575185a700Sflorian 				 * were usually nonempty, the 'optimization'
6585185a700Sflorian 				 * might even hurt rather than help.
6595185a700Sflorian 				 */
6605185a700Sflorian 				ENQUEUE(new_ready_tasks, task, ready_link);
6615185a700Sflorian 				if ((task->flags & TASK_F_PRIVILEGED) != 0)
6625185a700Sflorian 					ENQUEUE(new_priority_tasks, task,
6635185a700Sflorian 						ready_priority_link);
6645185a700Sflorian 				tasks_ready++;
6655185a700Sflorian 			}
6665185a700Sflorian 		}
6675185a700Sflorian 
6685185a700Sflorian 	}
6695185a700Sflorian 
6705185a700Sflorian 	ISC_LIST_APPENDLIST(manager->ready_tasks, new_ready_tasks, ready_link);
6715185a700Sflorian 	ISC_LIST_APPENDLIST(manager->ready_priority_tasks, new_priority_tasks,
6725185a700Sflorian 			    ready_priority_link);
6735185a700Sflorian 	manager->tasks_ready += tasks_ready;
6745185a700Sflorian 	if (empty_readyq(manager))
6755185a700Sflorian 		manager->mode = isc_taskmgrmode_normal;
6765185a700Sflorian 
6775185a700Sflorian }
6785185a700Sflorian 
6795185a700Sflorian static void
manager_free(isc_taskmgr_t * manager)6808b553854Sflorian manager_free(isc_taskmgr_t *manager) {
6815185a700Sflorian 	free(manager);
6825185a700Sflorian 	taskmgr = NULL;
6835185a700Sflorian }
6845185a700Sflorian 
6855185a700Sflorian isc_result_t
isc_taskmgr_create(unsigned int workers,unsigned int default_quantum,isc_taskmgr_t ** managerp)6868b553854Sflorian isc_taskmgr_create(unsigned int workers,
6875185a700Sflorian 		    unsigned int default_quantum, isc_taskmgr_t **managerp)
6885185a700Sflorian {
6895185a700Sflorian 	unsigned int i, started = 0;
6908b553854Sflorian 	isc_taskmgr_t *manager;
6915185a700Sflorian 
6925185a700Sflorian 	/*
6935185a700Sflorian 	 * Create a new task manager.
6945185a700Sflorian 	 */
6955185a700Sflorian 
6965185a700Sflorian 	REQUIRE(workers > 0);
6975185a700Sflorian 	REQUIRE(managerp != NULL && *managerp == NULL);
6985185a700Sflorian 
6995185a700Sflorian 	UNUSED(i);
7005185a700Sflorian 	UNUSED(started);
7015185a700Sflorian 
7025185a700Sflorian 	if (taskmgr != NULL) {
7035185a700Sflorian 		if (taskmgr->refs == 0)
7045185a700Sflorian 			return (ISC_R_SHUTTINGDOWN);
7055185a700Sflorian 		taskmgr->refs++;
7065185a700Sflorian 		*managerp = (isc_taskmgr_t *)taskmgr;
7075185a700Sflorian 		return (ISC_R_SUCCESS);
7085185a700Sflorian 	}
7095185a700Sflorian 
7105185a700Sflorian 	manager = malloc(sizeof(*manager));
7115185a700Sflorian 	if (manager == NULL)
7125185a700Sflorian 		return (ISC_R_NOMEMORY);
7135185a700Sflorian 	manager->mode = isc_taskmgrmode_normal;
7145185a700Sflorian 
7155185a700Sflorian 	if (default_quantum == 0)
7165185a700Sflorian 		default_quantum = DEFAULT_DEFAULT_QUANTUM;
7175185a700Sflorian 	manager->default_quantum = default_quantum;
7185185a700Sflorian 	INIT_LIST(manager->tasks);
7195185a700Sflorian 	INIT_LIST(manager->ready_tasks);
7205185a700Sflorian 	INIT_LIST(manager->ready_priority_tasks);
7215185a700Sflorian 	manager->tasks_running = 0;
7225185a700Sflorian 	manager->tasks_ready = 0;
723*1fb015a8Sflorian 	manager->exclusive_requested = 0;
724*1fb015a8Sflorian 	manager->pause_requested = 0;
725*1fb015a8Sflorian 	manager->exiting = 0;
7265185a700Sflorian 	manager->excl = NULL;
7275185a700Sflorian 
7285185a700Sflorian 	manager->refs = 1;
7295185a700Sflorian 	taskmgr = manager;
7305185a700Sflorian 
7315185a700Sflorian 	*managerp = (isc_taskmgr_t *)manager;
7325185a700Sflorian 
7335185a700Sflorian 	return (ISC_R_SUCCESS);
7345185a700Sflorian }
7355185a700Sflorian 
7365185a700Sflorian void
isc_taskmgr_destroy(isc_taskmgr_t ** managerp)7378b553854Sflorian isc_taskmgr_destroy(isc_taskmgr_t **managerp) {
7388b553854Sflorian 	isc_taskmgr_t *manager;
7398b553854Sflorian 	isc_task_t *task;
7405185a700Sflorian 	unsigned int i;
7415185a700Sflorian 
7425185a700Sflorian 	/*
7435185a700Sflorian 	 * Destroy '*managerp'.
7445185a700Sflorian 	 */
7455185a700Sflorian 
7465185a700Sflorian 	REQUIRE(managerp != NULL);
7478b553854Sflorian 	manager = (isc_taskmgr_t *)*managerp;
7485185a700Sflorian 
7495185a700Sflorian 	UNUSED(i);
7505185a700Sflorian 
7515185a700Sflorian 	manager->refs--;
7525185a700Sflorian 	if (manager->refs > 0) {
7535185a700Sflorian 		*managerp = NULL;
7545185a700Sflorian 		return;
7555185a700Sflorian 	}
7565185a700Sflorian 
7575185a700Sflorian 	/*
7585185a700Sflorian 	 * Only one non-worker thread may ever call this routine.
7595185a700Sflorian 	 * If a worker thread wants to initiate shutdown of the
7605185a700Sflorian 	 * task manager, it should ask some non-worker thread to call
7615185a700Sflorian 	 * isc_taskmgr_destroy(), e.g. by signalling a condition variable
7625185a700Sflorian 	 * that the startup thread is sleeping on.
7635185a700Sflorian 	 */
7645185a700Sflorian 
7655185a700Sflorian 	/*
7665185a700Sflorian 	 * Detach the exclusive task before acquiring the manager lock
7675185a700Sflorian 	 */
7685185a700Sflorian 	if (manager->excl != NULL)
7698b553854Sflorian 		isc_task_detach((isc_task_t **) &manager->excl);
7705185a700Sflorian 
7715185a700Sflorian 	/*
7725185a700Sflorian 	 * Make sure we only get called once.
7735185a700Sflorian 	 */
7745185a700Sflorian 	INSIST(!manager->exiting);
775*1fb015a8Sflorian 	manager->exiting = 1;
7765185a700Sflorian 
7775185a700Sflorian 	/*
7785185a700Sflorian 	 * If privileged mode was on, turn it off.
7795185a700Sflorian 	 */
7805185a700Sflorian 	manager->mode = isc_taskmgrmode_normal;
7815185a700Sflorian 
7825185a700Sflorian 	/*
7835185a700Sflorian 	 * Post shutdown event(s) to every task (if they haven't already been
7845185a700Sflorian 	 * posted).
7855185a700Sflorian 	 */
7865185a700Sflorian 	for (task = HEAD(manager->tasks);
7875185a700Sflorian 	     task != NULL;
7885185a700Sflorian 	     task = NEXT(task, link)) {
7895185a700Sflorian 		if (task_shutdown(task))
7905185a700Sflorian 			push_readyq(manager, task);
7915185a700Sflorian 	}
7925185a700Sflorian 	/*
7935185a700Sflorian 	 * Dispatch the shutdown events.
7945185a700Sflorian 	 */
7958b553854Sflorian 	while (isc_taskmgr_ready((isc_taskmgr_t *)manager))
7968b553854Sflorian 		(void)isc_taskmgr_dispatch((isc_taskmgr_t *)manager);
7975185a700Sflorian 	INSIST(ISC_LIST_EMPTY(manager->tasks));
7985185a700Sflorian 	taskmgr = NULL;
7995185a700Sflorian 
8005185a700Sflorian 	manager_free(manager);
8015185a700Sflorian 
8025185a700Sflorian 	*managerp = NULL;
8035185a700Sflorian }
8045185a700Sflorian 
805*1fb015a8Sflorian int
isc_taskmgr_ready(isc_taskmgr_t * manager)8068b553854Sflorian isc_taskmgr_ready(isc_taskmgr_t *manager) {
807*1fb015a8Sflorian 	int is_ready;
8085185a700Sflorian 
8095185a700Sflorian 	if (manager == NULL)
8105185a700Sflorian 		manager = taskmgr;
8115185a700Sflorian 	if (manager == NULL)
812*1fb015a8Sflorian 		return (0);
8135185a700Sflorian 
8145185a700Sflorian 	is_ready = !empty_readyq(manager);
8155185a700Sflorian 
8165185a700Sflorian 	return (is_ready);
8175185a700Sflorian }
8185185a700Sflorian 
8195185a700Sflorian isc_result_t
isc_taskmgr_dispatch(isc_taskmgr_t * manager)8208b553854Sflorian isc_taskmgr_dispatch(isc_taskmgr_t *manager) {
8215185a700Sflorian 	if (manager == NULL)
8225185a700Sflorian 		manager = taskmgr;
8235185a700Sflorian 	if (manager == NULL)
8245185a700Sflorian 		return (ISC_R_NOTFOUND);
8255185a700Sflorian 
8265185a700Sflorian 	dispatch(manager);
8275185a700Sflorian 
8285185a700Sflorian 	return (ISC_R_SUCCESS);
8295185a700Sflorian }
830