task.c 22.5 KB
Newer Older
Bob Halley's avatar
Bob Halley committed
1
/*
Bob Halley's avatar
Bob Halley committed
2
 * Copyright (C) 1998, 1999  Internet Software Consortium.
Bob Halley's avatar
Bob Halley committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */
Bob Halley's avatar
base  
Bob Halley committed
17

Bob Halley's avatar
Bob Halley committed
18 19 20 21
/*
 * Principal Author: Bob Halley
 */

22 23 24 25 26
/*
 * XXXRTH  Need to document the states a task can be in, and the rules
 * for changing states.
 */

Bob Halley's avatar
Bob Halley committed
27 28
#include <config.h>

Bob Halley's avatar
base  
Bob Halley committed
29
#include <isc/assertions.h>
30
#include <isc/boolean.h>
Bob Halley's avatar
Bob Halley committed
31
#include <isc/thread.h>
32 33
#include <isc/mutex.h>
#include <isc/condition.h>
34
#include <isc/error.h>
35
#include <isc/event.h>
Bob Halley's avatar
Bob Halley committed
36
#include <isc/task.h>
Bob Halley's avatar
base  
Bob Halley committed
37

38
#include "util.h"
Bob Halley's avatar
base  
Bob Halley committed
39

Bob Halley's avatar
Bob Halley committed
40
#ifdef ISC_TASK_TRACE
Bob Halley's avatar
update  
Bob Halley committed
41
#define XTRACE(m)		printf("%s task %p thread %lu\n", (m), \
Bob Halley's avatar
Bob Halley committed
42
				       task, isc_thread_self())
43 44
#define XTHREADTRACE(m)		printf("%s thread %lu\n", (m), \
				       isc_thread_self())
Bob Halley's avatar
Bob Halley committed
45 46
#else
#define XTRACE(m)
47
#define XTHREADTRACE(m)
Bob Halley's avatar
Bob Halley committed
48
#endif
Bob Halley's avatar
base  
Bob Halley committed
49

Bob Halley's avatar
Bob Halley committed
50
/***
Bob Halley's avatar
Bob Halley committed
51
 *** Types.
Bob Halley's avatar
Bob Halley committed
52 53
 ***/

Bob Halley's avatar
Bob Halley committed
54 55
typedef enum {
	task_state_idle, task_state_ready, task_state_running,
56
	task_state_done
Bob Halley's avatar
Bob Halley committed
57 58 59 60 61 62
} task_state_t;

#define TASK_MAGIC			0x5441534BU	/* TASK. */
#define VALID_TASK(t)			((t) != NULL && \
					 (t)->magic == TASK_MAGIC)

Bob Halley's avatar
Bob Halley committed
63
struct isc_task {
Bob Halley's avatar
Bob Halley committed
64 65
	/* Not locked. */
	unsigned int			magic;
Bob Halley's avatar
Bob Halley committed
66
	isc_taskmgr_t *			manager;
Bob Halley's avatar
Bob Halley committed
67
	isc_mutex_t			lock;
Bob Halley's avatar
Bob Halley committed
68
	isc_mem_t *			mctx;
Bob Halley's avatar
Bob Halley committed
69 70 71
	/* Locked by task lock. */
	task_state_t			state;
	unsigned int			references;
Bob Halley's avatar
Bob Halley committed
72
	isc_eventlist_t			events;
73
	isc_eventlist_t			on_shutdown;
Bob Halley's avatar
Bob Halley committed
74
	unsigned int			quantum;
75
	unsigned int			flags;
Bob Halley's avatar
Bob Halley committed
76
	/* Locked by task manager lock. */
Bob Halley's avatar
Bob Halley committed
77 78
	LINK(isc_task_t)		link;
	LINK(isc_task_t)		ready_link;
Bob Halley's avatar
Bob Halley committed
79 80
};

81 82 83 84 85 86 87 88
#define TASK_F_DONEOK			0x01
#define TASK_F_SENDOK			0x02
#define TASK_F_SHUTTINGDOWN		0x04

#define DONE_FLAGS			(TASK_F_DONEOK|TASK_F_SHUTTINGDOWN)
#define TASK_DONE(t)			(((t)->flags & DONE_FLAGS) == \
					 DONE_FLAGS)

Bob Halley's avatar
Bob Halley committed
89 90 91 92
#define TASK_MANAGER_MAGIC		0x54534B4DU	/* TSKM. */
#define VALID_MANAGER(m)		((m) != NULL && \
					 (m)->magic == TASK_MANAGER_MAGIC)

Bob Halley's avatar
Bob Halley committed
93
struct isc_taskmgr {
Bob Halley's avatar
Bob Halley committed
94 95
	/* Not locked. */
	unsigned int			magic;
Bob Halley's avatar
Bob Halley committed
96
	isc_mem_t *			mctx;
Bob Halley's avatar
Bob Halley committed
97
	isc_mutex_t			lock;
98 99
	unsigned int			workers;
	isc_thread_t *			threads;
Bob Halley's avatar
Bob Halley committed
100 101
	/* Locked by task manager lock. */
	unsigned int			default_quantum;
Bob Halley's avatar
Bob Halley committed
102 103
	LIST(isc_task_t)		tasks;
	LIST(isc_task_t)		ready_tasks;
Bob Halley's avatar
Bob Halley committed
104
	isc_condition_t			work_available;
Bob Halley's avatar
Bob Halley committed
105
	isc_boolean_t			exiting;
Bob Halley's avatar
Bob Halley committed
106
};
Bob Halley's avatar
Bob Halley committed
107

