xref: /openbsd-src/lib/libc/thread/rthread_mutex.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /*	$OpenBSD: rthread_mutex.c,v 1.3 2017/08/15 07:06:29 guenther Exp $ */
2 /*
3  * Copyright (c) 2017 Martin Pieuchot <mpi@openbsd.org>
4  * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <assert.h>
20 #include <errno.h>
21 #include <pthread.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "rthread.h"
28 #include "cancel.h"
29 #include "synch.h"
30 
31 /*
32  * States defined in "Futexes Are Tricky" 5.2
33  */
34 enum {
35 	UNLOCKED = 0,
36 	LOCKED = 1,	/* locked without waiter */
37 	CONTENDED = 2,	/* threads waiting for this mutex */
38 };
39 
40 #define SPIN_COUNT	128
41 #if defined(__i386__) || defined(__amd64__)
42 #define SPIN_WAIT()	asm volatile("pause": : : "memory")
43 #else
44 #define SPIN_WAIT()	do { } while (0)
45 #endif
46 
47 static _atomic_lock_t static_init_lock = _SPINLOCK_UNLOCKED;
48 
49 int
50 pthread_mutex_init(pthread_mutex_t *mutexp, const pthread_mutexattr_t *attr)
51 {
52 	pthread_mutex_t mutex;
53 
54 	mutex = calloc(1, sizeof(*mutex));
55 	if (mutex == NULL)
56 		return (ENOMEM);
57 
58 	if (attr == NULL) {
59 		mutex->type = PTHREAD_MUTEX_DEFAULT;
60 		mutex->prioceiling = -1;
61 	} else {
62 		mutex->type = (*attr)->ma_type;
63 		mutex->prioceiling = (*attr)->ma_protocol ==
64 		    PTHREAD_PRIO_PROTECT ? (*attr)->ma_prioceiling : -1;
65 	}
66 	*mutexp = mutex;
67 
68 	return (0);
69 }
70 DEF_STRONG(pthread_mutex_init);
71 
72 int
73 pthread_mutex_destroy(pthread_mutex_t *mutexp)
74 {
75 	pthread_mutex_t mutex;
76 
77 	if (mutexp == NULL || *mutexp == NULL)
78 		return (EINVAL);
79 
80 	mutex = *mutexp;
81 	if (mutex) {
82 		if (mutex->lock != UNLOCKED) {
83 #define MSG "pthread_mutex_destroy on mutex with waiters!\n"
84 			write(2, MSG, sizeof(MSG) - 1);
85 #undef MSG
86 			return (EBUSY);
87 		}
88 		free((void *)mutex);
89 		*mutexp = NULL;
90 	}
91 
92 	return (0);
93 }
94 DEF_STRONG(pthread_mutex_destroy);
95 
96 static int
97 _rthread_mutex_trylock(pthread_mutex_t mutex, int trywait,
98     const struct timespec *abs)
99 {
100 	pthread_t self = pthread_self();
101 
102 	if (atomic_cas_uint(&mutex->lock, UNLOCKED, LOCKED) == UNLOCKED) {
103 		membar_enter_after_atomic();
104 		mutex->owner = self;
105 		return (0);
106 	}
107 
108 	if (mutex->owner == self) {
109 		int type = mutex->type;
110 
111 		/* already owner?  handle recursive behavior */
112 		if (type != PTHREAD_MUTEX_RECURSIVE) {
113 			if (trywait || type == PTHREAD_MUTEX_ERRORCHECK)
114 				return (trywait ? EBUSY : EDEADLK);
115 
116 			/* self-deadlock is disallowed by strict */
117 			if (type == PTHREAD_MUTEX_STRICT_NP && abs == NULL)
118 				abort();
119 
120 			/* self-deadlock, possibly until timeout */
121 			while (_twait(&mutex->type, type, CLOCK_REALTIME,
122 			    abs) != ETIMEDOUT)
123 				;
124 			return (ETIMEDOUT);
125 		} else {
126 			if (mutex->count == INT_MAX)
127 				return (EAGAIN);
128 			mutex->count++;
129 			return (0);
130 		}
131 	}
132 
133 	return (EBUSY);
134 }
135 
136 static int
137 _rthread_mutex_timedlock(pthread_mutex_t *mutexp, int trywait,
138     const struct timespec *abs, int timed)
139 {
140 	pthread_t self = pthread_self();
141 	pthread_mutex_t mutex;
142 	unsigned int i, lock;
143 	int error = 0;
144 
145 	if (mutexp == NULL)
146 		return (EINVAL);
147 
148 	/*
149 	 * If the mutex is statically initialized, perform the dynamic
150 	 * initialization. Note: _thread_mutex_lock() in libc requires
151 	 * pthread_mutex_lock() to perform the mutex init when *mutexp
152 	 * is NULL.
153 	 */
154 	if (*mutexp == NULL) {
155 		_spinlock(&static_init_lock);
156 		if (*mutexp == NULL)
157 			error = pthread_mutex_init(mutexp, NULL);
158 		_spinunlock(&static_init_lock);
159 		if (error != 0)
160 			return (EINVAL);
161 	}
162 
163 	mutex = *mutexp;
164 	_rthread_debug(5, "%p: mutex_%slock %p (%p)\n", self,
165 	    (timed ? "timed" : (trywait ? "try" : "")), (void *)mutex,
166 	    (void *)mutex->owner);
167 
168 	error = _rthread_mutex_trylock(mutex, trywait, abs);
169 	if (error != EBUSY || trywait)
170 		return (error);
171 
172 	/* Try hard to not enter the kernel. */
173 	for (i = 0; i < SPIN_COUNT; i ++) {
174 		if (mutex->lock == UNLOCKED)
175 			break;
176 
177 		SPIN_WAIT();
178 	}
179 
180 	lock = atomic_cas_uint(&mutex->lock, UNLOCKED, LOCKED);
181 	if (lock == UNLOCKED) {
182 		membar_enter_after_atomic();
183 		mutex->owner = self;
184 		return (0);
185 	}
186 
187 	if (lock != CONTENDED) {
188 		/* Indicate that we're waiting on this mutex. */
189 		lock = atomic_swap_uint(&mutex->lock, CONTENDED);
190 	}
191 
192 	while (lock != UNLOCKED) {
193 		error = _twait(&mutex->lock, CONTENDED, CLOCK_REALTIME, abs);
194 		if (error == ETIMEDOUT)
195 			return (error);
196 		/*
197 		 * We cannot know if there's another waiter, so in
198 		 * doubt set the state to CONTENDED.
199 		 */
200 		lock = atomic_swap_uint(&mutex->lock, CONTENDED);
201 	};
202 
203 	membar_enter_after_atomic();
204 	mutex->owner = self;
205 	return (0);
206 }
207 
208 int
209 pthread_mutex_trylock(pthread_mutex_t *mutexp)
210 {
211 	return (_rthread_mutex_timedlock(mutexp, 1, NULL, 0));
212 }
213 
214 int
215 pthread_mutex_timedlock(pthread_mutex_t *mutexp, const struct timespec *abs)
216 {
217 	return (_rthread_mutex_timedlock(mutexp, 0, abs, 1));
218 }
219 
220 int
221 pthread_mutex_lock(pthread_mutex_t *mutexp)
222 {
223 	return (_rthread_mutex_timedlock(mutexp, 0, NULL, 0));
224 }
225 DEF_STRONG(pthread_mutex_lock);
226 
227 int
228 pthread_mutex_unlock(pthread_mutex_t *mutexp)
229 {
230 	pthread_t self = pthread_self();
231 	pthread_mutex_t mutex;
232 
233 	if (mutexp == NULL)
234 		return (EINVAL);
235 
236 	if (*mutexp == NULL)
237 #if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_ERRORCHECK
238 		return (EPERM);
239 #elif PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
240 		return(0);
241 #else
242 		abort();
243 #endif
244 
245 	mutex = *mutexp;
246 	_rthread_debug(5, "%p: mutex_unlock %p (%p)\n", self, (void *)mutex,
247 	    (void *)mutex->owner);
248 
249 	if (mutex->owner != self) {
250 	_rthread_debug(5, "%p: different owner %p (%p)\n", self, (void *)mutex,
251 	    (void *)mutex->owner);
252 		if (mutex->type == PTHREAD_MUTEX_ERRORCHECK ||
253 		    mutex->type == PTHREAD_MUTEX_RECURSIVE) {
254 			return (EPERM);
255 		} else {
256 			/*
257 			 * For mutex type NORMAL our undefined behavior for
258 			 * unlocking an unlocked mutex is to succeed without
259 			 * error.  All other undefined behaviors are to
260 			 * abort() immediately.
261 			 */
262 			if (mutex->owner == NULL &&
263 			    mutex->type == PTHREAD_MUTEX_NORMAL)
264 				return (0);
265 			else
266 				abort();
267 
268 		}
269 	}
270 
271 	if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
272 		if (mutex->count > 0) {
273 			mutex->count--;
274 			return (0);
275 		}
276 	}
277 
278 	mutex->owner = NULL;
279 	membar_exit_before_atomic();
280 	if (atomic_dec_int_nv(&mutex->lock) != UNLOCKED) {
281 		mutex->lock = UNLOCKED;
282 		_wake(&mutex->lock, 1);
283 	}
284 
285 	return (0);
286 }
287 DEF_STRONG(pthread_mutex_unlock);
288