task.c 20.9 KB
Newer Older
Bob Halley's avatar
Bob Halley committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright (C) 1998  Internet Software Consortium.
 * 
 * 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 21
#include <isc/assertions.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

Bob Halley's avatar
Bob Halley committed
28 29 30 31

/***
 *** General Macros.
 ***/
Bob Halley's avatar
base  
Bob Halley committed
32

Bob Halley's avatar
update  
Bob Halley committed
33
/*
Bob Halley's avatar
Bob Halley committed
34
 * We use macros instead of calling the routines directly because
Bob Halley's avatar
update  
Bob Halley committed
35 36 37 38
 * the capital letters make the locking stand out.
 *
 * We INSIST that they succeed since there's no way for us to continue
 * if they fail.
Bob Halley's avatar
update  
Bob Halley committed
39
 */
Bob Halley's avatar
Bob Halley committed
40 41 42 43 44 45 46

#define LOCK(lp) \
	INSIST(isc_mutex_lock((lp)) == ISC_R_SUCCESS);
#define UNLOCK(lp) \
	INSIST(isc_mutex_unlock((lp)) == ISC_R_SUCCESS);
#define BROADCAST(cvp) \
	INSIST(isc_condition_broadcast((cvp)) == ISC_R_SUCCESS);
47 48
#define SIGNAL(cvp) \
	INSIST(isc_condition_signal((cvp)) == ISC_R_SUCCESS);
Bob Halley's avatar
Bob Halley committed
49 50 51 52 53
#define WAIT(cvp, lp) \
	INSIST(isc_condition_wait((cvp), (lp)) == ISC_R_SUCCESS);
#define WAITUNTIL(cvp, lp, tp, bp) \
	INSIST(isc_condition_waituntil((cvp), (lp), (tp), (bp)) == \
	ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
54

Bob Halley's avatar
Bob Halley committed
55
#ifdef ISC_TASK_TRACE
Bob Halley's avatar
update  
Bob Halley committed
56
#define XTRACE(m)		printf("%s task %p thread %lu\n", (m), \
Bob Halley's avatar
Bob Halley committed
57
				       task, isc_thread_self())
Bob Halley's avatar
Bob Halley committed
58 59 60
#else
#define XTRACE(m)
#endif
Bob Halley's avatar
base  
Bob Halley committed
61

Bob Halley's avatar
Bob Halley committed
62 63

/***
Bob Halley's avatar
Bob Halley committed
64
 *** Types.
Bob Halley's avatar
Bob Halley committed
65 66
 ***/

Bob Halley's avatar
Bob Halley committed
67 68 69 70 71 72 73 74 75
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
76
struct isc_task {
Bob Halley's avatar
Bob Halley committed
77 78
	/* Not locked. */
	unsigned int			magic;
Bob Halley's avatar
Bob Halley committed
79
	isc_taskmgr_t			manager;
Bob Halley's avatar
Bob Halley committed
80
	isc_mutex_t			lock;
Bob Halley's avatar
Bob Halley committed
81 82 83
	/* Locked by task lock. */
	task_state_t			state;
	unsigned int			references;
Bob Halley's avatar
Bob Halley committed
84
	isc_eventlist_t			events;
Bob Halley's avatar
Bob Halley committed
85
	unsigned int			quantum;
Bob Halley's avatar
Bob Halley committed
86
	isc_boolean_t			enqueue_allowed;
Bob Halley's avatar
Bob Halley committed
87
	isc_event_t			shutdown_event;
Bob Halley's avatar
Bob Halley committed
88
	/* Locked by task manager lock. */
Bob Halley's avatar
Bob Halley committed
89 90
	LINK(struct isc_task)		link;
	LINK(struct isc_task)		ready_link;
Bob Halley's avatar
Bob Halley committed
91 92 93 94 95 96
};

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

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

Bob Halley's avatar
Bob Halley committed
112 113
#define DEFAULT_DEFAULT_QUANTUM		5
#define FINISHED(m)			((m)->exiting && EMPTY((m)->tasks))
Bob Halley's avatar
Bob Halley committed
114

Bob Halley's avatar
update  
Bob Halley committed
115 116 117 118 119

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

Bob Halley's avatar
Bob Halley committed
120
static inline isc_event_t
Bob Halley's avatar
Bob Halley committed
121
event_allocate(isc_memctx_t mctx, void *sender, isc_eventtype_t type,
Bob Halley's avatar
Bob Halley committed
122
	       isc_taskaction_t action, void *arg, size_t size)