Bob Halley's avatar
Bob Halley committed
108 109
#define DEFAULT_DEFAULT_QUANTUM		5
#define FINISHED(m)			((m)->exiting && EMPTY((m)->tasks))
Bob Halley's avatar
Bob Halley committed
110

Bob Halley's avatar
update  
Bob Halley committed
111

Bob Halley's avatar
base  
Bob Halley committed
112 113 114 115 116
/***
 *** Tasks.
 ***/

static void
Bob Halley's avatar
Bob Halley committed
117 118
task_free(isc_task_t *task) {
	isc_taskmgr_t *manager = task->manager;
Bob Halley's avatar
base  
Bob Halley committed
119

Bob Halley's avatar
Bob Halley committed
120
	XTRACE("free task");
Bob Halley's avatar
base  
Bob Halley committed
121
	REQUIRE(EMPTY(task->events));
122
	REQUIRE(EMPTY(task->on_shutdown));
Bob Halley's avatar
base  
Bob Halley committed
123 124 125 126 127 128 129 130 131 132 133 134 135

	LOCK(&manager->lock);
	UNLINK(manager->tasks, task, link);
	if (FINISHED(manager)) {
		/*
		 * All tasks have completed and the
		 * task manager is exiting.  Wake up
		 * any idle worker threads so they
		 * can exit.
		 */
		BROADCAST(&manager->work_available);
	}
	UNLOCK(&manager->lock);
Bob Halley's avatar
Bob Halley committed
136
	(void)isc_mutex_destroy(&task->lock);
Bob Halley's avatar
base  
Bob Halley committed
137
	task->magic = 0;
138
	isc_mem_put(task->mctx, task, sizeof *task);
Bob Halley's avatar
base  
Bob Halley committed
139 140
}

Bob Halley's avatar
Bob Halley committed
141
isc_result_t
Bob Halley's avatar
Bob Halley committed
142
isc_task_create(isc_taskmgr_t *manager, isc_mem_t *mctx, unsigned int quantum,
143
		isc_task_t **taskp)
Bob Halley's avatar
Bob Halley committed
144
{
Bob Halley's avatar
Bob Halley committed
145
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
146 147 148 149

	REQUIRE(VALID_MANAGER(manager));
	REQUIRE(taskp != NULL && *taskp == NULL);

Bob Halley's avatar
Bob Halley committed
150 151 152
	if (mctx == NULL)
		mctx = manager->mctx;
	task = isc_mem_get(mctx, sizeof *task);
Bob Halley's avatar
base  
Bob Halley committed
153
	if (task == NULL)
Bob Halley's avatar
Bob Halley committed
154
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
155
	task->manager = manager;
Bob Halley's avatar
Bob Halley committed
156
	task->mctx = mctx;
Bob Halley's avatar
Bob Halley committed
157
	if (isc_mutex_init(&task->lock) != ISC_R_SUCCESS) {
Bob Halley's avatar
Bob Halley committed
158
		isc_mem_put(mctx, task, sizeof *task);
Bob Halley's avatar
Bob Halley committed
159 160 161
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
162
	}
Bob Halley's avatar
base  
Bob Halley committed
163 164 165
	task->state = task_state_idle;
	task->references = 1;
	INIT_LIST(task->events);
166
	INIT_LIST(task->on_shutdown);
Bob Halley's avatar
base  
Bob Halley committed
167
	task->quantum = quantum;
168
	task->flags = (TASK_F_DONEOK|TASK_F_SENDOK);
Bob Halley's avatar
base  
Bob Halley committed
169 170 171 172
	INIT_LINK(task, link);
	INIT_LINK(task, ready_link);

	LOCK(&manager->lock);
Bob Halley's avatar
Bob Halley committed
173
	/* XXX Should disallow if task manager is exiting. */
Bob Halley's avatar
base  
Bob Halley committed
174 175 176 177 178
	if (task->quantum == 0)
		task->quantum = manager->default_quantum;
	APPEND(manager->tasks, task, link);
	UNLOCK(&manager->lock);

179
	task->magic = TASK_MAGIC;
Bob Halley's avatar
base  
Bob Halley committed
180 181
	*taskp = task;

Bob Halley's avatar
Bob Halley committed
182
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
183 184
}

