task.c 21 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
#include <config.h>

Bob Halley's avatar
base  
Bob Halley committed
20
#include <isc/assertions.h>
21
#include <isc/boolean.h>
Bob Halley's avatar
Bob Halley committed
22
#include <isc/thread.h>
23 24
#include <isc/mutex.h>
#include <isc/condition.h>
25
#include <isc/error.h>
Bob Halley's avatar
Bob Halley committed
26
#include <isc/task.h>
Bob Halley's avatar
base  
Bob Halley committed
27

28
#include "util.h"
Bob Halley's avatar
base  
Bob Halley committed
29

Bob Halley's avatar
Bob Halley committed
30
#ifdef ISC_TASK_TRACE
Bob Halley's avatar
update  
Bob Halley committed
31
#define XTRACE(m)		printf("%s task %p thread %lu\n", (m), \
Bob Halley's avatar
Bob Halley committed
32
				       task, isc_thread_self())
Bob Halley's avatar
Bob Halley committed
33 34 35
#else
#define XTRACE(m)
#endif
Bob Halley's avatar
base  
Bob Halley committed
36

Bob Halley's avatar
Bob Halley committed
37 38

/***
Bob Halley's avatar
Bob Halley committed
39
 *** Types.
Bob Halley's avatar
Bob Halley committed
40 41
 ***/

Bob Halley's avatar
Bob Halley committed
42 43 44 45 46 47 48 49 50
typedef enum {
	task_state_idle, task_state_ready, task_state_running,
	task_state_shutdown
} 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
51
struct isc_task {
Bob Halley's avatar
Bob Halley committed
52 53
	/* Not locked. */
	unsigned int			magic;
Bob Halley's avatar
Bob Halley committed
54
	isc_taskmgr_t *			manager;
Bob Halley's avatar
Bob Halley committed
55
	isc_mutex_t			lock;
Bob Halley's avatar
Bob Halley committed
56
	isc_mem_t *			mctx;
Bob Halley's avatar
Bob Halley committed
57 58 59
	/* Locked by task lock. */
	task_state_t			state;
	unsigned int			references;
Bob Halley's avatar
Bob Halley committed
60
	isc_eventlist_t			events;
61
	isc_eventlist_t			on_shutdown;
Bob Halley's avatar
Bob Halley committed
62
	unsigned int			quantum;
Bob Halley's avatar
Bob Halley committed
63
	isc_boolean_t			enqueue_allowed;
64
	isc_boolean_t			shutting_down;
Bob Halley's avatar
Bob Halley committed
65
	/* Locked by task manager lock. */
Bob Halley's avatar
Bob Halley committed
66 67
	LINK(isc_task_t)		link;
	LINK(isc_task_t)		ready_link;
Bob Halley's avatar
Bob Halley committed
68 69 70 71 72 73
};

#define TASK_MANAGER_MAGIC		0x54534B4DU	/* TSKM. */
#define VALID_MANAGER(m)		((m) != NULL && \
					 (m)->magic == TASK_MANAGER_MAGIC)

Bob Halley's avatar
Bob Halley committed
74
struct isc_taskmgr {
Bob Halley's avatar
Bob Halley committed
75 76
	/* Not locked. */
	unsigned int			magic;
Bob Halley's avatar
Bob Halley committed
77
	isc_mem_t *			mctx;
Bob Halley's avatar
Bob Halley committed
78
	isc_mutex_t			lock;
79 80
	unsigned int			workers;
	isc_thread_t *			threads;
Bob Halley's avatar
Bob Halley committed
81 82
	/* Locked by task manager lock. */
	unsigned int			default_quantum;
Bob Halley's avatar
Bob Halley committed
83 84
	LIST(isc_task_t)		tasks;
	LIST(isc_task_t)		ready_tasks;
Bob Halley's avatar
Bob Halley committed
85
	isc_condition_t			work_available;
Bob Halley's avatar
Bob Halley committed
86
	isc_boolean_t			exiting;
Bob Halley's avatar
Bob Halley committed
87
};
Bob Halley's avatar
Bob Halley committed
88

Bob Halley's avatar
Bob Halley committed
89 90
#define DEFAULT_DEFAULT_QUANTUM		5
#define FINISHED(m)			((m)->exiting && EMPTY((m)->tasks))
Bob Halley's avatar
Bob Halley committed
91

