task.c 21.1 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
 */

Bob Halley's avatar
Bob Halley committed
22 23
#include <config.h>

Bob Halley's avatar
base  
Bob Halley committed
24
#include <isc/assertions.h>
25
#include <isc/boolean.h>
Bob Halley's avatar
Bob Halley committed
26
#include <isc/thread.h>
27 28
#include <isc/mutex.h>
#include <isc/condition.h>
29
#include <isc/error.h>
Bob Halley's avatar
Bob Halley committed
30
#include <isc/task.h>
Bob Halley's avatar
base  
Bob Halley committed
31

32
#include "util.h"
Bob Halley's avatar
base  
Bob Halley committed
33

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

Bob Halley's avatar
Bob Halley committed
41 42

/***
Bob Halley's avatar
Bob Halley committed
43
 *** Types.
Bob Halley's avatar
Bob Halley committed
44 45
 ***/

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

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

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

Bob Halley's avatar
Bob Halley committed
93 94
#define DEFAULT_DEFAULT_QUANTUM		5
#define FINISHED(m)			((m)->exiting && EMPTY((m)->tasks))
Bob Halley's avatar
Bob Halley committed
95

Bob Halley's avatar
update  
Bob Halley committed
96 97 98 99 100

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

Bob Halley's avatar
Bob Halley committed
101
static inline isc_event_t *
Bob Halley's avatar
Bob Halley committed
102
event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
Bob Halley's avatar
Bob Halley committed
103
	       isc_taskaction_t action, void *arg, size_t size)
Bob Halley's avatar
update  
Bob Halley committed
104
{
Bob Halley's avatar
Bob Halley committed
105
	isc_event_t *event;
Bob Halley's avatar
update  
Bob Halley committed
106

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

	return (event);
}

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

Bob Halley's avatar
Bob Halley committed
133
	return (event_allocate(mctx, sender, type, action, arg, size));
Bob Halley's avatar
update  
Bob Halley committed
134 135 136
}

void
Bob Halley's avatar
Bob Halley committed
137 138
isc_event_free(isc_event_t **eventp) {
	isc_event_t *event;
Bob Halley's avatar
update  
Bob Halley committed
139 140 141 142 143
	
	REQUIRE(eventp != NULL);
	event = *eventp;
	REQUIRE(event != NULL);

Bob Halley's avatar
Bob Halley committed
144 145
	if (event->destroy != NULL)
		(event->destroy)(event);
Bob Halley's avatar
Bob Halley committed
146
	isc_mem_put(event->mctx, event, event->size);
Bob Halley's avatar
update  
Bob Halley committed
147 148 149 150

	*eventp = NULL;
}

Bob Halley's avatar
base  
Bob Halley committed
151 152 153 154 155
/***
 *** Tasks.
 ***/

