task.c 23.4 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));
}

Bob Halley's avatar
Bob Halley committed
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
isc_boolean_t
isc_task_purgeevent(isc_task_t *task, isc_event_t *event) {
	isc_event_t *curr_event, *next_event;

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

	REQUIRE(VALID_TASK(task));

	/*
	 * If 'event' is on the task's event queue, it will be purged,
	 * unless it is marked as unpurgeable.  'event' does not have to be
	 * on the task's event queue; in fact, it can even be an invalid
	 * pointer.  Purging only occurs if the event is actually on the task's
	 * event queue.
	 *
	 * Purging never changes the state of the task.
	 */

	LOCK(&task->lock);
	for (curr_event = HEAD(task->events);
	     curr_event != NULL;
	     curr_event = next_event) {
		next_event = NEXT(curr_event, link);
		if (curr_event == event &&
		    (event->attributes & ISC_EVENTATTR_NOPURGE) == 0) {
			DEQUEUE(task->events, curr_event, link);
			break;
		}
	}
	UNLOCK(&task->lock);

	if (curr_event == NULL)
		return (ISC_FALSE);

	isc_event_free(&curr_event);

	return (ISC_TRUE);
}

435
isc_result_t
436
isc_task_allowsend(isc_task_t *task, isc_boolean_t allowed) {
437 438
	isc_result_t result = ISC_R_SUCCESS;

439 440 441 442
	/*
	 * Allow or disallow sending events to 'task'.
	 */

443 444 445
	REQUIRE(VALID_TASK(task));

	LOCK(&task->lock);
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
	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;

463 464 465 466
	/*
	 * Allow or disallow automatic termination of 'task'.
	 */

467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
	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;
	}
483 484 485 486 487 488 489 490 491 492 493
	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;

494 495 496 497 498
	/*
	 * Send a shutdown event with action 'action' and argument 'arg' when
	 * 'task' is shutdown.
	 */

499
	REQUIRE(VALID_TASK(task));
500
	REQUIRE(action != NULL);
501

502 503 504 505 506 507
	event = isc_event_allocate(task->mctx,
				   NULL,
				   ISC_TASKEVENT_SHUTDOWN,
				   action,
				   arg,
				   sizeof *event);
508 509 510 511
	if (event == NULL)
		return (ISC_R_NOMEMORY);

	LOCK(&task->lock);
512
	if (task->state == task_state_done) {
513
		disallowed = ISC_TRUE;
514 515
		result = ISC_R_TASKDONE;
	} else if ((task->flags & TASK_F_SHUTTINGDOWN) != 0) {
516 517 518 519 520 521 522
		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
523
		isc_mem_put(task->mctx, event, sizeof *event);
524 525 526 527

	return (result);
}

Bob Halley's avatar
Bob Halley committed
528
void
Bob Halley's avatar
Bob Halley committed
529
isc_task_shutdown(isc_task_t *task) {
Bob Halley's avatar
Bob Halley committed
530
	isc_boolean_t was_idle = ISC_FALSE;
531 532
	isc_boolean_t queued_something = ISC_FALSE;
	isc_event_t *event, *prev;
Bob Halley's avatar
base  
Bob Halley committed
533

534 535 536 537
	/*
	 * Shutdown 'task'.
	 */

Bob Halley's avatar
base  
Bob Halley committed
538 539 540
	REQUIRE(VALID_TASK(task));

	/*
541
	 * This routine is very similar to isc_task_send() above.
Bob Halley's avatar
base  
Bob Halley committed
542 543 544
	 */

	LOCK(&task->lock);
545 546 547
	if ((task->flags & TASK_F_SHUTTINGDOWN) == 0) {
		XTRACE("shutting down");
		task->flags |= TASK_F_SHUTTINGDOWN;
Bob Halley's avatar
base  
Bob Halley committed
548
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
549
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
550
			INSIST(EMPTY(task->events));
551 552 553 554 555
			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
556 557
		}
		INSIST(task->state == task_state_ready ||
558 559 560 561 562 563 564 565 566 567 568 569 570 571
		       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;
			}
572 573
		}
	}
Bob Halley's avatar
base  
Bob Halley committed
574 575
	UNLOCK(&task->lock);

576
	if (was_idle && queued_something) {
Bob Halley's avatar
Bob Halley committed
577
		isc_taskmgr_t *manager;
Bob Halley's avatar
base  
Bob Halley committed
578 579 580 581 582

		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
583
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
584 585
		UNLOCK(&manager->lock);
	}
Bob Halley's avatar
Bob Halley committed
586
}
Bob Halley's avatar
base  
Bob Halley committed
587

Bob Halley's avatar
Bob Halley committed
588
void
Bob Halley's avatar
Bob Halley committed
589
isc_task_destroy(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
590

591 592 593 594
	/*
	 * Destroy '*taskp'.
	 */

Bob Halley's avatar
Bob Halley committed
595 596
	REQUIRE(taskp != NULL);

Bob Halley's avatar
Bob Halley committed
597 598
	isc_task_shutdown(*taskp);
	isc_task_detach(taskp);
Bob Halley's avatar
base  
Bob Halley committed
599 600 601
}