Bob Halley's avatar
update  
Bob Halley committed
92 93 94 95 96

/***
 *** Events.
 ***/

Bob Halley's avatar
Bob Halley committed
97
static inline isc_event_t *
Bob Halley's avatar
Bob Halley committed
98
event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
Bob Halley's avatar
Bob Halley committed
99
	       isc_taskaction_t action, void *arg, size_t size)
Bob Halley's avatar
update  
Bob Halley committed
100
{
Bob Halley's avatar
Bob Halley committed
101
	isc_event_t *event;
Bob Halley's avatar
update  
Bob Halley committed
102

Bob Halley's avatar
Bob Halley committed
103
	event = isc_mem_get(mctx, size);
Bob Halley's avatar
update  
Bob Halley committed
104 105 106 107
	if (event == NULL)
		return (NULL);
	event->mctx = mctx;
	event->size = size;
Bob Halley's avatar
Bob Halley committed
108
	event->sender = sender;
Bob Halley's avatar
update  
Bob Halley committed
109 110 111
	event->type = type;
	event->action = action;
	event->arg = arg;
112 113
	event->destroy = NULL;
	INIT_LINK(event, link);
Bob Halley's avatar
update  
Bob Halley committed
114 115 116 117

	return (event);
}

Bob Halley's avatar
Bob Halley committed
118
isc_event_t *
Bob Halley's avatar
Bob Halley committed
119
isc_event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
Bob Halley's avatar
Bob Halley committed
120
		   isc_taskaction_t action, void *arg, size_t size)
Bob Halley's avatar
update  
Bob Halley committed
121
{
Bob Halley's avatar
Bob Halley committed
122
	if (size < sizeof (struct isc_event))
Bob Halley's avatar
update  
Bob Halley committed
123 124 125 126 127 128
		return (NULL);
	if (type < 0)
		return (NULL);
	if (action == NULL)
		return (NULL);

Bob Halley's avatar
Bob Halley committed
129
	return (event_allocate(mctx, sender, type, action, arg, size));
Bob Halley's avatar
update  
Bob Halley committed
130 131 132
}

void
Bob Halley's avatar
Bob Halley committed
133 134
isc_event_free(isc_event_t **eventp) {
	isc_event_t *event;
Bob Halley's avatar
update  
Bob Halley committed
135 136 137 138 139
	
	REQUIRE(eventp != NULL);
	event = *eventp;
	REQUIRE(event != NULL);

Bob Halley's avatar
Bob Halley committed
140 141
	if (event->destroy != NULL)
		(event->destroy)(event);
Bob Halley's avatar
Bob Halley committed
142
	isc_mem_put(event->mctx, event, event->size);
Bob Halley's avatar
update  
Bob Halley committed
143 144 145 146

	*eventp = NULL;
}

Bob Halley's avatar
base  
Bob Halley committed
147 148 149 150 151
/***
 *** Tasks.
 ***/

static void
Bob Halley's avatar
Bob Halley committed
152 153
task_free(isc_task_t *task) {
	isc_taskmgr_t *manager = task->manager;
Bob Halley's avatar
base  
Bob Halley committed
154

Bob Halley's avatar
Bob Halley committed
155
	XTRACE("free task");
Bob Halley's avatar
base  
Bob Halley committed
156
	REQUIRE(EMPTY(task->events));
157
	REQUIRE(EMPTY(task->on_shutdown));
Bob Halley's avatar
base  
Bob Halley committed
158 159 160 161 162 163 164 165 166 167 168 169 170

	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
171
	(void)isc_mutex_destroy(&task->lock);
Bob Halley's avatar
base  
Bob Halley committed
172
	task->magic = 0;
173
	isc_mem_put(task->mctx, task, sizeof *task);
Bob Halley's avatar
base  
Bob Halley committed
174 175
}

Bob Halley's avatar
Bob Halley committed
176
isc_result_t
Bob Halley's avatar
Bob Halley committed
177
isc_task_create(isc_taskmgr_t *manager, isc_mem_t *mctx, unsigned int quantum,
178
		isc_task_t **taskp)