static void
Bob Halley's avatar
Bob Halley committed
156 157
task_free(isc_task_t *task) {
	isc_taskmgr_t *manager = task->manager;
Bob Halley's avatar
base  
Bob Halley committed
158

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

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

Bob Halley's avatar
Bob Halley committed
180
isc_result_t
Bob Halley's avatar
Bob Halley committed
181
isc_task_create(isc_taskmgr_t *manager, isc_mem_t *mctx, unsigned int quantum,
182
		isc_task_t **taskp)
Bob Halley's avatar
Bob Halley committed
183
{
Bob Halley's avatar
Bob Halley committed
184
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
185 186 187 188

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

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

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

219
	task->magic = TASK_MAGIC;
Bob Halley's avatar
base  
Bob Halley committed
220 221
	*taskp = task;

Bob Halley's avatar
Bob Halley committed
222
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
223 224
}

Bob Halley's avatar
update  
Bob Halley committed
225
void
Bob Halley's avatar
Bob Halley committed
226
isc_task_attach(isc_task_t *task, isc_task_t **taskp) {
Bob Halley's avatar
base  
Bob Halley committed
227 228 229 230 231 232 233 234 235 236 237

	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
238
void
Bob Halley's avatar
Bob Halley committed
239
isc_task_detach(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
240
	isc_boolean_t free_task = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
241
	isc_task_t *task;
Bob Halley's avatar
base  
Bob Halley committed
242

Bob Halley's avatar
Bob Halley committed
243
	XTRACE("isc_task_detach");
Bob Halley's avatar
base  
Bob Halley committed
244 245 246 247 248 249 250 251

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

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

	if (free_task)
		task_free(task);

	*taskp = NULL;
}

Bob Halley's avatar
Bob Halley committed
262 263 264 265 266 267 268 269
isc_mem_t *
isc_task_mem(isc_task_t *task) {

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

270
isc_result_t
Bob Halley's avatar
Bob Halley committed
271
isc_task_send(isc_task_t *task, isc_event_t **eventp) {
Bob Halley's avatar
Bob Halley committed
272
	isc_boolean_t was_idle = ISC_FALSE;
273 274
	isc_boolean_t disallowed = ISC_FALSE;
	isc_result_t result = ISC_R_SUCCESS;
Bob Halley's avatar
Bob Halley committed
275
	isc_event_t *event;
Bob Halley's avatar
base  
Bob Halley committed
276 277

	REQUIRE(VALID_TASK(task));
Bob Halley's avatar
update  
Bob Halley committed
278 279
	REQUIRE(eventp != NULL);
	event = *eventp;
Bob Halley's avatar
base  
Bob Halley committed
280
	REQUIRE(event != NULL);
Bob Halley's avatar
Bob Halley committed
281 282
	REQUIRE(event->sender != NULL);
	REQUIRE(event->type > 0);
Bob Halley's avatar
base  
Bob Halley committed
283

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

315 316
	if (disallowed)
		return (result);
Bob Halley's avatar
base  
Bob Halley committed
317 318

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

		/*
		 * 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);
344
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
345 346 347
		UNLOCK(&manager->lock);
	}

Bob Halley's avatar
update  
Bob Halley committed
348 349
	*eventp = NULL;

Bob Halley's avatar
Bob Halley committed
350
	XTRACE("sent");
351 352

	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
353 354
}

355
unsigned int
Bob Halley's avatar
Bob Halley committed
356 357
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
358
	isc_eventlist_t purgeable;
359
	unsigned int purge_count;
Bob Halley's avatar
Bob Halley committed
360 361 362 363 364 365 366 367

	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.
368 369
	 *
	 * Purging never changes the state of the task.
Bob Halley's avatar
Bob Halley committed
370 371 372
	 */

	INIT_LIST(purgeable);
373
	purge_count = 0;
Bob Halley's avatar
Bob Halley committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391

	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
392
		isc_event_free(&event);
393
		purge_count++;
Bob Halley's avatar
Bob Halley committed
394
	}
395 396

	return (purge_count);
Bob Halley's avatar
Bob Halley committed
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
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
425
	event = event_allocate(task->mctx,
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
			       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
446
		isc_mem_put(task->mctx, event, sizeof *event);
447 448 449 450

	return (result);
}

Bob Halley's avatar
Bob Halley committed
451
void
Bob Halley's avatar
Bob Halley committed
452
isc_task_shutdown(isc_task_t *task) {
Bob Halley's avatar
Bob Halley committed
453
	isc_boolean_t was_idle = ISC_FALSE;
454 455
	isc_boolean_t queued_something = ISC_FALSE;
	isc_event_t *event, *prev;
Bob Halley's avatar
base  
Bob Halley committed
456 457 458 459

	REQUIRE(VALID_TASK(task));

	/*
460
	 * This routine is very similar to isc_task_send() above.
Bob Halley's avatar
base  
Bob Halley committed
461 462 463
	 */

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

488
	if (was_idle && queued_something) {
Bob Halley's avatar
Bob Halley committed
489
		isc_taskmgr_t *manager;
Bob Halley's avatar
base  
Bob Halley committed
490 491 492 493 494

		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
495
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
496 497
		UNLOCK(&manager->lock);
	}
Bob Halley's avatar
Bob Halley committed
498
}
Bob Halley's avatar
base  
Bob Halley committed
499

Bob Halley's avatar
Bob Halley committed
500
void
Bob Halley's avatar
Bob Halley committed
501
isc_task_destroy(isc_task_t **taskp) {
Bob Halley's avatar
Bob Halley committed
502 503 504

	REQUIRE(taskp != NULL);

Bob Halley's avatar
Bob Halley committed
505 506
	isc_task_shutdown(*taskp);
	isc_task_detach(taskp);
Bob Halley's avatar
base  
Bob Halley committed
507 508 509
}


Bob Halley's avatar
Bob Halley committed
510

Bob Halley's avatar
base  
Bob Halley committed
511 512 513 514
/***
 *** Task Manager.
 ***/

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

Bob Halley's avatar
Bob Halley committed
523
	XTRACE("start");
Bob Halley's avatar
base  
Bob Halley committed
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 577 578 579 580

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

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

				/*
				 * Execute the event action.
				 */
Bob Halley's avatar
Bob Halley committed
632
				XTRACE("execute action");
633 634
				if (event->action != NULL) {
					UNLOCK(&task->lock);
635
					(event->action)(task, event);
636 637
					LOCK(&task->lock);
				}
Bob Halley's avatar
Bob Halley committed
638
				dispatch_count++;
Bob Halley's avatar
update  
Bob Halley committed
639
				
640
				if (EMPTY(task->events)) {
Bob Halley's avatar
Bob Halley committed
641
					/*
642 643 644 645
					 * 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
646
					 */
Bob Halley's avatar
Bob Halley committed
647
					XTRACE("empty");
648 649 650 651 652 653 654 655
					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
656
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
657 658 659 660 661 662 663 664 665 666 667
				} 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
668
					XTRACE("quantum");
Bob Halley's avatar
base  
Bob Halley committed
669
					task->state = task_state_ready;
Bob Halley's avatar
Bob Halley committed
670 671
					requeue = ISC_TRUE;
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
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 702 703 704 705 706
				}
			}
			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
707
	XTRACE("exit");
Bob Halley's avatar
base  
Bob Halley committed
708

Bob Halley's avatar
update  
Bob Halley committed
709
	return ((isc_threadresult_t)0);
Bob Halley's avatar
base  
Bob Halley committed
710 711 712
}