Bob Halley's avatar
update  
Bob Halley committed
123
{
Bob Halley's avatar
Bob Halley committed
124
	isc_event_t event;
Bob Halley's avatar
update  
Bob Halley committed
125

Bob Halley's avatar
Bob Halley committed
126
	event = isc_mem_get(mctx, size);
Bob Halley's avatar
update  
Bob Halley committed
127 128 129 130
	if (event == NULL)
		return (NULL);
	event->mctx = mctx;
	event->size = size;
Bob Halley's avatar
Bob Halley committed
131
	event->sender = sender;
Bob Halley's avatar
update  
Bob Halley committed
132 133 134 135 136 137 138
	event->type = type;
	event->action = action;
	event->arg = arg;

	return (event);
}

Bob Halley's avatar
Bob Halley committed
139
isc_event_t
Bob Halley's avatar
Bob Halley committed
140 141
isc_event_allocate(isc_memctx_t mctx, void *sender, isc_eventtype_t type,
		   isc_taskaction_t action, void *arg, size_t size)
Bob Halley's avatar
update  
Bob Halley committed
142
{
Bob Halley's avatar
Bob Halley committed
143
	if (size < sizeof (struct isc_event))
Bob Halley's avatar
update  
Bob Halley committed
144 145 146 147 148 149
		return (NULL);
	if (type < 0)
		return (NULL);
	if (action == NULL)
		return (NULL);

Bob Halley's avatar
Bob Halley committed
150
	return (event_allocate(mctx, sender, type, action, arg, size));
Bob Halley's avatar
update  
Bob Halley committed
151 152 153
}

void
Bob Halley's avatar
Bob Halley committed
154 155
isc_event_free(isc_event_t *eventp) {
	isc_event_t event;
Bob Halley's avatar
update  
Bob Halley committed
156 157 158 159 160
	
	REQUIRE(eventp != NULL);
	event = *eventp;
	REQUIRE(event != NULL);

Bob Halley's avatar
Bob Halley committed
161 162
	if (event->destroy != NULL)
		(event->destroy)(event);
Bob Halley's avatar
Bob Halley committed
163
	isc_mem_put(event->mctx, event, event->size);
Bob Halley's avatar
update  
Bob Halley committed
164 165 166 167

	*eventp = NULL;
}

Bob Halley's avatar
base  
Bob Halley committed
168 169 170 171 172
/***
 *** Tasks.
 ***/

static void
Bob Halley's avatar
Bob Halley committed
173 174
task_free(isc_task_t task) {
	isc_taskmgr_t manager = task->manager;
Bob Halley's avatar
base  
Bob Halley committed
175

Bob Halley's avatar
Bob Halley committed
176
	XTRACE("free task");
Bob Halley's avatar
base  
Bob Halley committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190
	REQUIRE(EMPTY(task->events));

	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
191
	(void)isc_mutex_destroy(&task->lock);
Bob Halley's avatar
update  
Bob Halley committed
192
	if (task->shutdown_event != NULL)
Bob Halley's avatar
Bob Halley committed
193
		isc_event_free(&task->shutdown_event);
Bob Halley's avatar
base  
Bob Halley committed
194
	task->magic = 0;
Bob Halley's avatar
Bob Halley committed
195
	isc_mem_put(manager->mctx, task, sizeof *task);
Bob Halley's avatar
base  
Bob Halley committed
196 197
}

Bob Halley's avatar
Bob Halley committed
198
isc_result_t
Bob Halley's avatar
Bob Halley committed
199 200
isc_task_create(isc_taskmgr_t manager, isc_taskaction_t shutdown_action,
		void *shutdown_arg, unsigned int quantum, isc_task_t *taskp)