Bob Halley's avatar
Bob Halley committed
179
{
Bob Halley's avatar
Bob Halley committed
180
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
181 182 183 184

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

Bob Halley's avatar
Bob Halley committed
185 186 187
	if (mctx == NULL)
		mctx = manager->mctx;
	task = isc_mem_get(mctx, sizeof *task);
Bob Halley's avatar
base  
Bob Halley committed
188
	if (task == NULL)
Bob Halley's avatar
Bob Halley committed
189
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
190
	task->manager = manager;
Bob Halley's avatar
Bob Halley committed
191
	task->mctx = mctx;
Bob Halley's avatar
Bob Halley committed
192
	if (isc_mutex_init(&task->lock) != ISC_R_SUCCESS) {
Bob Halley's avatar
Bob Halley committed
193
		isc_mem_put(mctx, task, sizeof *task);
Bob Halley's avatar
Bob Halley committed
194 195 196
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
197
	}
Bob Halley's avatar
base  
Bob Halley committed
198 199 200
	task->state = task_state_idle;
	task->references = 1;
	INIT_LIST(task->events);
201
	INIT_LIST(task->on_shutdown);
Bob Halley's avatar
base  
Bob Halley committed
202
	task->quantum = quantum;
Bob Halley's avatar
Bob Halley committed
203
	task->enqueue_allowed = ISC_TRUE;
204
	task->shutting_down = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
205 206 207 208
	INIT_LINK(task, link);
	INIT_LINK(task, ready_link);

	LOCK(&manager->lock);
Bob Halley's avatar
Bob Halley committed
209
	/* XXX Should disallow if task manager is exiting. */
Bob Halley's avatar
base  
Bob Halley committed
210 211 212 213 214
	if (task->quantum == 0)
		task->quantum = manager->default_quantum;
	APPEND(manager->tasks, task, link);
	UNLOCK(&manager->lock);

215
	task->magic = TASK_MAGIC;
Bob Halley's avatar
base  
Bob Halley committed
216 217
	*taskp = task;

Bob Halley's avatar
Bob Halley committed
218
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
219 220
}

Bob Halley's avatar
update  
Bob Halley committed
221
void
Bob Halley's avatar
Bob Halley committed
222
isc_task_attach(isc_task_t *task, isc_task_t **taskp) {
Bob Halley's avatar
base  
Bob Halley committed
223 224 225 226 227 228 229 230 231 232 233

	REQUIRE(VALID_TASK(task));
	REQUIRE(taskp != NULL && *taskp == NULL);

	LOCK(&task->lock);
	task->references++;
	UNLOCK(&task->lock);

	*taskp = task;
}

Bob Halley's avatar
Bob Halley committed
234
void
Bob Halley's avatar
Bob Halley committed
235
isc_task_detach(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
236
	isc_boolean_t free_task = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
237
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
238

Bob Halley's avatar
Bob Halley committed
239
	XTRACE("isc_task_detach");
Bob Halley's avatar
base  
Bob Halley committed
240 241 242 243 244 245 246 247

	REQUIRE(taskp != NULL);
	task = *taskp;
	REQUIRE(VALID_TASK(task));

	LOCK(&task->lock);
	REQUIRE(task->references > 0);
	task->references--;
248
	if (task->state == task_state_shutdown && task->references == 0)
Bob Halley's avatar
Bob Halley committed
249
		free_task = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
250 251 252 253 254 255 256 257
	UNLOCK(&task->lock);

	if (free_task)
		task_free(task);

	*taskp = NULL;
}

Bob Halley's avatar
Bob Halley committed
258 259 260 261 262 263 264 265
isc_mem_t *
isc_task_mem(isc_task_t *task) {

	REQUIRE(VALID_TASK(task));
	
	return (task->mctx);
}

