xref: /freebsd/lib/libthr/thread/thr_cond.c (revision 87569f75a91f298c52a71823c04d41cf53c88889)
1 /*
2  * Copyright (c) 2005 David Xu <davidxu@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 unmodified, this list of conditions, and the following
10  *    disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <pthread.h>
33 #include <limits.h>
34 
35 #include "thr_private.h"
36 
37 /*
38  * Prototypes
39  */
40 static int cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
41 static int cond_wait_common(pthread_cond_t *cond, pthread_mutex_t *mutex,
42 		    const struct timespec *abstime, int cancel);
43 static int cond_signal_common(pthread_cond_t *cond, int broadcast);
44 
45 /*
46  * Double underscore versions are cancellation points.  Single underscore
47  * versions are not and are provided for libc internal usage (which
48  * shouldn't introduce cancellation points).
49  */
50 __weak_reference(__pthread_cond_wait, pthread_cond_wait);
51 __weak_reference(__pthread_cond_timedwait, pthread_cond_timedwait);
52 
53 __weak_reference(_pthread_cond_init, pthread_cond_init);
54 __weak_reference(_pthread_cond_destroy, pthread_cond_destroy);
55 __weak_reference(_pthread_cond_signal, pthread_cond_signal);
56 __weak_reference(_pthread_cond_broadcast, pthread_cond_broadcast);
57 
58 static int
59 cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr)
60 {
61 	pthread_cond_t	pcond;
62 	int             rval = 0;
63 
64 	if ((pcond = (pthread_cond_t)
65 	    malloc(sizeof(struct pthread_cond))) == NULL) {
66 		rval = ENOMEM;
67 	} else {
68 		/*
69 		 * Initialise the condition variable structure:
70 		 */
71 		_thr_umtx_init(&pcond->c_lock);
72 		pcond->c_seqno = 0;
73 		pcond->c_waiters = 0;
74 		pcond->c_wakeups = 0;
75 		if (cond_attr == NULL || *cond_attr == NULL) {
76 			pcond->c_pshared = 0;
77 			pcond->c_clockid = CLOCK_REALTIME;
78 		} else {
79 			pcond->c_pshared = (*cond_attr)->c_pshared;
80 			pcond->c_clockid = (*cond_attr)->c_clockid;
81 		}
82 		*cond = pcond;
83 	}
84 	/* Return the completion status: */
85 	return (rval);
86 }
87 
88 static int
89 init_static(struct pthread *thread, pthread_cond_t *cond)
90 {
91 	int ret;
92 
93 	THR_LOCK_ACQUIRE(thread, &_cond_static_lock);
94 
95 	if (*cond == NULL)
96 		ret = cond_init(cond, NULL);
97 	else
98 		ret = 0;
99 
100 	THR_LOCK_RELEASE(thread, &_cond_static_lock);
101 
102 	return (ret);
103 }
104 
105 int
106 _pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr)
107 {
108 
109 	*cond = NULL;
110 	return (cond_init(cond, cond_attr));
111 }
112 
113 int
114 _pthread_cond_destroy(pthread_cond_t *cond)
115 {
116 	struct pthread_cond	*cv;
117 	struct pthread		*curthread = _get_curthread();
118 	int			rval = 0;
119 
120 	if (*cond == NULL)
121 		rval = EINVAL;
122 	else {
123 		/* Lock the condition variable structure: */
124 		THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock);
125 		if ((*cond)->c_waiters + (*cond)->c_wakeups != 0) {
126 			THR_LOCK_RELEASE(curthread, &(*cond)->c_lock);
127 			return (EBUSY);
128 		}
129 
130 		/*
131 		 * NULL the caller's pointer now that the condition
132 		 * variable has been destroyed:
133 		 */
134 		cv = *cond;
135 		*cond = NULL;
136 
137 		/* Unlock the condition variable structure: */
138 		THR_LOCK_RELEASE(curthread, &cv->c_lock);
139 
140 		/* Free the cond lock structure: */
141 
142 		/*
143 		 * Free the memory allocated for the condition
144 		 * variable structure:
145 		 */
146 		free(cv);
147 
148 	}
149 	/* Return the completion status: */
150 	return (rval);
151 }
152 
153 struct cond_cancel_info
154 {
155 	pthread_mutex_t	*mutex;
156 	pthread_cond_t	*cond;
157 	long		seqno;
158 };
159 
160 static void
161 cond_cancel_handler(void *arg)
162 {
163 	struct pthread *curthread = _get_curthread();
164 	struct cond_cancel_info *cci = (struct cond_cancel_info *)arg;
165 	pthread_cond_t cv;
166 
167 	cv = *(cci->cond);
168 	THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
169 	if (cv->c_seqno != cci->seqno && cv->c_wakeups != 0) {
170 		if (cv->c_waiters > 0) {
171 			cv->c_seqno++;
172 			_thr_umtx_wake(&cv->c_seqno, 1);
173 		} else
174 			cv->c_wakeups--;
175 	} else {
176 		cv->c_waiters--;
177 	}
178 	THR_LOCK_RELEASE(curthread, &cv->c_lock);
179 
180 	_mutex_cv_lock(cci->mutex);
181 }
182 
183 static int
184 cond_wait_common(pthread_cond_t *cond, pthread_mutex_t *mutex,
185 	const struct timespec *abstime, int cancel)
186 {
187 	struct pthread	*curthread = _get_curthread();
188 	struct timespec ts, ts2, *tsp;
189 	struct cond_cancel_info cci;
190 	pthread_cond_t  cv;
191 	long		seq, oldseq;
192 	int		oldcancel;
193 	int		ret = 0;
194 
195 	/*
196 	 * If the condition variable is statically initialized,
197 	 * perform the dynamic initialization:
198 	 */
199 	if (__predict_false(*cond == NULL &&
200 	    (ret = init_static(curthread, cond)) != 0))
201 		return (ret);
202 
203 	cv = *cond;
204 	THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
205 	ret = _mutex_cv_unlock(mutex);
206 	if (ret) {
207 		THR_LOCK_RELEASE(curthread, &cv->c_lock);
208 		return (ret);
209 	}
210 	oldseq = seq = cv->c_seqno;
211 	cci.mutex = mutex;
212 	cci.cond  = cond;
213 	cci.seqno = oldseq;
214 
215 	cv->c_waiters++;
216 	do {
217 		THR_LOCK_RELEASE(curthread, &cv->c_lock);
218 
219 		if (abstime != NULL) {
220 			clock_gettime(cv->c_clockid, &ts);
221 			TIMESPEC_SUB(&ts2, abstime, &ts);
222 			tsp = &ts2;
223 		} else
224 			tsp = NULL;
225 
226 		if (cancel) {
227 			THR_CLEANUP_PUSH(curthread, cond_cancel_handler, &cci);
228 			oldcancel = _thr_cancel_enter(curthread);
229 			ret = _thr_umtx_wait(&cv->c_seqno, seq, tsp);
230 			_thr_cancel_leave(curthread, oldcancel);
231 			THR_CLEANUP_POP(curthread, 0);
232 		} else {
233 			ret = _thr_umtx_wait(&cv->c_seqno, seq, tsp);
234 		}
235 
236 		THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
237 		seq = cv->c_seqno;
238 		if (abstime != NULL && ret == ETIMEDOUT)
239 			break;
240 
241 		/*
242 		 * loop if we have never been told to wake up
243 		 * or we lost a race.
244 		 */
245 	} while (seq == oldseq || cv->c_wakeups == 0);
246 
247 	if (seq != oldseq && cv->c_wakeups != 0) {
248 		cv->c_wakeups--;
249 		ret = 0;
250 	} else {
251 		cv->c_waiters--;
252 	}
253 	THR_LOCK_RELEASE(curthread, &cv->c_lock);
254 	_mutex_cv_lock(mutex);
255 	return (ret);
256 }
257 
258 int
259 _pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
260 {
261 
262 	return (cond_wait_common(cond, mutex, NULL, 0));
263 }
264 
265 int
266 __pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
267 {
268 
269 	return (cond_wait_common(cond, mutex, NULL, 1));
270 }
271 
272 int
273 _pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex,
274 		       const struct timespec * abstime)
275 {
276 
277 	if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
278 	    abstime->tv_nsec >= 1000000000)
279 		return (EINVAL);
280 
281 	return (cond_wait_common(cond, mutex, abstime, 0));
282 }
283 
284 int
285 __pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
286 		       const struct timespec *abstime)
287 {
288 
289 	if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
290 	    abstime->tv_nsec >= 1000000000)
291 		return (EINVAL);
292 
293 	return (cond_wait_common(cond, mutex, abstime, 1));
294 }
295 
296 static int
297 cond_signal_common(pthread_cond_t *cond, int broadcast)
298 {
299 	struct pthread	*curthread = _get_curthread();
300 	pthread_cond_t	cv;
301 	int		ret = 0, oldwaiters;
302 
303 	/*
304 	 * If the condition variable is statically initialized, perform dynamic
305 	 * initialization.
306 	 */
307 	if (__predict_false(*cond == NULL &&
308 	    (ret = init_static(curthread, cond)) != 0))
309 		return (ret);
310 
311 	cv = *cond;
312 	/* Lock the condition variable structure. */
313 	THR_LOCK_ACQUIRE(curthread, &cv->c_lock);
314 	if (cv->c_waiters) {
315 		if (!broadcast) {
316 			cv->c_wakeups++;
317 			cv->c_waiters--;
318 			cv->c_seqno++;
319 			_thr_umtx_wake(&cv->c_seqno, 1);
320 		} else {
321 			oldwaiters = cv->c_waiters;
322 			cv->c_wakeups += cv->c_waiters;
323 			cv->c_waiters = 0;
324 			cv->c_seqno++;
325 			_thr_umtx_wake(&cv->c_seqno, oldwaiters);
326 		}
327 	}
328 	THR_LOCK_RELEASE(curthread, &cv->c_lock);
329 	return (ret);
330 }
331 
332 int
333 _pthread_cond_signal(pthread_cond_t * cond)
334 {
335 
336 	return (cond_signal_common(cond, 0));
337 }
338 
339 int
340 _pthread_cond_broadcast(pthread_cond_t * cond)
341 {
342 
343 	return (cond_signal_common(cond, 1));
344 }
345