Bob Halley's avatar
Bob Halley committed
602

Bob Halley's avatar
base  
Bob Halley committed
603 604 605 606
/***
 *** Task Manager.
 ***/

Bob Halley's avatar
update  
Bob Halley committed
607
static isc_threadresult_t
Bob Halley's avatar
Bob Halley committed
608 609 610
#ifdef _WIN32
WINAPI
#endif
Bob Halley's avatar
update  
Bob Halley committed
611
run(void *uap) {
Bob Halley's avatar
Bob Halley committed
612 613
	isc_taskmgr_t *manager = uap;
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
614

615
	XTHREADTRACE("start");
Bob Halley's avatar
base  
Bob Halley committed
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672

	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
673
		 * isc_task_send() above, it is safe for us to dequeue
Bob Halley's avatar
base  
Bob Halley committed
674 675 676 677 678
		 * 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)) {
679
			XTHREADTRACE("wait");
Bob Halley's avatar
base  
Bob Halley committed
680
			WAIT(&manager->work_available, &manager->lock);
681
			XTHREADTRACE("awake");
Bob Halley's avatar
base  
Bob Halley committed
682
		}
683
		XTHREADTRACE("working");
Bob Halley's avatar
base  
Bob Halley committed
684 685 686
		
		task = HEAD(manager->ready_tasks);
		if (task != NULL) {
Bob Halley's avatar
Bob Halley committed
687
			unsigned int dispatch_count = 0;
Bob Halley's avatar
Bob Halley committed
688 689 690
			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
691
			isc_event_t *event;
Bob Halley's avatar
base  
Bob Halley committed
692 693 694 695 696 697 698 699 700 701 702 703

			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
704 705 706 707 708 709 710 711
			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
712
				done = ISC_TRUE;
Bob Halley's avatar
Bob Halley committed
713 714 715
				XTRACE("ready but empty");
			} else
				task->state = task_state_running;
Bob Halley's avatar
base  
Bob Halley committed
716
			while (!done) {
Bob Halley's avatar
update  
Bob Halley committed
717 718 719
				INSIST(!EMPTY(task->events));
				event = HEAD(task->events);
				DEQUEUE(task->events, event, link);
Bob Halley's avatar
base  
Bob Halley committed
720 721 722 723

				/*
				 * Execute the event action.
				 */
Bob Halley's avatar
Bob Halley committed
724
				XTRACE("execute action");
725 726
				if (event->action != NULL) {
					UNLOCK(&task->lock);
727
					(event->action)(task, event);
728 729
					LOCK(&task->lock);
				}
Bob Halley's avatar
Bob Halley committed
730
				dispatch_count++;
Bob Halley's avatar
update  
Bob Halley committed
731
				
732
				if (EMPTY(task->events)) {
Bob Halley's avatar
Bob Halley committed
733
					/*
734 735 736 737
					 * 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
738
					 */
Bob Halley's avatar
Bob Halley committed
739
					XTRACE("empty");
740 741
					if (TASK_DONE(task)) {
						XTRACE("done");
742 743
						if (task->references == 0)
							free_task = ISC_TRUE;
744 745 746
						task->flags &=
							~TASK_F_SENDOK;
						task->state = task_state_done;
747 748
					} else
						task->state = task_state_idle;
Bob Halley's avatar
Bob Halley committed
749
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
750 751 752 753 754 755 756 757 758 759 760
				} 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
761
					XTRACE("quantum");
Bob Halley's avatar
base  
Bob Halley committed
762
					task->state = task_state_ready;
Bob Halley's avatar
Bob Halley committed
763 764
					requeue = ISC_TRUE;
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
				}
			}
			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);

800
	XTHREADTRACE("exit");
Bob Halley's avatar
base  
Bob Halley committed
801

Bob Halley's avatar
update  
Bob Halley committed
802
	return ((isc_threadresult_t)0);
Bob Halley's avatar
base  
Bob Halley committed
803 804 805
}