266
isc_result_t
Bob Halley's avatar
Bob Halley committed
267
isc_task_send(isc_task_t *task, isc_event_t **eventp) {
Bob Halley's avatar
Bob Halley committed
268
	isc_boolean_t was_idle = ISC_FALSE;
269 270
	isc_boolean_t disallowed = ISC_FALSE;
	isc_result_t result = ISC_R_SUCCESS;
Bob Halley's avatar
Bob Halley committed
271
	isc_event_t *event;
Bob Halley's avatar
base  
Bob Halley committed
272 273

	REQUIRE(VALID_TASK(task));
Bob Halley's avatar
update  
Bob Halley committed
274 275
	REQUIRE(eventp != NULL);
	event = *eventp;
Bob Halley's avatar
base  
Bob Halley committed
276
	REQUIRE(event != NULL);
Bob Halley's avatar
Bob Halley committed
277 278
	REQUIRE(event->sender != NULL);
	REQUIRE(event->type > 0);
Bob Halley's avatar
base  
Bob Halley committed
279

Bob Halley's avatar
Bob Halley committed
280
	XTRACE("sending");
Bob Halley's avatar
base  
Bob Halley committed
281 282 283 284 285 286
	/*
	 * 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);
287 288 289 290
	/*
	 * Note: we require that task->shutting_down implies
	 * !task->enqueue_allowed.
	 */
Bob Halley's avatar
update  
Bob Halley committed
291
	if (task->enqueue_allowed) {
Bob Halley's avatar
base  
Bob Halley committed
292
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
293
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
294 295 296 297 298 299
			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);
300 301 302 303 304 305 306 307 308
	} else {
		disallowed = ISC_TRUE;
		if (task->state == task_state_shutdown)
			result = ISC_R_TASKSHUTDOWN;
		else if (task->shutting_down)
			result = ISC_R_TASKSHUTTINGDOWN;
		else
			result = ISC_R_TASKNOSEND;
	}
Bob Halley's avatar
base  
Bob Halley committed
309 310
	UNLOCK(&task->lock);

311 312
	if (disallowed)
		return (result);
Bob Halley's avatar
base  
Bob Halley committed
313 314

	if (was_idle) {
Bob Halley's avatar
Bob Halley committed
315
		isc_taskmgr_t *manager;
Bob Halley's avatar
base  
Bob Halley committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339

		/*
		 * 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
		 * be trying to add this task to the ready queue.  It
		 * thus doesn't matter if more events have been added to
		 * the queue after we gave up the task lock.
		 *
		 * Shutting down a task requires posting a shutdown event
		 * to the task's queue and then executing it, so there's
		 * no way the task can disappear.  A task is always on the
		 * task manager's 'tasks' list, so the task manager can
		 * always post a shutdown event to all tasks if it is
		 * requested to shutdown.
		 */
		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
340
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
341 342 343
		UNLOCK(&manager->lock);
	}

Bob Halley's avatar
update  
Bob Halley committed
344 345
	*eventp = NULL;

Bob Halley's avatar
Bob Halley committed
346
	XTRACE("sent");
347 348

	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
349 350
}