Bob Halley's avatar
update  
Bob Halley committed
185
void
186
isc_task_attach(isc_task_t *source, isc_task_t **targetp) {
Bob Halley's avatar
base  
Bob Halley committed
187

188 189 190
	/*
	 * Attach *targetp to source.
	 */
Bob Halley's avatar
base  
Bob Halley committed
191

192 193
	REQUIRE(VALID_TASK(source));
	REQUIRE(targetp != NULL && *targetp == NULL);
Bob Halley's avatar
base  
Bob Halley committed
194

195 196 197 198 199
	LOCK(&source->lock);
	source->references++;
	UNLOCK(&source->lock);

	*targetp = source;
Bob Halley's avatar
base  
Bob Halley committed
200 201
}

Bob Halley's avatar
Bob Halley committed
202
void
Bob Halley's avatar
Bob Halley committed
203
isc_task_detach(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
204
	isc_boolean_t free_task = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
205
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
206

207 208 209 210
	/*
	 * Detach *taskp from its task.
	 */

Bob Halley's avatar
base  
Bob Halley committed
211 212 213 214
	REQUIRE(taskp != NULL);
	task = *taskp;
	REQUIRE(VALID_TASK(task));

215 216
	XTRACE("isc_task_detach");

Bob Halley's avatar
base  
Bob Halley committed
217 218 219
	LOCK(&task->lock);
	REQUIRE(task->references > 0);
	task->references--;
220
	if (task->state == task_state_done && task->references == 0)
Bob Halley's avatar
Bob Halley committed
221
		free_task = ISC_TRUE;
222 223 224 225 226 227 228
	/*
	 * XXXRTH  It is currently possible to detach the last
	 * reference from a task that has not been shutdown.  This
	 * will prevent the task from being shutdown until the
	 * task manager is destroyed.  Should there be an
	 * automatic shutdown on last detach?
	 */
Bob Halley's avatar
base  
Bob Halley committed
229 230 231 232 233 234 235 236
	UNLOCK(&task->lock);

	if (free_task)
		task_free(task);

	*taskp = NULL;
}

Bob Halley's avatar
Bob Halley committed
237 238 239
isc_mem_t *
isc_task_mem(isc_task_t *task) {

240 241 242 243
	/*
	 * Get the task's memory context.
	 */

Bob Halley's avatar
Bob Halley committed
244 245 246 247 248
	REQUIRE(VALID_TASK(task));
	
	return (task->mctx);
}

249
isc_result_t
Bob Halley's avatar
Bob Halley committed
250
isc_task_send(isc_task_t *task, isc_event_t **eventp) {
Bob Halley's avatar
Bob Halley committed
251
	isc_boolean_t was_idle = ISC_FALSE;
252 253
	isc_boolean_t disallowed = ISC_FALSE;
	isc_result_t result = ISC_R_SUCCESS;
Bob Halley's avatar
Bob Halley committed
254
	isc_event_t *event;
Bob Halley's avatar
base  
Bob Halley committed
255

256 257 258 259
	/*
	 * Send '*event' to 'task'.
	 */

Bob Halley's avatar
base  
Bob Halley committed
260
	REQUIRE(VALID_TASK(task));
Bob Halley's avatar
update  
Bob Halley committed
261 262
	REQUIRE(eventp != NULL);
	event = *eventp;
Bob Halley's avatar
base  
Bob Halley committed
263
	REQUIRE(event != NULL);
Bob Halley's avatar
Bob Halley committed
264 265
	REQUIRE(event->sender != NULL);
	REQUIRE(event->type > 0);
Bob Halley's avatar
base  
Bob Halley committed
266

Bob Halley's avatar
Bob Halley committed
267
	XTRACE("sending");
Bob Halley's avatar
base  
Bob Halley committed
268 269 270 271 272 273
	/*
	 * We're trying hard to hold locks for as short a time as possible.
	 * We're also trying to hold as few locks as possible.  This is why
	 * some processing is deferred until after a lock is released.
	 */
	LOCK(&task->lock);
274
	/*
275 276
	 * Note: we require that task->state == task_state_done implies
	 * (task->flags & TASK_F_SENDOK) == 0.
277
	 */
278
	if ((task->flags & TASK_F_SENDOK) != 0) {
Bob Halley's avatar
base  
Bob Halley committed
279
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
280
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
281 282 283 284 285 286
			INSIST(EMPTY(task->events));
			task->state = task_state_ready;
		}
		INSIST(task->state == task_state_ready ||
		       task->state == task_state_running);
		ENQUEUE(task->events, event, link);
287 288
	} else {
		disallowed = ISC_TRUE;
289 290
		if (task->state == task_state_done)
			result = ISC_R_TASKDONE;
291 292 293
		else
			result = ISC_R_TASKNOSEND;
	}
Bob Halley's avatar
base  
Bob Halley committed
294 295
	UNLOCK(&task->lock);

296 297
	if (disallowed)
		return (result);