Bob Halley's avatar
Bob Halley committed
201
{
Bob Halley's avatar
Bob Halley committed
202
	isc_task_t task;
Bob Halley's avatar
base  
Bob Halley committed
203 204 205 206

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

Bob Halley's avatar
Bob Halley committed
207
	task = isc_mem_get(manager->mctx, sizeof *task);
Bob Halley's avatar
base  
Bob Halley committed
208
	if (task == NULL)
Bob Halley's avatar
Bob Halley committed
209
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
210 211 212

	task->magic = TASK_MAGIC;
	task->manager = manager;
Bob Halley's avatar
Bob Halley committed
213
	if (isc_mutex_init(&task->lock) != ISC_R_SUCCESS) {
Bob Halley's avatar
Bob Halley committed
214
		isc_mem_put(manager->mctx, task, sizeof *task);
Bob Halley's avatar
Bob Halley committed
215 216 217
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
218
	}
Bob Halley's avatar
base  
Bob Halley committed
219 220 221 222
	task->state = task_state_idle;
	task->references = 1;
	INIT_LIST(task->events);
	task->quantum = quantum;
Bob Halley's avatar
Bob Halley committed
223
	task->enqueue_allowed = ISC_TRUE;
Bob Halley's avatar
update  
Bob Halley committed
224
	task->shutdown_event = event_allocate(manager->mctx,
Bob Halley's avatar
Bob Halley committed
225
					      NULL,
Bob Halley's avatar
Bob Halley committed
226
					      ISC_TASKEVENT_SHUTDOWN,
Bob Halley's avatar
update  
Bob Halley committed
227 228 229 230
					      shutdown_action,
					      shutdown_arg,
					      sizeof *task->shutdown_event);
	if (task->shutdown_event == NULL) {
Bob Halley's avatar
Bob Halley committed
231
		(void)isc_mutex_destroy(&task->lock);
Bob Halley's avatar
Bob Halley committed
232
		isc_mem_put(manager->mctx, task, sizeof *task);
Bob Halley's avatar
Bob Halley committed
233
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
update  
Bob Halley committed
234
	}
Bob Halley's avatar
base  
Bob Halley committed
235 236 237 238 239 240 241 242 243 244 245
	INIT_LINK(task, link);
	INIT_LINK(task, ready_link);

	LOCK(&manager->lock);
	if (task->quantum == 0)
		task->quantum = manager->default_quantum;
	APPEND(manager->tasks, task, link);
	UNLOCK(&manager->lock);

	*taskp = task;

Bob Halley's avatar
Bob Halley committed
246
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
247 248
}

Bob Halley's avatar
update  
Bob Halley committed
249
void
Bob Halley's avatar
Bob Halley committed
250
isc_task_attach(isc_task_t task, isc_task_t *taskp) {
Bob Halley's avatar
base  
Bob Halley committed
251 252 253 254 255 256 257 258 259 260 261

	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
262
void
Bob Halley's avatar
Bob Halley committed
263
isc_task_detach(isc_task_t *taskp) {
Bob Halley's avatar
Bob Halley committed
264
	isc_boolean_t free_task = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
265
	isc_task_t task;
Bob Halley's avatar
base  
Bob Halley committed
266

Bob Halley's avatar
Bob Halley committed
267
	XTRACE("isc_task_detach");
Bob Halley's avatar
base  
Bob Halley committed
268 269 270 271 272 273 274 275

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

	LOCK(&task->lock);
	REQUIRE(task->references > 0);
	task->references--;
276
	if (task->state == task_state_shutdown && task->references == 0)
Bob Halley's avatar
Bob Halley committed
277
		free_task = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
278 279 280 281 282 283 284 285
	UNLOCK(&task->lock);

	if (free_task)
		task_free(task);

	*taskp = NULL;
}

286
isc_result_t
Bob Halley's avatar
Bob Halley committed
287
isc_task_send(isc_task_t task, isc_event_t *eventp) {
Bob Halley's avatar
Bob Halley committed
288 289
	isc_boolean_t was_idle = ISC_FALSE;
	isc_boolean_t discard = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
290
	isc_event_t event;
Bob Halley's avatar
base  
Bob Halley committed
291 292

	REQUIRE(VALID_TASK(task));
Bob Halley's avatar
update  
Bob Halley committed
293 294
	REQUIRE(eventp != NULL);
	event = *eventp;
Bob Halley's avatar
base  
Bob Halley committed
295
	REQUIRE(event != NULL);
Bob Halley's avatar
Bob Halley committed
296 297
	REQUIRE(event->sender != NULL);
	REQUIRE(event->type > 0);
Bob Halley's avatar
base  
Bob Halley committed
298

Bob Halley's avatar
Bob Halley committed
299
	XTRACE("sending");
Bob Halley's avatar
base  
Bob Halley committed
300 301 302 303 304 305
	/*
	 * 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);
Bob Halley's avatar
update  
Bob Halley committed
306
	if (task->enqueue_allowed) {
Bob Halley's avatar
base  
Bob Halley committed
307
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
308
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
309 310 311 312 313 314 315
			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);
	} else
Bob Halley's avatar
Bob Halley committed
316
		discard = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
317 318 319
	UNLOCK(&task->lock);

	if (discard) {
Bob Halley's avatar
Bob Halley committed
320
		isc_event_free(&event);
Bob Halley's avatar
update  
Bob Halley committed
321
		*eventp = NULL;
322
		return (ISC_R_TASKSHUTDOWN);
Bob Halley's avatar
base  
Bob Halley committed
323 324 325
	}

	if (was_idle) {
Bob Halley's avatar
Bob Halley committed
326
		isc_taskmgr_t manager;
Bob Halley's avatar
base  
Bob Halley committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350

		/*
		 * 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);
351
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
352 353 354
		UNLOCK(&manager->lock);
	}

Bob Halley's avatar
update  
Bob Halley committed
355 356
	*eventp = NULL;

Bob Halley's avatar
Bob Halley committed
357
	XTRACE("sent");
358 359

	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
360 361
}

362
unsigned int
Bob Halley's avatar
Bob Halley committed
363 364 365
isc_task_purge(isc_task_t task, void *sender, isc_eventtype_t type) {
	isc_event_t event, next_event;
	isc_eventlist_t purgeable;
366
	unsigned int purge_count;
Bob Halley's avatar
Bob Halley committed
367 368 369 370 371 372 373 374 375 376 377

	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.
	 */

	INIT_LIST(purgeable);
378
	purge_count = 0;
Bob Halley's avatar
Bob Halley committed
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396

	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
397
		isc_event_free(&event);
398
		purge_count++;
Bob Halley's avatar
Bob Halley committed
399
	}
400 401

	return (purge_count);
Bob Halley's avatar
Bob Halley committed
402 403
}