351
unsigned int
Bob Halley's avatar
Bob Halley committed
352 353
isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type) {
	isc_event_t *event, *next_event;
Bob Halley's avatar
Bob Halley committed
354
	isc_eventlist_t purgeable;
355
	unsigned int purge_count;
Bob Halley's avatar
Bob Halley committed
356 357 358 359 360 361 362 363

	REQUIRE(VALID_TASK(task));
	REQUIRE(type >= 0);

	/*
	 * Purge events matching 'sender' and 'type'.  sender == NULL means
	 * "any sender".  type == NULL means any type.  Task manager events
	 * cannot be purged.
364 365
	 *
	 * Purging never changes the state of the task.
Bob Halley's avatar
Bob Halley committed
366 367 368
	 */

	INIT_LIST(purgeable);
369
	purge_count = 0;
Bob Halley's avatar
Bob Halley committed
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387

	LOCK(&task->lock);
	for (event = HEAD(task->events);
	     event != NULL;
	     event = next_event) {
		next_event = NEXT(event, link);
		if ((sender == NULL || event->sender == sender) &&
		    ((type == 0 && event->type > 0) || event->type == type)) {
			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
388
		isc_event_free(&event);
389
		purge_count++;
Bob Halley's avatar
Bob Halley committed
390
	}
391 392

	return (purge_count);
Bob Halley's avatar
Bob Halley committed
393 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
isc_result_t
isc_task_allowsend(isc_task_t *task, isc_boolean_t enqueue_allowed) {
	isc_result_t result = ISC_R_SUCCESS;

	REQUIRE(VALID_TASK(task));

	LOCK(&task->lock);
	if (task->state == task_state_shutdown)
		result = ISC_R_TASKSHUTDOWN;
	else if (task->shutting_down)
		result = ISC_R_TASKSHUTTINGDOWN;
	else
		task->enqueue_allowed = enqueue_allowed;
	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;

	REQUIRE(VALID_TASK(task));

Bob Halley's avatar
Bob Halley committed
421
	event = event_allocate(task->mctx,
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
			       NULL,
			       ISC_TASKEVENT_SHUTDOWN,
			       action,
			       arg,
			       sizeof *event);
	if (event == NULL)
		return (ISC_R_NOMEMORY);

	LOCK(&task->lock);
	if (task->state == task_state_shutdown) {
		disallowed = ISC_TRUE;
		result = ISC_R_TASKSHUTDOWN;
	} else if (task->shutting_down) {
		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
442
		isc_mem_put(task->mctx, event, sizeof *event);
443 444 445 446

	return (result);
}

Bob Halley's avatar
Bob Halley committed
447
void
Bob Halley's avatar
Bob Halley committed
448
isc_task_shutdown(isc_task_t *task) {
Bob Halley's avatar
Bob Halley committed
449
	isc_boolean_t was_idle = ISC_FALSE;
450 451
	isc_boolean_t queued_something = ISC_FALSE;
	isc_event_t *event, *prev;
Bob Halley's avatar
base  
Bob Halley committed
452 453 454 455

	REQUIRE(VALID_TASK(task));

	/*
456
	 * This routine is very similar to isc_task_send() above.
Bob Halley's avatar
base  
Bob Halley committed
457 458 459
	 */

	LOCK(&task->lock);
460
	if (!task->shutting_down) {
Bob Halley's avatar
base  
Bob Halley committed
461
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
462
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
463 464 465 466 467
			INSIST(EMPTY(task->events));
			task->state = task_state_ready;
		}
		INSIST(task->state == task_state_ready ||
		       task->state == task_state_running);
468 469 470 471 472 473 474 475 476 477 478
		/*
		 * 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;
		}
Bob Halley's avatar
Bob Halley committed
479
		task->enqueue_allowed = ISC_FALSE;
480 481
		task->shutting_down = ISC_TRUE;
	}
Bob Halley's avatar
base  
Bob Halley committed
482 483
	UNLOCK(&task->lock);

484
	if (was_idle && queued_something) {
Bob Halley's avatar
Bob Halley committed
485
		isc_taskmgr_t *manager;
Bob Halley's avatar
base  
Bob Halley committed
486 487 488 489 490

		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
491
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
492 493
		UNLOCK(&manager->lock);
	}
Bob Halley's avatar
Bob Halley committed
494
}
Bob Halley's avatar
base  
Bob Halley committed
495

Bob Halley's avatar
Bob Halley committed
496
void
Bob Halley's avatar
Bob Halley committed
497
isc_task_destroy(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
498 499 500

	REQUIRE(taskp != NULL);

Bob Halley's avatar
Bob Halley committed
501 502
	isc_task_shutdown(*taskp);
	isc_task_detach(taskp);
Bob Halley's avatar
base  
Bob Halley committed
503 504 505
}


Bob Halley's avatar
Bob Halley committed
506

Bob Halley's avatar
base  
Bob Halley committed
507 508 509 510
/***
 *** Task Manager.
 ***/

Bob Halley's avatar
update  
Bob Halley committed
511
static isc_threadresult_t
Bob Halley's avatar
Bob Halley committed
512 513 514
#ifdef _WIN32
WINAPI
#endif
Bob Halley's avatar
update  
Bob Halley committed
515
run(void *uap) {
Bob Halley's avatar
Bob Halley committed
516 517
	isc_taskmgr_t *manager = uap;
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
518

Bob Halley's avatar
Bob Halley committed
519
	XTRACE("start");
Bob Halley's avatar
base  
Bob Halley committed
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576

	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
577
		 * isc_task_send() above, it is safe for us to dequeue
Bob Halley's avatar
base  
Bob Halley committed
578 579 580 581 582
		 * 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)) {
Bob Halley's avatar
Bob Halley committed
583
			XTRACE("wait");
Bob Halley's avatar
base  
Bob Halley committed
584
			WAIT(&manager->work_available, &manager->lock);
Bob Halley's avatar
Bob Halley committed
585
			XTRACE("awake");
Bob Halley's avatar
base  
Bob Halley committed
586
		}
Bob Halley's avatar
Bob Halley committed
587
		XTRACE("working");
Bob Halley's avatar
base  
Bob Halley committed
588 589 590
		
		task = HEAD(manager->ready_tasks);
		if (task != NULL) {
Bob Halley's avatar
Bob Halley committed
591
			unsigned int dispatch_count = 0;
Bob Halley's avatar
Bob Halley committed
592 593 594
			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
595
			isc_event_t *event;
Bob Halley's avatar
base  
Bob Halley committed
596 597 598 599 600 601 602 603 604 605 606 607

			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
608 609 610 611 612 613 614 615
			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
616
				done = ISC_TRUE;
Bob Halley's avatar
Bob Halley committed
617 618 619
				XTRACE("ready but empty");
			} else
				task->state = task_state_running;
Bob Halley's avatar
base  
Bob Halley committed
620
			while (!done) {
Bob Halley's avatar
update  
Bob Halley committed
621 622 623
				INSIST(!EMPTY(task->events));
				event = HEAD(task->events);
				DEQUEUE(task->events, event, link);
Bob Halley's avatar
base  
Bob Halley committed
624 625 626 627 628
				UNLOCK(&task->lock);

				/*
				 * Execute the event action.
				 */
Bob Halley's avatar
Bob Halley committed
629
				XTRACE("execute action");
Bob Halley's avatar
update  
Bob Halley committed
630
				if (event->action != NULL)
631
					(event->action)(task, event);
Bob Halley's avatar
Bob Halley committed
632
				dispatch_count++;
Bob Halley's avatar
update  
Bob Halley committed
633
				
Bob Halley's avatar
base  
Bob Halley committed
634
				LOCK(&task->lock);
635
				if (EMPTY(task->events)) {
Bob Halley's avatar
Bob Halley committed
636
					/*
637 638 639 640
					 * 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
641
					 */
Bob Halley's avatar
Bob Halley committed
642
					XTRACE("empty");
643 644 645 646 647 648 649 650
					if (task->shutting_down) {
						XTRACE("shutdown");
						if (task->references == 0)
							free_task = ISC_TRUE;
						task->state =
							task_state_shutdown;
					} else
						task->state = task_state_idle;
Bob Halley's avatar
Bob Halley committed
651
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
652 653 654 655 656 657 658 659 660 661 662
				} 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
663
					XTRACE("quantum");
Bob Halley's avatar
base  
Bob Halley committed
664
					task->state = task_state_ready;
Bob Halley's avatar
Bob Halley committed
665 666
					requeue = ISC_TRUE;
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
				}
			}
			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);

Bob Halley's avatar
Bob Halley committed
702
	XTRACE("exit");
Bob Halley's avatar
base  
Bob Halley committed
703

Bob Halley's avatar
update  
Bob Halley committed
704
	return ((isc_threadresult_t)0);
Bob Halley's avatar
base  
Bob Halley committed
705 706 707
}

