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