static void
Bob Halley's avatar
Bob Halley committed
806
manager_free(isc_taskmgr_t *manager) {
Bob Halley's avatar
Bob Halley committed
807 808
	(void)isc_condition_destroy(&manager->work_available);
	(void)isc_mutex_destroy(&manager->lock);
809 810
	isc_mem_put(manager->mctx, manager->threads,
		    manager->workers * sizeof (isc_thread_t));
Bob Halley's avatar
base  
Bob Halley committed
811
	manager->magic = 0;
Bob Halley's avatar
Bob Halley committed
812
	isc_mem_put(manager->mctx, manager, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
813 814
}

Bob Halley's avatar
Bob Halley committed
815
isc_result_t
Bob Halley's avatar
Bob Halley committed
816
isc_taskmgr_create(isc_mem_t *mctx, unsigned int workers, 
Bob Halley's avatar
Bob Halley committed
817
		   unsigned int default_quantum, isc_taskmgr_t **managerp)
Bob Halley's avatar
Bob Halley committed
818 819
{
	unsigned int i, started = 0;
Bob Halley's avatar
Bob Halley committed
820
	isc_taskmgr_t *manager;
821
	isc_thread_t *threads;
Bob Halley's avatar
Bob Halley committed
822

823 824 825 826
	/*
	 * Create a new task manager.
	 */

Bob Halley's avatar
Bob Halley committed
827
	REQUIRE(workers > 0);
Bob Halley's avatar
Bob Halley committed
828
	REQUIRE(managerp != NULL && *managerp == NULL);
Bob Halley's avatar
base  
Bob Halley committed
829

Bob Halley's avatar
Bob Halley committed
830
	manager = isc_mem_get(mctx, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
831
	if (manager == NULL)
Bob Halley's avatar
Bob Halley committed
832
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
833 834
	manager->magic = TASK_MANAGER_MAGIC;
	manager->mctx = mctx;
835 836 837 838 839 840 841
	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
842
	if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
843
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
844
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
845 846 847
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
848
	}
Bob Halley's avatar
base  
Bob Halley committed
849 850 851 852 853
	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
854 855
	if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
		(void)isc_mutex_destroy(&manager->lock);
856
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
857
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
858 859 860
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_condition_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
861
	}
Bob Halley's avatar
Bob Halley committed
862
	manager->exiting = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
863 864 865 866 867 868 869
	manager->workers = 0;

	LOCK(&manager->lock);
	/*
	 * Start workers.
	 */
	for (i = 0; i < workers; i++) {
870 871
		if (isc_thread_create(run, manager,
				      &manager->threads[manager->workers]) == 
Bob Halley's avatar
Bob Halley committed
872
		    ISC_R_SUCCESS) {
Bob Halley's avatar
base  
Bob Halley committed
873 874 875 876 877 878 879 880
			manager->workers++;
			started++;
		}
	}
	UNLOCK(&manager->lock);

	if (started == 0) {
		manager_free(manager);
Bob Halley's avatar
Bob Halley committed
881
		return (ISC_R_NOTHREADS);
Bob Halley's avatar
base  
Bob Halley committed
882 883 884 885
	}		

	*managerp = manager;

Bob Halley's avatar
Bob Halley committed
886
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
887 888
}

Bob Halley's avatar
update  
Bob Halley committed
889
void
Bob Halley's avatar
Bob Halley committed
890 891 892
isc_taskmgr_destroy(isc_taskmgr_t **managerp) {
	isc_taskmgr_t *manager;
	isc_task_t *task;
893
	isc_event_t *event, *prev;
894
	unsigned int i;
Bob Halley's avatar
base  
Bob Halley committed
895

896 897 898 899
	/*
	 * Destroy '*managerp'.
	 */

Bob Halley's avatar
base  
Bob Halley committed
900 901 902 903
	REQUIRE(managerp != NULL);
	manager = *managerp;
	REQUIRE(VALID_MANAGER(manager));

904
	XTHREADTRACE("isc_taskmgr_destroy");
Bob Halley's avatar
base  
Bob Halley committed
905 906 907 908
	/*
	 * 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
909
	 * isc_taskmgr_destroy(), e.g. by signalling a condition variable
Bob Halley's avatar
base  
Bob Halley committed
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927
	 * 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
928
	manager->exiting = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
929 930

	/*
931
	 * Post shutdown event(s) to every task (if they haven't already been
Bob Halley's avatar
update  
Bob Halley committed
932
	 * posted).
Bob Halley's avatar
base  
Bob Halley committed
933 934 935 936 937
	 */
	for (task = HEAD(manager->tasks);
	     task != NULL;
	     task = NEXT(task, link)) {
		LOCK(&task->lock);
938 939
		if ((task->flags & TASK_F_SHUTTINGDOWN) == 0) {
			task->flags |= TASK_F_SHUTTINGDOWN;
Bob Halley's avatar
update  
Bob Halley committed
940
			if (task->state == task_state_idle) {
941 942 943 944 945 946 947 948 949 950
				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
951 952
			}
			INSIST(task->state == task_state_ready ||
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
			       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
968 969 970 971 972 973 974 975 976 977
		}
		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);
978
	UNLOCK(&manager->lock);
Bob Halley's avatar
base  
Bob Halley committed
979 980 981 982

	/*
	 * Wait for all the worker threads to exit.
	 */
983 984
	for (i = 0; i < manager->workers; i++)
		(void)isc_thread_join(manager->threads[i], NULL);
Bob Halley's avatar
base  
Bob Halley committed
985 986 987 988 989

	manager_free(manager);

	*managerp = NULL;
}