1/*-
2 * Copyright (c) 2004-2006, Maxime Henrion <mux@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29#include <assert.h>
30#include <err.h>
31#include <pthread.h>
32#include <stdlib.h>
33
34#include "misc.h"
35#include "queue.h"
36#include "threads.h"
37
38/*
39 * This API is a wrapper around the pthread(3) API, which mainly
40 * allows me to wait for multiple threads to exit.  We use a
41 * condition variable to signal a thread's death.  All threads
42 * created with this API have a common entry/exit point, so we
43 * don't need to add any code in the threads themselves.
44 */
45
46/* Structure describing a thread. */
47struct thread {
48	pthread_t thread;
49	void *(*start)(void *);
50	void *data;
51	struct threads *threads;
52	LIST_ENTRY(thread) runlist;
53	STAILQ_ENTRY(thread) deadlist;
54};
55
56/* A set of threads. */
57struct threads {
58	pthread_mutex_t threads_mtx;
59	pthread_cond_t thread_exited;
60	LIST_HEAD(, thread) threads_running;
61	STAILQ_HEAD(, thread) threads_dead;
62};
63
64static void	*thread_start(void *);	/* Common entry point for threads. */
65
66static void	 threads_lock(struct threads *);
67static void	 threads_unlock(struct threads *);
68
69static void
70threads_lock(struct threads *tds)
71{
72	int error;
73
74	error = pthread_mutex_lock(&tds->threads_mtx);
75	assert(!error);
76}
77
78static void
79threads_unlock(struct threads *tds)
80{
81	int error;
82
83	error = pthread_mutex_unlock(&tds->threads_mtx);
84	assert(!error);
85}
86
87/* Create a new set of threads. */
88struct threads *
89threads_new(void)
90{
91	struct threads *tds;
92
93	tds = xmalloc(sizeof(struct threads));
94	pthread_mutex_init(&tds->threads_mtx, NULL);
95	pthread_cond_init(&tds->thread_exited, NULL);
96	LIST_INIT(&tds->threads_running);
97	STAILQ_INIT(&tds->threads_dead);
98	return (tds);
99}
100
101/* Create a new thread in this set. */
102void
103threads_create(struct threads *tds, void *(*start)(void *), void *data)
104{
105	pthread_attr_t attr;
106	struct thread *td;
107	int error;
108
109	td = xmalloc(sizeof(struct thread));
110	td->threads = tds;
111	td->start = start;
112	td->data = data;
113	/* We don't use pthread_join() to wait for the threads to finish. */
114	pthread_attr_init(&attr);
115	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
116	threads_lock(tds);
117	error = pthread_create(&td->thread, &attr, thread_start, td);
118	if (error)
119		err(1, "pthread_create");
120	LIST_INSERT_HEAD(&tds->threads_running, td, runlist);
121	threads_unlock(tds);
122}
123
124/* Wait for a thread in the set to exit, and return its data pointer. */
125void *
126threads_wait(struct threads *tds)
127{
128	struct thread *td;
129	void *data;
130
131	threads_lock(tds);
132	while (STAILQ_EMPTY(&tds->threads_dead)) {
133		assert(!LIST_EMPTY(&tds->threads_running));
134		pthread_cond_wait(&tds->thread_exited, &tds->threads_mtx);
135	}
136	td = STAILQ_FIRST(&tds->threads_dead);
137	STAILQ_REMOVE_HEAD(&tds->threads_dead, deadlist);
138	threads_unlock(tds);
139	data = td->data;
140	free(td);
141	return (data);
142}
143
144/* Free a threads set. */
145void
146threads_free(struct threads *tds)
147{
148
149	assert(LIST_EMPTY(&tds->threads_running));
150	assert(STAILQ_EMPTY(&tds->threads_dead));
151	pthread_cond_destroy(&tds->thread_exited);
152	pthread_mutex_destroy(&tds->threads_mtx);
153	free(tds);
154}
155
156/*
157 * Common entry point for threads.  This just calls the real start
158 * routine, and then signals the thread's death, after having
159 * removed the thread from the list.
160 */
161static void *
162thread_start(void *data)
163{
164	struct threads *tds;
165	struct thread *td;
166
167	td = data;
168	tds = td->threads;
169	td->start(td->data);
170	threads_lock(tds);
171	LIST_REMOVE(td, runlist);
172	STAILQ_INSERT_TAIL(&tds->threads_dead, td, deadlist);
173	pthread_cond_signal(&tds->thread_exited);
174	threads_unlock(tds);
175	return (NULL);
176}
177