Bob Halley's avatar
Bob Halley committed
404
void
Bob Halley's avatar
Bob Halley committed
405
isc_task_shutdown(isc_task_t task) {
Bob Halley's avatar
Bob Halley committed
406 407
	isc_boolean_t was_idle = ISC_FALSE;
	isc_boolean_t discard = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
408 409 410 411

	REQUIRE(VALID_TASK(task));

	/*
412
	 * This routine is very similar to isc_task_send() above.
Bob Halley's avatar
base  
Bob Halley committed
413 414 415
	 */

	LOCK(&task->lock);
Bob Halley's avatar
update  
Bob Halley committed
416
	if (task->enqueue_allowed) {
Bob Halley's avatar
base  
Bob Halley committed
417
		if (task->state == task_state_idle) {
Bob Halley's avatar
Bob Halley committed
418
			was_idle = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
419 420 421 422 423
			INSIST(EMPTY(task->events));
			task->state = task_state_ready;
		}
		INSIST(task->state == task_state_ready ||
		       task->state == task_state_running);
Bob Halley's avatar
update  
Bob Halley committed
424 425 426
		INSIST(task->shutdown_event != NULL);
		ENQUEUE(task->events, task->shutdown_event, link);
		task->shutdown_event = NULL;
Bob Halley's avatar
Bob Halley committed
427
		task->enqueue_allowed = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
428
	} else
Bob Halley's avatar
Bob Halley committed
429
		discard = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
430 431
	UNLOCK(&task->lock);

Bob Halley's avatar
Bob Halley committed
432
	if (discard)
Bob Halley's avatar
Bob Halley committed
433
		return;
Bob Halley's avatar
base  
Bob Halley committed
434 435

	if (was_idle) {
Bob Halley's avatar
Bob Halley committed
436
		isc_taskmgr_t manager;
Bob Halley's avatar
base  
Bob Halley committed
437 438 439 440 441

		manager = task->manager;
		INSIST(VALID_MANAGER(manager));
		LOCK(&manager->lock);
		ENQUEUE(manager->ready_tasks, task, ready_link);
442
		SIGNAL(&manager->work_available);
Bob Halley's avatar
base  
Bob Halley committed
443 444
		UNLOCK(&manager->lock);
	}
Bob Halley's avatar
Bob Halley committed
445
}
Bob Halley's avatar
base  
Bob Halley committed
446

Bob Halley's avatar
Bob Halley committed
447
void
Bob Halley's avatar
Bob Halley committed
448
isc_task_destroy(isc_task_t *taskp) {
Bob Halley's avatar
Bob Halley committed
449 450 451

	REQUIRE(taskp != NULL);

Bob Halley's avatar
Bob Halley committed
452 453
	isc_task_shutdown(*taskp);
	isc_task_detach(taskp);
Bob Halley's avatar
base  
Bob Halley committed
454 455 456
}


