task.c revision 3f8744a28fd2cd21b058a3a32622911ef9aa8039
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Copyright (C) 1998, 1999 Internet Software Consortium.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Permission to use, copy, modify, and distribute this software for any
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * purpose with or without fee is hereby granted, provided that the above
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * copyright notice and this permission notice appear in all copies.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Principal Author: Bob Halley
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * XXXRTH Need to document the states a task can be in, and the rules
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * for changing states.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews#define XTRACE(m) printf("%s task %p thread %lu\n", (m), \
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews#define XTHREADTRACE(m) printf("%s thread %lu\n", (m), \
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrewstypedef enum {
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews task_state_idle, task_state_ready, task_state_running,
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews /* Not locked. */
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews unsigned int magic;
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews /* Locked by task lock. */
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews unsigned int quantum;
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews unsigned int flags;
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews /* Locked by task manager lock. */
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews#define DONE_FLAGS (TASK_F_DONEOK|TASK_F_SHUTTINGDOWN)
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews#define TASK_DONE(t) (((t)->flags & DONE_FLAGS) == \
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews#define TASK_MANAGER_MAGIC 0x54534B4DU /* TSKM. */
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews /* Not locked. */
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews unsigned int magic;
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews unsigned int workers;
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews /* Locked by task manager lock. */
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews#define FINISHED(m) ((m)->exiting && EMPTY((m)->tasks))
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews * All tasks have completed and the
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * task manager is exiting. Wake up
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * any idle worker threads so they
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrewsisc_task_create(isc_taskmgr_t *manager, isc_mem_t *mctx, unsigned int quantum,
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews if (isc_mutex_init(&task->lock) != ISC_R_SUCCESS) {
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews "isc_mutex_init() failed");
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews /* XXX Should disallow if task manager is exiting. */
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrewsisc_task_attach(isc_task_t *source, isc_task_t **targetp) {
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Attach *targetp to source.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Detach *taskp from its task.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews if (task->state == task_state_done && task->references == 0)
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * XXXRTH It is currently possible to detach the last
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * reference from a task that has not been shutdown. This
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * will prevent the task from being shutdown until the
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * task manager is destroyed. Should there be an
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * automatic shutdown on last detach?
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Get the task's memory context.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrewsisc_task_send(isc_task_t *task, isc_event_t **eventp) {
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * Send '*event' to 'task'.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * We're trying hard to hold locks for as short a time as possible.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * We're also trying to hold as few locks as possible. This is why
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * some processing is deferred until after a lock is released.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * Note: we require that task->state == task_state_done implies
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * (task->flags & TASK_F_SENDOK) == 0.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * We need to add this task to the ready queue.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * We've waited until now to do it, rather than doing it
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * while holding the task lock, because we don't want to
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * block while holding the task lock.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * We've changed the state to ready, so no one else will
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * be trying to add this task to the ready queue. The
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * only way to leave the ready state is by executing the
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * task. It thus doesn't matter if events are added,
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * removed, or shutting_down is started in the interval
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * between the time we released the task lock, and the time
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * we add the task to the ready queue.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews ENQUEUE(manager->ready_tasks, task, ready_link);
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrewsisc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first,
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * Purge events from a task's event queue.
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff * Events matching 'sender' and whose type is >= first and
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff * <= last will be purged, unless they are marked as unpurgable.
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff * sender == NULL means "any sender".
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Purging never changes the state of the task.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews if ((sender == NULL || event->sender == sender) &&
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews (event->attributes & ISC_EVENTATTR_NOPURGE) == 0) {
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrewsisc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type) {
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews * Purge events from a task's event queue.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews return (isc_task_purgerange(task, sender, type, type));
390b2077fc751105e40174ceaa1ce34ef06e7dd4Mark Andrewsisc_task_allowsend(isc_task_t *task, isc_boolean_t allowed) {
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Allow or disallow sending events to 'task'.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrewsisc_task_allowdone(isc_task_t *task, isc_boolean_t allowed) {
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews * Allow or disallow automatic termination of 'task'.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrewsisc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg) {
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Send a shutdown event with action 'action' and argument 'arg' when
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * 'task' is shutdown.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews } else if ((task->flags & TASK_F_SHUTTINGDOWN) != 0) {
419590499823ce15b5d2ad4fe71eaf04bd5a86c0Michael Graff * Shutdown 'task'.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * This routine is very similar to isc_task_send() above.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews if ((task->flags & TASK_F_SHUTTINGDOWN) == 0) {
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews if (EMPTY(task->on_shutdown) && TASK_DONE(task)) {
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Note that we post shutdown events LIFO.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews ENQUEUE(manager->ready_tasks, task, ready_link);
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Destroy '*taskp'.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews *** Task Manager.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Again we're trying to hold the lock for as short a time as possible
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * and to do as little locking and unlocking as possible.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * In both while loops, the appropriate lock must be held before the
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * while body starts. Code which acquired the lock at the top of
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * the loop would be more readable, but would result in a lot of
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * extra locking. Compare:
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * Straightforward:
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * while (expression) {
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * Unlocked part here...
6e49e91bd08778d7eae45a2229dcf41ed97cc636David Lawrence * Note how if the loop continues we unlock and then immediately lock.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * For N iterations of the loop, this code does 2N+1 locks and 2N+1
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * unlocks. Also note that the lock is not held when the while
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * condition is tested, which may or may not be important, depending
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * on the expression.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * As written:
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * while (expression) {
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * Unlocked part here...
419590499823ce15b5d2ad4fe71eaf04bd5a86c0Michael Graff * For N iterations of the loop, this code does N+1 locks and N+1
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * unlocks. The while expression is always protected by the lock.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * For reasons similar to those given in the comment in
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * isc_task_send() above, it is safe for us to dequeue
6e49e91bd08778d7eae45a2229dcf41ed97cc636David Lawrence * the task while only holding the manager lock, and then
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * change the task to running state while only holding the
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews while (EMPTY(manager->ready_tasks) && !FINISHED(manager)) {
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews WAIT(&manager->work_available, &manager->lock);
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews unsigned int dispatch_count = 0;
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * Note we only unlock the manager lock if we actually
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * have a task to do. We must reacquire the manager
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * lock before exiting the 'if (task != NULL)' block.
b54630c4518a1a173fee3478f4bf51dff450b6dcMark Andrews DEQUEUE(manager->ready_tasks, task, ready_link);
390b2077fc751105e40174ceaa1ce34ef06e7dd4Mark Andrews * The task became runnable, but all events
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * in the run queue were subsequently purged.
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews * Put the task to sleep.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * Execute the event action.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * Nothing else to do for this task
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * right now. If it is shutting down,
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * then it is done, otherwise we just
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * put it to sleep.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Our quantum has expired, but
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * there is more work to be done.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * We'll requeue it to the ready
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * queue later.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * We don't check quantum until
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews * dispatching at least one event,
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * so the minimum quantum is one.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * We know we're awake, so we don't have
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * to wakeup any sleeping threads if the
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * ready queue is empty before we requeue.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * A possible optimization if the queue is
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * empty is to 'goto' the 'if (task != NULL)'
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * block, avoiding the ENQUEUE of the task
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * and the subsequent immediate DEQUEUE
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * (since it is the only executable task).
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * We don't do this because then we'd be
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * skipping the exit_requested check. The
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff * cost of ENQUEUE is low anyway, especially
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * when you consider that we'd have to do
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff * an extra EMPTY check to see if we could
390b2077fc751105e40174ceaa1ce34ef06e7dd4Mark Andrews * do the optimization. If the ready queue
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * were usually nonempty, the 'optimization'
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * might even hurt rather than help.
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews (void)isc_condition_destroy(&manager->work_available);
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews isc_mem_put(manager->mctx, manager, sizeof *manager);
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrewsisc_taskmgr_create(isc_mem_t *mctx, unsigned int workers,
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews unsigned int default_quantum, isc_taskmgr_t **managerp)
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews unsigned int i, started = 0;
6e49e91bd08778d7eae45a2229dcf41ed97cc636David Lawrence * Create a new task manager.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews REQUIRE(managerp != NULL && *managerp == NULL);
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews threads = isc_mem_get(mctx, workers * sizeof (isc_thread_t));
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff "isc_mutex_init() failed");
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
34ee961fa2f0f5f2ee3cff40fdb4d7d7b48b7728Mark Andrews isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews "isc_condition_init() failed");
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews * Start workers.
e44487bfc23599b6b240e09d83d1c862fecfcc82Michael Graff for (i = 0; i < workers; i++) {
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews unsigned int i;
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * Destroy '*managerp'.
390b2077fc751105e40174ceaa1ce34ef06e7dd4Mark Andrews * Only one non-worker thread may ever call this routine.
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * If a worker thread wants to initiate shutdown of the
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * task manager, it should ask some non-worker thread to call
440164d3e36353a4b9801fcc05fe66b6cf1fb8ceMark Andrews * isc_taskmgr_destroy(), e.g. by signalling a condition variable
a5c30de2601a1d130a15a78cf3dc7610a02b2d85Mark Andrews * that the startup thread is sleeping on.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * Unlike elsewhere, we're going to hold this lock a long time.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * We need to do so, because otherwise the list of tasks could
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * change while we were traversing it.
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * This is also the only function where we will hold both the
78da321b437bbb690ef570ccf17dcc8583a5a4a0Mark Andrews * task manager lock and a task lock at the same time.
link);