Bob Halley's avatar
base  
Bob Halley committed
298 299

	if (was_idle) {
Bob Halley's avatar
Bob Halley committed
300
		isc_taskmgr_t *manager;
Bob Halley's avatar
base  
Bob Halley committed
301 302 303 304 305 306 307 308 309

		/*
		 * We need to add this task to the ready queue.
		 *
		 * We've waited until now to do it, rather than doing it
		 * while holding the task lock, because we don't want to
		 * block while holding the task lock.
		 *
		 * We've changed the state to ready, so no one else will
310 311 312 313 314 315
		 * be trying to add this task to the ready queue.  The
		 * only way to leave the ready state is by executing the
		 * task.  It thus doesn't matter if events are added,
		 * removed, or shutting_down is started in the interval
		 * between the time we released the task lock, and the time
		 * we add the task to the ready queue.
Bob Halley's avatar
base  
Bob Halley committed
316 317 318 319 320
		 */
		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
321
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
322 323 324
		UNLOCK(&manager->lock);
	}

Bob Halley's avatar
update  
Bob Halley committed
325 326
	*eventp = NULL;

Bob Halley's avatar
Bob Halley committed
327
	XTRACE("sent");
328 329

	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
330 331
}

332 333 334 335
unsigned int
isc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first,
		    isc_eventtype_t last)
{
Bob Halley's avatar
Bob Halley committed
336
	isc_event_t *event, *next_event;
Bob Halley's avatar
Bob Halley committed
337
	isc_eventlist_t purgeable;
338
	unsigned int purge_count;
Bob Halley's avatar
Bob Halley committed
339

340 341 342 343
	/*
	 * Purge events from a task's event queue.
	 */

Bob Halley's avatar
Bob Halley committed
344
	REQUIRE(VALID_TASK(task));
345
	REQUIRE(last >= first);
Bob Halley's avatar
Bob Halley committed
346 347

	/*
348
	 * Events matching 'sender' and whose type is >= first and
Bob Halley's avatar
Bob Halley committed
349 350
	 * <= last will be purged, unless they are marked as unpurgable.
	 * sender == NULL means "any sender".
351 352
	 *
	 * Purging never changes the state of the task.
Bob Halley's avatar
Bob Halley committed
353 354 355
	 */

	INIT_LIST(purgeable);
356
	purge_count = 0;
Bob Halley's avatar
Bob Halley committed
357 358 359 360 361 362 363

	LOCK(&task->lock);
	for (event = HEAD(task->events);
	     event != NULL;
	     event = next_event) {
		next_event = NEXT(event, link);
		if ((sender == NULL || event->sender == sender) &&
Bob Halley's avatar
Bob Halley committed
364 365 366
		    event->type >= first &&
		    event->type <= last &&
		    (event->attributes & ISC_EVENTATTR_NOPURGE) == 0) {
Bob Halley's avatar
Bob Halley committed
367 368 369 370 371 372 373 374 375 376
			DEQUEUE(task->events, event, link);
			ENQUEUE(purgeable, event, link);
		}
	}
	UNLOCK(&task->lock);

	for (event = HEAD(purgeable);
	     event != NULL;
	     event = next_event) {
		next_event = NEXT(event, link);
Bob Halley's avatar
Bob Halley committed
377
		isc_event_free(&event);
378
		purge_count++;
Bob Halley's avatar
Bob Halley committed
379
	}
380 381

	return (purge_count);
Bob Halley's avatar
Bob Halley committed
382 383
}

384 385 386 387 388 389 390 391 392 393
unsigned int
isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type) {

	/*
	 * Purge events from a task's event queue.
	 */

	return (isc_task_purgerange(task, sender, type, type));
}

394
isc_result_t
395
isc_task_allowsend(isc_task_t *task, isc_boolean_t allowed) {
396 397
	isc_result_t result = ISC_R_SUCCESS;

398 399 400 401
	/*
	 * Allow or disallow sending events to 'task'.
	 */

402 403 404
	REQUIRE(VALID_TASK(task));

	LOCK(&task->lock);
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
	if (task->state == task_state_done)
		result = ISC_R_TASKDONE;
	else {
		if (allowed)
			task->flags |= TASK_F_SENDOK;
		else
			task->flags &= ~TASK_F_SENDOK;
	}
	UNLOCK(&task->lock);

	return (result);
}

isc_result_t
isc_task_allowdone(isc_task_t *task, isc_boolean_t allowed) {
	isc_result_t result = ISC_R_SUCCESS;

422 423 424 425
	/*
	 * Allow or disallow automatic termination of 'task'.
	 */

426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
	REQUIRE(VALID_TASK(task));

	LOCK(&task->lock);
	if (task->state == task_state_done)
		result = ISC_R_TASKDONE;
	else if (allowed &&
		 (task->flags & TASK_F_SHUTTINGDOWN) != 0 &&
		 task->state == task_state_idle) {
		task->flags &= ~TASK_F_SENDOK;
		task->state = task_state_done;
	} else {
		if (allowed)
			task->flags |= TASK_F_DONEOK;
		else
			task->flags &= ~TASK_F_DONEOK;
	}
442 443 444 445 446 447 448 449 450 451 452
	UNLOCK(&task->lock);

	return (result);
}