static void
Bob Halley's avatar
Bob Halley committed
708
manager_free(isc_taskmgr_t *manager) {
Bob Halley's avatar
Bob Halley committed
709 710
	(void)isc_condition_destroy(&manager->work_available);
	(void)isc_mutex_destroy(&manager->lock);
711 712
	isc_mem_put(manager->mctx, manager->threads,
		    manager->workers * sizeof (isc_thread_t));
Bob Halley's avatar
base  
Bob Halley committed
713
	manager->magic = 0;
Bob Halley's avatar
Bob Halley committed
714
	isc_mem_put(manager->mctx, manager, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
715 716
}

Bob Halley's avatar
Bob Halley committed
717
isc_result_t
Bob Halley's avatar
Bob Halley committed
718
isc_taskmgr_create(isc_mem_t *mctx, unsigned int workers, 
Bob Halley's avatar
Bob Halley committed
719
		   unsigned int default_quantum, isc_taskmgr_t **managerp)
Bob Halley's avatar
Bob Halley committed
720 721
{
	unsigned int i, started = 0;
Bob Halley's avatar
Bob Halley committed
722
	isc_taskmgr_t *manager;
723
	isc_thread_t *threads;
Bob Halley's avatar
Bob Halley committed
724 725

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

Bob Halley's avatar
Bob Halley committed
728
	manager = isc_mem_get(mctx, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
729
	if (manager == NULL)
Bob Halley's avatar
Bob Halley committed
730
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
731 732
	manager->magic = TASK_MANAGER_MAGIC;
	manager->mctx = mctx;
733 734 735 736 737 738 739
	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
740
	if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
741
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
742
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
743 744 745
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
746
	}
Bob Halley's avatar
base  
Bob Halley committed
747 748 749 750 751
	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
752 753
	if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
		(void)isc_mutex_destroy(&manager->lock);
754
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
755
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
756 757 758
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_condition_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
759
	}
Bob Halley's avatar
Bob Halley committed
760
	manager->exiting = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
761 762 763 764 765 766 767
	manager->workers = 0;

	LOCK(&manager->lock);
	/*
	 * Start workers.
	 */
	for (i = 0; i < workers; i++) {
768 769
		if (isc_thread_create(run, manager,
				      &manager->threads[manager->workers]) == 
Bob Halley's avatar
Bob Halley committed
770
		    ISC_R_SUCCESS) {
Bob Halley's avatar
base  
Bob Halley committed
771 772 773 774 775 776 777 778
			manager->workers++;
			started++;
		}
	}
	UNLOCK(&manager->lock);

	if (started == 0) {
		manager_free(manager);
Bob Halley's avatar
Bob Halley committed
779
		return (ISC_R_NOTHREADS);
Bob Halley's avatar
base  
Bob Halley committed
780 781 782 783
	}		

	*managerp = manager;

Bob Halley's avatar
Bob Halley committed
784
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
785 786
}

