task.c revision 9b8123723a4b38cc8885baf14344396f3edad4cc
7d32c065c7bb56f281651ae3dd2888f32ce4f1d9Bob Halley * Copyright (C) 1998, 1999 Internet Software Consortium.
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * Permission to use, copy, modify, and distribute this software for any
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * purpose with or without fee is hereby granted, provided that the above
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * copyright notice and this permission notice appear in all copies.
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
1633838b8255282d10af15c5c84cee5a51466712Bob Halley * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
9192e92f7d0f4e78385a1d5f9b6607cc5bf0e42aBob Halley#define XTRACE(m) printf("%s task %p thread %lu\n", (m), \
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halleytypedef enum {
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halley task_state_idle, task_state_ready, task_state_running,
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halley /* Not locked. */
cee7525336d4710a64368875d92eb439d4d3efb1Mark Andrews unsigned int magic;
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halley /* Locked by task lock. */
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halley unsigned int references;
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley /* Locked by task manager lock. */
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley#define TASK_MANAGER_MAGIC 0x54534B4DU /* TSKM. */
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley /* Not locked. */
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley unsigned int magic;
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley unsigned int workers;
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley /* Locked by task manager lock. */
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley#define FINISHED(m) ((m)->exiting && EMPTY((m)->tasks))
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halleystatic inline isc_event_t *
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halleyevent_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halleyisc_event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
86131d8d7aaf1bb8b8bfc7819985d05ea369b708Bob Halley return (event_allocate(mctx, sender, type, action, arg, size));
95f78a208ad6dfb8359320c77ab30c670c773922Mark Andrews * All tasks have completed and the
95f78a208ad6dfb8359320c77ab30c670c773922Mark Andrews * task manager is exiting. Wake up
95f78a208ad6dfb8359320c77ab30c670c773922Mark Andrews * any idle worker threads so they
5f120ce962b03e4dcf6f1974b9b896f0fa7cacb0Bob Halleyisc_task_create(isc_taskmgr_t *manager, isc_mem_t *mctx, unsigned int quantum,
577179503f2eb7695ec668d8eeb41889a150e28fBob Halley if (isc_mutex_init(&task->lock) != ISC_R_SUCCESS) {
577179503f2eb7695ec668d8eeb41889a150e28fBob Halley "isc_mutex_init() failed");
577179503f2eb7695ec668d8eeb41889a150e28fBob Halley /* XXX Should disallow if task manager is exiting. */
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halleyisc_task_attach(isc_task_t *task, isc_task_t **taskp) {
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley if (task->state == task_state_shutdown && task->references == 0)
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halleyisc_task_send(isc_task_t *task, isc_event_t **eventp) {
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * We're trying hard to hold locks for as short a time as possible.
70fdfcd1fa7ebd059deffa9a2cecc29df96dfe52Bob Halley * We're also trying to hold as few locks as possible. This is why
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * some processing is deferred until after a lock is released.
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halley * Note: we require that task->shutting_down implies
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halley * !task->enqueue_allowed.
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * We need to add this task to the ready queue.
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * We've waited until now to do it, rather than doing it
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * while holding the task lock, because we don't want to
5fc1b54cc6134bd70f4e22df90a2e5631aaea77aBob Halley * block while holding the task lock.
5fc1b54cc6134bd70f4e22df90a2e5631aaea77aBob Halley * We've changed the state to ready, so no one else will
5fc1b54cc6134bd70f4e22df90a2e5631aaea77aBob Halley * be trying to add this task to the ready queue. It
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * thus doesn't matter if more events have been added to
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * the queue after we gave up the task lock.
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * Shutting down a task requires posting a shutdown event
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * to the task's queue and then executing it, so there's
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * no way the task can disappear. A task is always on the
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * task manager's 'tasks' list, so the task manager can
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * always post a shutdown event to all tasks if it is
ce3761f64d3d734cc94605026985898900ecc474Bob Halley * requested to shutdown.
95c86af1e92dae4ff837a39e7e2dcb7308dd9cceBob Halleyisc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type) {
00d81794884f1eee59ca058a292f2d1e50d9547cBob Halley * Purge events matching 'sender' and 'type'. sender == NULL means
00d81794884f1eee59ca058a292f2d1e50d9547cBob Halley * "any sender". type == NULL means any type. Task manager events
00d81794884f1eee59ca058a292f2d1e50d9547cBob Halley * cannot be purged.
00d81794884f1eee59ca058a292f2d1e50d9547cBob Halley * Purging never changes the state of the task.
00d81794884f1eee59ca058a292f2d1e50d9547cBob Halley if ((sender == NULL || event->sender == sender) &&
00d81794884f1eee59ca058a292f2d1e50d9547cBob Halley ((type == 0 && event->type > 0) || event->type == type)) {
f18f3c93e7fecf120302658f93addae573a6e874Bob Halleyisc_task_allowsend(isc_task_t *task, isc_boolean_t enqueue_allowed) {
bcfcece57e9411ee4bd352b45a8b1ac1dbcf01f4Bob Halleyisc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg) {
1366b7833c86343de278480b9abd71754e418bfaBob Halley * This routine is very similar to isc_task_send() above.
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * Note that we post shutdown events LIFO.
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley *** Task Manager.
86131d8d7aaf1bb8b8bfc7819985d05ea369b708Bob Halley * Again we're trying to hold the lock for as short a time as possible
86131d8d7aaf1bb8b8bfc7819985d05ea369b708Bob Halley * and to do as little locking and unlocking as possible.
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * In both while loops, the appropriate lock must be held before the
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * while body starts. Code which acquired the lock at the top of
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * the loop would be more readable, but would result in a lot of
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * extra locking. Compare:
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * Straightforward:
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * while (expression) {
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * Unlocked part here...
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Note how if the loop continues we unlock and then immediately lock.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * For N iterations of the loop, this code does 2N+1 locks and 2N+1
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * unlocks. Also note that the lock is not held when the while
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * condition is tested, which may or may not be important, depending
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * on the expression.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * As written:
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * while (expression) {
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Unlocked part here...
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * For N iterations of the loop, this code does N+1 locks and N+1
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * unlocks. The while expression is always protected by the lock.
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * For reasons similar to those given in the comment in
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * isc_task_send() above, it is safe for us to dequeue
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * the task while only holding the manager lock, and then
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * change the task to running state while only holding the
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * task lock.
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley while (EMPTY(manager->ready_tasks) && !FINISHED(manager)) {
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley unsigned int dispatch_count = 0;
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Note we only unlock the manager lock if we actually
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * have a task to do. We must reacquire the manager
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * lock before exiting the 'if (task != NULL)' block.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * The task became runnable, but all events
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * in the run queue were subsequently purged.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Put the task to sleep.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Execute the event action.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Nothing else to do for this task
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * right now. If it is shutting down,
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * then it is done, otherwise we just
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * put it to sleep.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Our quantum has expired, but
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * there is more work to be done.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * We'll requeue it to the ready
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * queue later.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * We don't check quantum until
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * dispatching at least one event,
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * so the minimum quantum is one.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * We know we're awake, so we don't have
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * to wakeup any sleeping threads if the
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * ready queue is empty before we requeue.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * A possible optimization if the queue is
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * empty is to 'goto' the 'if (task != NULL)'
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * block, avoiding the ENQUEUE of the task
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * and the subsequent immediate DEQUEUE
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * (since it is the only executable task).
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * We don't do this because then we'd be
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * skipping the exit_requested check. The
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * cost of ENQUEUE is low anyway, especially
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * when you consider that we'd have to do
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * an extra EMPTY check to see if we could
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * do the optimization. If the ready queue
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley * were usually nonempty, the 'optimization'
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley * might even hurt rather than help.
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley (void)isc_condition_destroy(&manager->work_available);
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley isc_mem_put(manager->mctx, manager, sizeof *manager);
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halleyisc_taskmgr_create(isc_mem_t *mctx, unsigned int workers,
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley unsigned int default_quantum, isc_taskmgr_t **managerp)
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley unsigned int i, started = 0;
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley threads = isc_mem_get(mctx, workers * sizeof (isc_thread_t));
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley "isc_mutex_init() failed");
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
6957b87f931bb110ba4d0adf495932691ba550b1Bob Halley "isc_condition_init() failed");
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * Start workers.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews for (i = 0; i < workers; i++) {
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews unsigned int i;
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * Only one non-worker thread may ever call this routine.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * If a worker thread wants to initiate shutdown of the
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * task manager, it should ask some non-worker thread to call
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * isc_taskmgr_destroy(), e.g. by signalling a condition variable
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * that the startup thread is sleeping on.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * Unlike elsewhere, we're going to hold this lock a long time.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * We need to do so, because otherwise the list of tasks could
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * change while we were traversing it.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * This is also the only function where we will hold both the
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * task manager lock and a task lock at the same time.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * Make sure we only get called once.
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * Post the shutdown event to every task (if it hasn't already been
7c0539bea56022274da04263eb41fbb5b8835c38Mark Andrews * Note that we post shutdown events LIFO.
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * Wake up any sleeping workers. This ensures we get work done if
c50fd34a4e0e6978f8ca5f6f3ad8545549c3cfeeBob Halley * there's work left to do, and if there are already no tasks left
29b487b0a458d655f0aad9257ca46021f4903d08Bob Halley * it will cause the workers to see manager->exiting.
5d661f0bde49c68d33eb1146d60058782aca50a7Bob Halley * Wait for all the worker threads to exit.