static void
Bob Halley's avatar
Bob Halley committed
713
manager_free(isc_taskmgr_t *manager) {
Bob Halley's avatar
Bob Halley committed
714 715
	(void)isc_condition_destroy(&manager->work_available);
	(void)isc_mutex_destroy(&manager->lock);
716 717
	isc_mem_put(manager->mctx, manager->threads,
		    manager->workers * sizeof (isc_thread_t));
Bob Halley's avatar
base  
Bob Halley committed
718
	manager->magic = 0;
Bob Halley's avatar
Bob Halley committed
719
	isc_mem_put(manager->mctx, manager, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
720 721
}

Bob Halley's avatar
Bob Halley committed
722
isc_result_t
Bob Halley's avatar
Bob Halley committed
723
isc_taskmgr_create(isc_mem_t *mctx, unsigned int workers, 
Bob Halley's avatar
Bob Halley committed
724
		   unsigned int default_quantum, isc_taskmgr_t **managerp)
Bob Halley's avatar
Bob Halley committed
725 726
{
	unsigned int i, started = 0;
Bob Halley's avatar
Bob Halley committed
727
	isc_taskmgr_t *manager;
728
	isc_thread_t *threads;
Bob Halley's avatar
Bob Halley committed
729 730

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

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

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

	if (started == 0) {
		manager_free(manager);
Bob Halley's avatar
Bob Halley committed
784
		return (ISC_R_NOTHREADS);
Bob Halley's avatar
base  
Bob Halley committed
785 786 787 788
	}		

	*managerp = manager;

Bob Halley's avatar
Bob Halley committed
789
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
790 791
}

Bob Halley's avatar
update  
Bob Halley committed
792
void
Bob Halley's avatar
Bob Halley committed
793 794 795
isc_taskmgr_destroy(isc_taskmgr_t **managerp) {
	isc_taskmgr_t *manager;
	isc_task_t *task;
796
	isc_event_t *event, *prev;
797
	unsigned int i;
Bob Halley's avatar
base  
Bob Halley committed
798 799 800 801 802

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

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

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

	/*
	 * Wait for all the worker threads to exit.
	 */
872 873
	for (i = 0; i < manager->workers; i++)
		(void)isc_thread_join(manager->threads[i], NULL);
Bob Halley's avatar
base  
Bob Halley committed
874 875 876 877 878

	manager_free(manager);

	*managerp = NULL;
}