Bob Halley's avatar
update  
Bob Halley committed
787
void
Bob Halley's avatar
Bob Halley committed
788 789 790
isc_taskmgr_destroy(isc_taskmgr_t **managerp) {
	isc_taskmgr_t *manager;
	isc_task_t *task;
791
	isc_event_t *event, *prev;
792
	unsigned int i;
Bob Halley's avatar
base  
Bob Halley committed
793 794 795 796 797

	REQUIRE(managerp != NULL);
	manager = *managerp;
	REQUIRE(VALID_MANAGER(manager));

Bob Halley's avatar
Bob Halley committed
798
	XTRACE("isc_taskmgr_destroy");
Bob Halley's avatar
base  
Bob Halley committed
799 800 801 802
	/*
	 * 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
803
	 * isc_taskmgr_destroy(), e.g. by signalling a condition variable
Bob Halley's avatar
base  
Bob Halley committed
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
	 * 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
822
	manager->exiting = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
823 824

	/*
Bob Halley's avatar
update  
Bob Halley committed
825 826
	 * Post the shutdown event to every task (if it hasn't already been
	 * posted).
Bob Halley's avatar
base  
Bob Halley committed
827 828 829 830 831
	 */
	for (task = HEAD(manager->tasks);
	     task != NULL;
	     task = NEXT(task, link)) {
		LOCK(&task->lock);
832 833 834 835 836 837 838 839 840 841 842
		if (!task->shutting_down) {
			/*
			 * 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
update  
Bob Halley committed
843 844 845 846 847 848 849
			if (task->state == task_state_idle) {
				task->state = task_state_ready;
				ENQUEUE(manager->ready_tasks, task,
					ready_link);
			}
			INSIST(task->state == task_state_ready ||
			       task->state == task_state_running);
Bob Halley's avatar
Bob Halley committed
850
			task->enqueue_allowed = ISC_FALSE;
851
			task->shutting_down = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
852 853 854 855 856 857 858 859 860 861
		}
		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);
862
	UNLOCK(&manager->lock);
Bob Halley's avatar
base  
Bob Halley committed
863 864 865 866

	/*
	 * Wait for all the worker threads to exit.
	 */
867 868
	for (i = 0; i < manager->workers; i++)
		(void)isc_thread_join(manager->threads[i], NULL);
Bob Halley's avatar
base  
Bob Halley committed
869 870 871 872 873

	manager_free(manager);

	*managerp = NULL;
}