Bob Halley's avatar
Bob Halley committed
457

Bob Halley's avatar
base  
Bob Halley committed
458 459 460 461
/***
 *** Task Manager.
 ***/

Bob Halley's avatar
update  
Bob Halley committed
462
static isc_threadresult_t
Bob Halley's avatar
Bob Halley committed
463 464 465
#ifdef _WIN32
WINAPI
#endif
Bob Halley's avatar
update  
Bob Halley committed
466
run(void *uap) {
Bob Halley's avatar
Bob Halley committed
467 468
	isc_taskmgr_t manager = uap;
	isc_task_t task;
Bob Halley's avatar
base  
Bob Halley committed
469

Bob Halley's avatar
Bob Halley committed
470
	XTRACE("start");
Bob Halley's avatar
base  
Bob Halley committed
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527

	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
528
		 * isc_task_send() above, it is safe for us to dequeue
Bob Halley's avatar
base  
Bob Halley committed
529 530 531 532 533
		 * 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
534
			XTRACE("wait");
Bob Halley's avatar
base  
Bob Halley committed
535
			WAIT(&manager->work_available, &manager->lock);
Bob Halley's avatar
Bob Halley committed
536
			XTRACE("awake");
Bob Halley's avatar
base  
Bob Halley committed
537
		}
Bob Halley's avatar
Bob Halley committed
538
		XTRACE("working");
Bob Halley's avatar
base  
Bob Halley committed
539 540 541
		
		task = HEAD(manager->ready_tasks);
		if (task != NULL) {
Bob Halley's avatar
Bob Halley committed
542
			unsigned int dispatch_count = 0;
Bob Halley's avatar
Bob Halley committed
543 544 545 546 547
			isc_boolean_t done = ISC_FALSE;
			isc_boolean_t requeue = ISC_FALSE;
			isc_boolean_t wants_shutdown;
			isc_boolean_t is_shutdown;
			isc_boolean_t free_task = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
548 549
			isc_event_t event;
			isc_eventlist_t remaining_events;
Bob Halley's avatar
Bob Halley committed
550
			isc_boolean_t discard_remaining = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
551 552 553 554 555 556 557 558 559 560 561 562

			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
563 564 565 566 567 568 569 570
			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
571
				done = ISC_TRUE;
Bob Halley's avatar
Bob Halley committed
572 573 574
				XTRACE("ready but empty");
			} else
				task->state = task_state_running;