isc_result_t
isc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg) {
	isc_boolean_t disallowed = ISC_FALSE;
	isc_result_t result = ISC_R_SUCCESS;
	isc_event_t *event;

453 454 455 456 457
	/*
	 * Send a shutdown event with action 'action' and argument 'arg' when
	 * 'task' is shutdown.
	 */

458
	REQUIRE(VALID_TASK(task));
459
	REQUIRE(action != NULL);
460

461 462 463 464 465 466
	event = isc_event_allocate(task->mctx,
				   NULL,
				   ISC_TASKEVENT_SHUTDOWN,
				   action,
				   arg,
				   sizeof *event);
467 468 469 470
	if (event == NULL)
		return (ISC_R_NOMEMORY);

	LOCK(&task->lock);
471
	if (task->state == task_state_done) {
472
		disallowed = ISC_TRUE;
473 474
		result = ISC_R_TASKDONE;
	} else if ((task->flags & TASK_F_SHUTTINGDOWN) != 0) {
475 476 477 478 479 480 481
		disallowed = ISC_TRUE;
		result = ISC_R_TASKSHUTTINGDOWN;
	} else
		ENQUEUE(task->on_shutdown, event, link);
	UNLOCK(&task->lock);

	if (disallowed)
Bob Halley's avatar
Bob Halley committed
482
		isc_mem_put(task->mctx, event, sizeof *event);
483 484 485 486

	return (result);
}

Bob Halley's avatar
Bob Halley committed
487
void
Bob Halley's avatar
Bob Halley committed
488
isc_task_shutdown(isc_task_t *task) {
Bob Halley's avatar
Bob Halley committed
489
	isc_boolean_t was_idle = ISC_FALSE;
490 491
	isc_boolean_t queued_something = ISC_FALSE;
	isc_event_t *event, *prev;
Bob Halley's avatar
base  
Bob Halley committed
492

493 494 495 496
	/*
	 * Shutdown 'task'.
	 */

Bob Halley's avatar
base  
Bob Halley committed
497 498 499
	REQUIRE(VALID_TASK(task));

	/*
500
	 * This routine is very similar to isc_task_send() above.
Bob Halley's avatar
base  
Bob Halley committed
501 502 503
	 */

	LOCK(&task->lock);
504 505 506
	if ((task->flags & TASK_F_SHUTTINGDOWN) == 0) {
		XTRACE("shutting down");
		task->flags |= TASK_F_SHUTTINGDOWN;
Bob Halley's avatar
base  
Bob Halley committed
507
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
508
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
509
			INSIST(EMPTY(task->events));
510 511 512 513 514
			if (EMPTY(task->on_shutdown) && TASK_DONE(task)) {
				task->flags &= ~TASK_F_SENDOK;
				task->state = task_state_done;
			} else
				task->state = task_state_ready;
Bob Halley's avatar
base  
Bob Halley committed
515 516
		}
		INSIST(task->state == task_state_ready ||
517 518 519 520 521 522 523 524 525 526 527 528 529 530
		       task->state == task_state_running ||
		       task->state == task_state_done);
		if (task->state != task_state_done) {
			/*
			 * Note that we post shutdown events LIFO.
			 */
			for (event = TAIL(task->on_shutdown);
			     event != NULL;
			     event = prev) {
				prev = PREV(event, link);
				DEQUEUE(task->on_shutdown, event, link);
				ENQUEUE(task->events, event, link);
				queued_something = ISC_TRUE;
			}
531 532
		}
	}
Bob Halley's avatar
base  
Bob Halley committed
533 534
	UNLOCK(&task->lock);

535
	if (was_idle && queued_something) {
Bob Halley's avatar
Bob Halley committed
536
		isc_taskmgr_t *manager;
Bob Halley's avatar
base  
Bob Halley committed
537 538 539 540 541

		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
542
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
543 544
		UNLOCK(&manager->lock);
	}
Bob Halley's avatar
Bob Halley committed
545
}
Bob Halley's avatar
base  
Bob Halley committed
546

Bob Halley's avatar
Bob Halley committed
547
void
Bob Halley's avatar
Bob Halley committed
548
isc_task_destroy(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
549

550 551 552 553
	/*
	 * Destroy '*taskp'.
	 */

Bob Halley's avatar
Bob Halley committed
554 555
	REQUIRE(taskp != NULL);

Bob Halley's avatar
Bob Halley committed
556 557
	isc_task_shutdown(*taskp);
	isc_task_detach(taskp);
Bob Halley's avatar
base  
Bob Halley committed
558 559 560
}


Bob Halley's avatar
Bob Halley committed
561

Bob Halley's avatar
base  
Bob Halley committed
562 563 564 565
/***
 *** Task Manager.
 ***/