Bob Halley's avatar
base  
Bob Halley committed
575
			while (!done) {
Bob Halley's avatar
update  
Bob Halley committed
576 577 578
				INSIST(!EMPTY(task->events));
				event = HEAD(task->events);
				DEQUEUE(task->events, event, link);
Bob Halley's avatar
base  
Bob Halley committed
579 580
				UNLOCK(&task->lock);

Bob Halley's avatar
Bob Halley committed
581
				if (event->type == ISC_TASKEVENT_SHUTDOWN)
Bob Halley's avatar
Bob Halley committed
582
					is_shutdown = ISC_TRUE;
Bob Halley's avatar
update  
Bob Halley committed
583
				else
Bob Halley's avatar
Bob Halley committed
584
					is_shutdown = ISC_FALSE;
Bob Halley's avatar
update  
Bob Halley committed
585

Bob Halley's avatar
base  
Bob Halley committed
586 587 588
				/*
				 * Execute the event action.
				 */
Bob Halley's avatar
Bob Halley committed
589
				XTRACE("execute action");
Bob Halley's avatar
update  
Bob Halley committed
590 591 592
				if (event->action != NULL)
					wants_shutdown =
						(event->action)(task, event);
Bob Halley's avatar
base  
Bob Halley committed
593
				else
Bob Halley's avatar
Bob Halley committed
594
					wants_shutdown = ISC_FALSE;
Bob Halley's avatar
Bob Halley committed
595
				dispatch_count++;
Bob Halley's avatar
update  
Bob Halley committed
596
				
Bob Halley's avatar
base  
Bob Halley committed
597
				LOCK(&task->lock);
Bob Halley's avatar
update  
Bob Halley committed
598
				if (wants_shutdown || is_shutdown) {
Bob Halley's avatar
Bob Halley committed
599
					/*
Bob Halley's avatar
update  
Bob Halley committed
600 601 602 603
					 * The event action has either
					 * requested shutdown, or the event
					 * we just executed was the shutdown
					 * event.
Bob Halley's avatar
Bob Halley committed
604 605 606 607 608 609 610 611
					 *
					 * Since no more events can be
					 * delivered to the task, we purge
					 * any remaining events (but defer
					 * freeing them until we've released
					 * the lock).
					 */
					XTRACE("wants shutdown");
Bob Halley's avatar
base  
Bob Halley committed
612 613 614 615
					if (!EMPTY(task->events)) {
						remaining_events =
							task->events;
						INIT_LIST(task->events);
Bob Halley's avatar
Bob Halley committed
616
						discard_remaining = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
617 618
					}
					if (task->references == 0)
Bob Halley's avatar
Bob Halley committed
619
						free_task = ISC_TRUE;
Bob Halley's avatar
Bob Halley committed
620
					task->state = task_state_shutdown;
Bob Halley's avatar
Bob Halley committed
621 622
					task->enqueue_allowed = ISC_FALSE;
					done = ISC_TRUE;
Bob Halley's avatar
update  
Bob Halley committed
623
				} else if (EMPTY(task->events)) {
Bob Halley's avatar
Bob Halley committed
624 625 626
					/*
					 * Nothing else to do for this task.
					 * Put it to sleep.
Bob Halley's avatar
Bob Halley committed
627 628 629
					 *
					 * XXX detect tasks with 0 references
					 * and do something about them.
Bob Halley's avatar
Bob Halley committed
630
					 */
Bob Halley's avatar
Bob Halley committed
631
					XTRACE("empty");
Bob Halley's avatar
base  
Bob Halley committed
632
					task->state = task_state_idle;
Bob Halley's avatar
Bob Halley committed
633
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
634 635 636 637 638 639 640 641 642 643 644
				} 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
645
					XTRACE("quantum");
Bob Halley's avatar
base  
Bob Halley committed
646
					task->state = task_state_ready;
Bob Halley's avatar
Bob Halley committed
647 648
					requeue = ISC_TRUE;
					done = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
649 650 651 652 653
				}
			}
			UNLOCK(&task->lock);

			if (discard_remaining) {
Bob Halley's avatar
Bob Halley committed
654
				isc_event_t next_event;
Bob Halley's avatar
base  
Bob Halley committed
655 656 657 658 659

				for (event = HEAD(remaining_events);
				     event != NULL;
				     event = next_event) {
					next_event = NEXT(event, link);
Bob Halley's avatar
Bob Halley committed
660
					isc_event_free(&event);
Bob Halley's avatar
base  
Bob Halley committed
661 662 663 664 665 666 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
				}
			}

			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
695
	XTRACE("exit");
Bob Halley's avatar
base  
Bob Halley committed
696

Bob Halley's avatar
update  
Bob Halley committed
697
	return ((isc_threadresult_t)0);
Bob Halley's avatar
base  
Bob Halley committed
698 699 700
}

static void
Bob Halley's avatar
Bob Halley committed
701
manager_free(isc_taskmgr_t manager) {
Bob Halley's avatar
Bob Halley committed
702 703
	(void)isc_condition_destroy(&manager->work_available);
	(void)isc_mutex_destroy(&manager->lock);
704 705
	isc_mem_put(manager->mctx, manager->threads,
		    manager->workers * sizeof (isc_thread_t));
Bob Halley's avatar
base  
Bob Halley committed
706
	manager->magic = 0;
Bob Halley's avatar
Bob Halley committed
707
	isc_mem_put(manager->mctx, manager, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
708 709
}

Bob Halley's avatar
Bob Halley committed
710
isc_result_t
Bob Halley's avatar
Bob Halley committed
711 712
isc_taskmgr_create(isc_memctx_t mctx, unsigned int workers, 
		   unsigned int default_quantum, isc_taskmgr_t *managerp)
Bob Halley's avatar
Bob Halley committed
713 714
{
	unsigned int i, started = 0;
Bob Halley's avatar
Bob Halley committed
715
	isc_taskmgr_t manager;
716
	isc_thread_t *threads;
Bob Halley's avatar
Bob Halley committed
717 718

	REQUIRE(workers > 0);
Bob Halley's avatar
base  
Bob Halley committed
719

Bob Halley's avatar
Bob Halley committed
720
	manager = isc_mem_get(mctx, sizeof *manager);
Bob Halley's avatar
base  
Bob Halley committed
721
	if (manager == NULL)
Bob Halley's avatar
Bob Halley committed
722
		return (ISC_R_NOMEMORY);
Bob Halley's avatar
base  
Bob Halley committed
723 724
	manager->magic = TASK_MANAGER_MAGIC;
	manager->mctx = mctx;
725 726 727 728 729 730 731
	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
732
	if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
733
		isc_mem_put(mctx, threads, workers * sizeof (isc_thread_t));
Bob Halley's avatar
Bob Halley committed
734
		isc_mem_put(mctx, manager, sizeof *manager);
Bob Halley's avatar
Bob Halley committed
735 736 737
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "isc_mutex_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
738
	}
Bob Halley's avatar
base  
Bob Halley committed
739 740 741 742 743
	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
744 745
	if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
		(void)isc_mutex_destroy(&manager->lock);
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_condition_init() failed");
		return (ISC_R_UNEXPECTED);
Bob Halley's avatar
update  
Bob Halley committed
751
	}