Bob Halley's avatar
update  
Bob Halley committed
566
static isc_threadresult_t
Bob Halley's avatar
Bob Halley committed
567 568 569
#ifdef _WIN32
WINAPI
#endif
Bob Halley's avatar
update  
Bob Halley committed
570
run(void *uap) {
Bob Halley's avatar
Bob Halley committed
571 572
	isc_taskmgr_t *manager = uap;
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
573

574
	XTHREADTRACE("start");
Bob Halley's avatar
base  
Bob Halley committed
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631

	REQUIRE(VALID_MANAGER(manager));

	/*
	 * Again we're trying to hold the lock for as short a time as possible
	 * and to do as little locking and unlocking as possible.
	 * 
	 * In both while loops, the appropriate lock must be held before the
	 * while body starts.  Code which acquired the lock at the top of
	 * the loop would be more readable, but would result in a lot of
	 * extra locking.  Compare:
	 *
	 * Straightforward:
	 *
	 *	LOCK();
	 *	...
	 *	UNLOCK();
	 *	while (expression) {
	 *		LOCK();
	 *		...
	 *		UNLOCK();
	 *
	 *	       	Unlocked part here...
	 *
	 *		LOCK();
	 *		...
	 *		UNLOCK();
	 *	}
	 *
	 * Note how if the loop continues we unlock and then immediately lock.
	 * For N iterations of the loop, this code does 2N+1 locks and 2N+1
	 * unlocks.  Also note that the lock is not held when the while
	 * condition is tested, which may or may not be important, depending
	 * on the expression.
	 * 
	 * As written:
	 *
	 *	LOCK();
	 *	while (expression) {
	 *		...
	 *		UNLOCK();
	 *
	 *	       	Unlocked part here...
	 *
	 *		LOCK();
	 *		...
	 *	}
	 *	UNLOCK();
	 *
	 * For N iterations of the loop, this code does N+1 locks and N+1
	 * unlocks.  The while expression is always protected by the lock.
	 */

	LOCK(&manager->lock);
	while (!FINISHED(manager)) {
		/*
		 * For reasons similar to those given in the comment in
632
		 * isc_task_send() above, it is safe for us to dequeue
Bob Halley's avatar
base  
Bob Halley committed
633 634 635 636 637
		 * the task while only holding the manager lock, and then
		 * change the task to running state while only holding the
		 * task lock.
		 */
		while (EMPTY(manager->ready_tasks) && !FINISHED(manager)) {
638
			XTHREADTRACE("wait");
Bob Halley's avatar
base  
Bob Halley committed
639
			WAIT(&manager->work_available, &manager->lock);
640
			XTHREADTRACE("awake");
Bob Halley's avatar
base  
Bob Halley committed
641
		}
642
		XTHREADTRACE("working");
Bob Halley's avatar
base  
Bob Halley committed
643 644 645
		
		task = HEAD(manager->ready_tasks);
		if (task != NULL) {
Bob Halley's avatar
Bob Halley committed
646
			unsigned int dispatch_count = 0;
Bob Halley's avatar
Bob Halley committed
647 648 649
			isc_boolean_t done = ISC_FALSE;
			isc_boolean_t requeue = ISC_FALSE;
			isc_boolean_t free_task = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
650
			isc_event_t *event;
Bob Halley's avatar
base  
Bob Halley committed
651 652 653 654 655 656 657 658 659 660 661 662

			INSIST(VALID_TASK(task));

			/*
			 * Note we only unlock the manager lock if we actually
			 * have a task to do.  We must reacquire the manager 
			 * lock before exiting the 'if (task != NULL)' block.
			 */
			DEQUEUE(manager->ready_tasks, task, ready_link);
			UNLOCK(&manager->lock);

			LOCK(&task->lock);
Bob Halley's avatar
Bob Halley committed
663 664 665 666 667 668 669 670
			INSIST(task->state == task_state_ready);
			if (EMPTY(task->events)) {
				/*
				 * The task became runnable, but all events
				 * in the run queue were subsequently purged.
				 * Put the task to sleep.
				 */
				task->state = task_state_idle;
Bob Halley's avatar
Bob Halley committed
671
				done = ISC_TRUE;
Bob Halley's avatar
Bob Halley committed
672 673 674
				XTRACE("ready but empty");
			} else
				task->state = task_state_running;
Bob Halley's avatar
base  
Bob Halley committed
675
			while (!done) {
Bob Halley's avatar
update  
Bob Halley committed
676 677 678
				INSIST(!EMPTY(task->events));
				event = HEAD(task->events);
				DEQUEUE(task->events, event, link);
Bob Halley's avatar
base  
Bob Halley committed
679 680 681 682

				/*
				 * Execute the event action.
				 */
Bob Halley's avatar
Bob Halley committed
683
				XTRACE("execute action");
684 685
				if (event->action != NULL) {
					UNLOCK(&task->lock);
686
					(event->action)(task, event);
687 688
					LOCK(&task->lock);
				}
Bob Halley's avatar
Bob Halley committed
689
				dispatch_count++;
Bob Halley's avatar
update  
Bob Halley committed
690
				
691
				if (EMPTY(task->events)) {
Bob Halley's avatar
Bob Halley committed
692
					/*
693 694 695 696
					 * Nothing else to do for this task
					 * right now.  If it is shutting down,
					 * then it is done, otherwise we just
					 * put it to sleep.
Bob Halley's avatar
Bob Halley committed
697
					 */
Bob Halley's avatar
Bob Halley committed
698
					XTRACE("empty");
699 700
					if (TASK_DONE(task)) {
						XTRACE("done");
701 702
						if (task->references == 0)
							free_task = ISC_TRUE;
703 704 705
						task->flags &=
							~TASK_F_SENDOK;
						task->state = task_state_done;
706 707
					} else
						task->state = task_state_idle;
Bob Halley's avatar
Bob Halley committed
708
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
709 710 711 712 713 714 715 716 717 718 719
				} else if (dispatch_count >= task->quantum) {
					/*
					 * Our quantum has expired, but
					 * there is more work to be done.
					 * We'll requeue it to the ready
					 * queue later.
					 *
					 * We don't check quantum until
					 * dispatching at least one event,
					 * so the minimum quantum is one.
					 */
Bob Halley's avatar
Bob Halley committed
720
					XTRACE("quantum");
Bob Halley's avatar
base  
Bob Halley committed
721
					task->state = task_state_ready;
Bob Halley's avatar
Bob Halley committed
722 723
					requeue = ISC_TRUE;
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
				}
			}
			UNLOCK(&task->lock);

			if (free_task)
				task_free(task);

			LOCK(&manager->lock);
			if (requeue) {
				/*
				 * We know we're awake, so we don't have
				 * to wakeup any sleeping threads if the
				 * ready queue is empty before we requeue.
				 *
				 * A possible optimization if the queue is
				 * empty is to 'goto' the 'if (task != NULL)'
				 * block, avoiding the ENQUEUE of the task
				 * and the subsequent immediate DEQUEUE
				 * (since it is the only executable task).
				 * We don't do this because then we'd be
				 * skipping the exit_requested check.  The
				 * cost of ENQUEUE is low anyway, especially
				 * when you consider that we'd have to do
				 * an extra EMPTY check to see if we could
				 * do the optimization.  If the ready queue
				 * were usually nonempty, the 'optimization'
				 * might even hurt rather than help.
				 */
				ENQUEUE(manager->ready_tasks, task,
					ready_link);
			}
		}
	}
	UNLOCK(&manager->lock);

759
	XTHREADTRACE("exit");
Bob Halley's avatar
base  
Bob Halley committed
760

Bob Halley's avatar
update  
Bob Halley committed
761
	return ((isc_threadresult_t)0);
Bob Halley's avatar
base  
Bob Halley committed
762 763 764
}