Bob Halley's avatar
Bob Halley committed
752
	manager->exiting = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
753 754 755 756 757 758 759
	manager->workers = 0;

	LOCK(&manager->lock);
	/*
	 * Start workers.
	 */
	for (i = 0; i < workers; i++) {
760 761
		if (isc_thread_create(run, manager,
				      &manager->threads[manager->workers]) == 
Bob Halley's avatar
Bob Halley committed
762
		    ISC_R_SUCCESS) {
Bob Halley's avatar
base  
Bob Halley committed
763 764 765 766 767 768 769 770
			manager->workers++;
			started++;
		}
	}
	UNLOCK(&manager->lock);

	if (started == 0) {
		manager_free(manager);
Bob Halley's avatar
Bob Halley committed
771
		return (ISC_R_NOTHREADS);
Bob Halley's avatar
base  
Bob Halley committed
772 773 774 775
	}		

	*managerp = manager;

Bob Halley's avatar
Bob Halley committed
776
	return (ISC_R_SUCCESS);
Bob Halley's avatar
base  
Bob Halley committed
777 778
}

Bob Halley's avatar
update  
Bob Halley committed
779
void
Bob Halley's avatar
Bob Halley committed
780 781 782
isc_taskmgr_destroy(isc_taskmgr_t *managerp) {
	isc_taskmgr_t manager;
	isc_task_t task;
783
	unsigned int i;
Bob Halley's avatar
base  
Bob Halley committed
784 785 786 787 788

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

Bob Halley's avatar
Bob Halley committed
789
	XTRACE("isc_taskmgr_destroy");
Bob Halley's avatar
base  
Bob Halley committed
790 791 792 793
	/*
	 * 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
794
	 * isc_taskmgr_destroy(), e.g. by signalling a condition variable
Bob Halley's avatar
base  
Bob Halley committed
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
	 * 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
813
	manager->exiting = ISC_TRUE;
Bob Halley's avatar
base  
Bob Halley committed
814 815

	/*
Bob Halley's avatar
update  
Bob Halley committed
816 817
	 * Post the shutdown event to every task (if it hasn't already been
	 * posted).
Bob Halley's avatar
base  
Bob Halley committed
818 819 820 821 822
	 */
	for (task = HEAD(manager->tasks);
	     task != NULL;
	     task = NEXT(task, link)) {
		LOCK(&task->lock);
Bob Halley's avatar
update  
Bob Halley committed
823 824 825 826 827 828 829 830 831 832 833
		if (task->enqueue_allowed) {
			INSIST(task->shutdown_event != NULL);
			ENQUEUE(task->events, task->shutdown_event, link);
			task->shutdown_event = NULL;
			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
834
			task->enqueue_allowed = ISC_FALSE;
Bob Halley's avatar
base  
Bob Halley committed
835 836 837 838 839 840 841 842 843 844
		}
		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);
845
	UNLOCK(&manager->lock);
Bob Halley's avatar
base  
Bob Halley committed
846 847 848 849

	/*
	 * Wait for all the worker threads to exit.
	 */
850 851
	for (i = 0; i < manager->workers; i++)
		(void)isc_thread_join(manager->threads[i], NULL);
Bob Halley's avatar
base  
Bob Halley committed
852 853 854 855 856

	manager_free(manager);

	*managerp = NULL;
}