static void
Bob Halley's avatar
Bob Halley committed
765
manager_free(isc_taskmgr_t *manager) {
Bob Halley's avatar
Bob Halley committed
766 767
	(void)isc_condition_destroy(&manager->work_available);
	(void)isc_mutex_destroy(&manager->lock);
768 769
	isc_mem_put(manager->mctx, manager->threads,
		    manager->workers * sizeof (isc_thread_t));
Bob Halley's avatar
base  
Bob Halley committed
770
	manager->magic = 0;
Bob Halley's avatar
Bob Halley committed
771
	isc_mem_put(manager->mctx, manager, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
772 773
}

Bob Halley's avatar
Bob Halley committed
774
isc_result_t
Bob Halley's avatar
Bob Halley committed
775
isc_taskmgr_create(isc_mem_t *mctx, unsigned int workers, 
Bob Halley's avatar
Bob Halley committed
776
		   unsigned int default_quantum, isc_taskmgr_t **managerp)
Bob Halley's avatar
Bob Halley committed
777 778
{
	unsigned int i, started = 0;
Bob Halley's avatar
Bob Halley committed
779
	isc_taskmgr_t *manager;
780
	isc_thread_t *threads;
Bob Halley's avatar
Bob Halley committed
781

782 783 784 785
	/*
	 * Create a new task manager.
	 */

Bob Halley's avatar
Bob Halley committed
786
	REQUIRE(workers > 0);
Bob Halley's avatar
Bob Halley committed
787
	REQUIRE(managerp != NULL && *managerp == NULL);
Bob Halley's avatar
base  
Bob Halley committed
788

Bob Halley's avatar
Bob Halley committed
789
	manager = isc_mem_get(mctx, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
790
	if (manager == NULL)
Bob Halley's avatar
Bob Halley committed
791
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
792 793
	manager->magic = TASK_MANAGER_MAGIC;
	manager->mctx = mctx;
794 795 796 797 798 799 800
	threads = isc_mem_get(mctx, workers * sizeof (isc_thread_t));
	if (threads == NULL) {
		isc_mem_put(mctx, manager, sizeof *manager);
		return (ISC_R_NOMEMORY);
	}
	manager->threads = threads;
	manager->workers = 0;
Bob Halley's avatar
Bob Halley committed
801
	if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
802
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
803
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
804 805 806
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
807
	}
Bob Halley's avatar
base  
Bob Halley committed
808 809 810 811 812
	if (default_quantum == 0)
		default_quantum = DEFAULT_DEFAULT_QUANTUM;
	manager->default_quantum = default_quantum;
	INIT_LIST(manager->tasks);
	INIT_LIST(manager->ready_tasks);
Bob Halley's avatar
Bob Halley committed
813 814
	if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
		(void)isc_mutex_destroy(&manager->lock);
815
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
816
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
817 818 819
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_condition_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
820
	}
Bob Halley's avatar
Bob Halley committed
821
	manager->exiting = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
822 823 824 825 826 827 828
	manager->workers = 0;

	LOCK(&manager->lock);
	/*
	 * Start workers.
	 */
	for (i = 0; i < workers; i++) {
829 830
		if (isc_thread_create(run, manager,
				      &manager->threads[manager->workers]) == 
Bob Halley's avatar
Bob Halley committed
831
		    ISC_R_SUCCESS) {
Bob Halley's avatar
base  
Bob Halley committed
832 833 834 835 836 837 838 839
			manager->workers++;
			started++;
		}
	}
	UNLOCK(&manager->lock);

	if (started == 0) {
		manager_free(manager);
Bob Halley's avatar
Bob Halley committed
840
		return (ISC_R_NOTHREADS);
Bob Halley's avatar
base  
Bob Halley committed
841 842 843 844
	}		

	*managerp = manager;

Bob Halley's avatar
Bob Halley committed
845
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
846 847
}

Bob Halley's avatar
update  
Bob Halley committed
848
void
Bob Halley's avatar
Bob Halley committed
849 850 851
isc_taskmgr_destroy(isc_taskmgr_t **managerp) {
	isc_taskmgr_t *manager;
	isc_task_t *task;
852
	isc_event_t *event, *prev;
853
	unsigned int i;
Bob Halley's avatar
base  
Bob Halley committed
854

855 856 857 858
	/*
	 * Destroy '*managerp'.
	 */

Bob Halley's avatar
base  
Bob Halley committed
859 860 861 862
	REQUIRE(managerp != NULL);
	manager = *managerp;
	REQUIRE(VALID_MANAGER(manager));

863
	XTHREADTRACE("isc_taskmgr_destroy");
Bob Halley's avatar
base  
Bob Halley committed
864 865 866 867
	/*
	 * Only one non-worker thread may ever call this routine.
	 * If a worker thread wants to initiate shutdown of the
	 * task manager, it should ask some non-worker thread to call
Bob Halley's avatar
Bob Halley committed
868
	 * isc_taskmgr_destroy(), e.g. by signalling a condition variable
Bob Halley's avatar
base  
Bob Halley committed
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
	 * that the startup thread is sleeping on.
	 */

	/*
	 * Unlike elsewhere, we're going to hold this lock a long time.
	 * We need to do so, because otherwise the list of tasks could
	 * change while we were traversing it.
	 *
	 * This is also the only function where we will hold both the
	 * task manager lock and a task lock at the same time.
	 */

	LOCK(&manager->lock);

	/*
	 * Make sure we only get called once.
	 */
	INSIST(!manager->exiting);
Bob Halley's avatar
Bob Halley committed
887
	manager->exiting = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
888 889

	/*
890
	 * Post shutdown event(s) to every task (if they haven't already been
Bob Halley's avatar
update  
Bob Halley committed
891
	 * posted).
Bob Halley's avatar
base  
Bob Halley committed
892 893 894 895 896
	 */
	for (task = HEAD(manager->tasks);
	     task != NULL;
	     task = NEXT(task, link)) {
		LOCK(&task->lock);
897 898
		if ((task->flags & TASK_F_SHUTTINGDOWN) == 0) {
			task->flags |= TASK_F_SHUTTINGDOWN;
Bob Halley's avatar
update  
Bob Halley committed
899
			if (task->state == task_state_idle) {
900 901 902 903 904 905 906 907 908 909
				INSIST(EMPTY(task->events));
				if (EMPTY(task->on_shutdown) &&
				    TASK_DONE(task)) {
					task->flags &= ~TASK_F_SENDOK;
					task->state = task_state_done;
				} else {
					task->state = task_state_ready;
					ENQUEUE(manager->ready_tasks, task,
						ready_link);
				}
Bob Halley's avatar
update  
Bob Halley committed
910 911
			}
			INSIST(task->state == task_state_ready ||
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
			       task->state == task_state_running ||
			       task->state == task_state_done);
			if (task->state != task_state_done) {
				/*
				 * Note that we post shutdown events LIFO.
				 */
				for (event = TAIL(task->on_shutdown);
				     event != NULL;
				     event = prev) {
					prev = PREV(event, link);
					DEQUEUE(task->on_shutdown, event,
						link);
					ENQUEUE(task->events, event, link);
				}
			}
Bob Halley's avatar
base  
Bob Halley committed
927 928 929 930 931 932 933 934 935 936
		}
		UNLOCK(&task->lock);
	}

	/*
	 * Wake up any sleeping workers.  This ensures we get work done if
	 * there's work left to do, and if there are already no tasks left
	 * it will cause the workers to see manager->exiting.
	 */
	BROADCAST(&manager->work_available);
937
	UNLOCK(&manager->lock);
Bob Halley's avatar
base  
Bob Halley committed
938 939 940 941

	/*
	 * Wait for all the worker threads to exit.
	 */
942 943
	for (i = 0; i < manager->workers; i++)
		(void)isc_thread_join(manager->threads[i], NULL);
Bob Halley's avatar
base  
Bob Halley committed
944 945 946 947 948

	manager_free(manager);

	*managerp = NULL;
}