xref: /openbsd-src/lib/librthread/rthread_rwlock_compat.c (revision c554a768c1617ab8d979dfdbb7ffdf8827e11e8e)
1 /*	$OpenBSD: rthread_rwlock_compat.c,v 1.2 2022/05/14 14:52:20 cheloha Exp $ */
2 /*
3  * Copyright (c) 2004,2005 Ted Unangst <tedu@openbsd.org>
4  * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 /*
20  * rwlocks
21  */
22 
23 #include <assert.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <errno.h>
27 
28 #include <pthread.h>
29 
30 #include "rthread.h"
31 
32 static _atomic_lock_t rwlock_init_lock = _SPINLOCK_UNLOCKED;
33 
34 int
pthread_rwlock_init(pthread_rwlock_t * lockp,const pthread_rwlockattr_t * attrp __unused)35 pthread_rwlock_init(pthread_rwlock_t *lockp,
36     const pthread_rwlockattr_t *attrp __unused)
37 {
38 	pthread_rwlock_t lock;
39 
40 	lock = calloc(1, sizeof(*lock));
41 	if (!lock)
42 		return (errno);
43 	lock->lock = _SPINLOCK_UNLOCKED;
44 	TAILQ_INIT(&lock->writers);
45 
46 	*lockp = lock;
47 
48 	return (0);
49 }
50 DEF_STD(pthread_rwlock_init);
51 
52 int
pthread_rwlock_destroy(pthread_rwlock_t * lockp)53 pthread_rwlock_destroy(pthread_rwlock_t *lockp)
54 {
55 	pthread_rwlock_t lock;
56 
57 	assert(lockp);
58 	lock = *lockp;
59 	if (lock) {
60 		if (lock->readers || !TAILQ_EMPTY(&lock->writers)) {
61 #define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
62 			write(2, MSG, sizeof(MSG) - 1);
63 #undef MSG
64 			return (EBUSY);
65 		}
66 		free(lock);
67 	}
68 	*lockp = NULL;
69 
70 	return (0);
71 }
72 
73 static int
_rthread_rwlock_ensure_init(pthread_rwlock_t * lockp)74 _rthread_rwlock_ensure_init(pthread_rwlock_t *lockp)
75 {
76 	int ret = 0;
77 
78 	/*
79 	 * If the rwlock is statically initialized, perform the dynamic
80 	 * initialization.
81 	 */
82 	if (*lockp == NULL)
83 	{
84 		_spinlock(&rwlock_init_lock);
85 		if (*lockp == NULL)
86 			ret = pthread_rwlock_init(lockp, NULL);
87 		_spinunlock(&rwlock_init_lock);
88 	}
89 	return (ret);
90 }
91 
92 
93 static int
_rthread_rwlock_rdlock(pthread_rwlock_t * lockp,const struct timespec * abstime,int try)94 _rthread_rwlock_rdlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
95     int try)
96 {
97 	pthread_rwlock_t lock;
98 	pthread_t thread = pthread_self();
99 	int error;
100 
101 	if ((error = _rthread_rwlock_ensure_init(lockp)))
102 		return (error);
103 
104 	lock = *lockp;
105 	_rthread_debug(5, "%p: rwlock_rdlock %p\n", (void *)thread,
106 	    (void *)lock);
107 	_spinlock(&lock->lock);
108 
109 	/* writers have precedence */
110 	if (lock->owner == NULL && TAILQ_EMPTY(&lock->writers))
111 		lock->readers++;
112 	else if (try)
113 		error = EBUSY;
114 	else if (lock->owner == thread)
115 		error = EDEADLK;
116 	else {
117 		do {
118 			if (__thrsleep(lock, CLOCK_REALTIME, abstime,
119 			    &lock->lock, NULL) == EWOULDBLOCK)
120 				return (ETIMEDOUT);
121 			_spinlock(&lock->lock);
122 		} while (lock->owner != NULL || !TAILQ_EMPTY(&lock->writers));
123 		lock->readers++;
124 	}
125 	_spinunlock(&lock->lock);
126 
127 	return (error);
128 }
129 
130 int
pthread_rwlock_rdlock(pthread_rwlock_t * lockp)131 pthread_rwlock_rdlock(pthread_rwlock_t *lockp)
132 {
133 	return (_rthread_rwlock_rdlock(lockp, NULL, 0));
134 }
135 
136 int
pthread_rwlock_tryrdlock(pthread_rwlock_t * lockp)137 pthread_rwlock_tryrdlock(pthread_rwlock_t *lockp)
138 {
139 	return (_rthread_rwlock_rdlock(lockp, NULL, 1));
140 }
141 
142 int
pthread_rwlock_timedrdlock(pthread_rwlock_t * lockp,const struct timespec * abstime)143 pthread_rwlock_timedrdlock(pthread_rwlock_t *lockp,
144     const struct timespec *abstime)
145 {
146 	if (abstime == NULL || !timespecisvalid(abstime))
147 		return (EINVAL);
148 	return (_rthread_rwlock_rdlock(lockp, abstime, 0));
149 }
150 
151 
152 static int
_rthread_rwlock_wrlock(pthread_rwlock_t * lockp,const struct timespec * abstime,int try)153 _rthread_rwlock_wrlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
154     int try)
155 {
156 	pthread_rwlock_t lock;
157 	pthread_t thread = pthread_self();
158 	int error;
159 
160 	if ((error = _rthread_rwlock_ensure_init(lockp)))
161 		return (error);
162 
163 	lock = *lockp;
164 
165 	_rthread_debug(5, "%p: rwlock_timedwrlock %p\n", (void *)thread,
166 	    (void *)lock);
167 	_spinlock(&lock->lock);
168 	if (lock->readers == 0 && lock->owner == NULL)
169 		lock->owner = thread;
170 	else if (try)
171 		error = EBUSY;
172 	else if (lock->owner == thread)
173 		error = EDEADLK;
174 	else {
175 		int do_wait;
176 
177 		/* gotta block */
178 		TAILQ_INSERT_TAIL(&lock->writers, thread, waiting);
179 		do {
180 			do_wait = __thrsleep(thread, CLOCK_REALTIME, abstime,
181 			    &lock->lock, NULL) != EWOULDBLOCK;
182 			_spinlock(&lock->lock);
183 		} while (lock->owner != thread && do_wait);
184 
185 		if (lock->owner != thread) {
186 			/* timed out, sigh */
187 			TAILQ_REMOVE(&lock->writers, thread, waiting);
188 			error = ETIMEDOUT;
189 		}
190 	}
191 	_spinunlock(&lock->lock);
192 
193 	return (error);
194 }
195 
196 int
pthread_rwlock_wrlock(pthread_rwlock_t * lockp)197 pthread_rwlock_wrlock(pthread_rwlock_t *lockp)
198 {
199 	return (_rthread_rwlock_wrlock(lockp, NULL, 0));
200 }
201 
202 int
pthread_rwlock_trywrlock(pthread_rwlock_t * lockp)203 pthread_rwlock_trywrlock(pthread_rwlock_t *lockp)
204 {
205 	return (_rthread_rwlock_wrlock(lockp, NULL, 1));
206 }
207 
208 int
pthread_rwlock_timedwrlock(pthread_rwlock_t * lockp,const struct timespec * abstime)209 pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp,
210     const struct timespec *abstime)
211 {
212 	if (abstime == NULL || !timespecisvalid(abstime))
213 		return (EINVAL);
214 	return (_rthread_rwlock_wrlock(lockp, abstime, 0));
215 }
216 
217 
218 int
pthread_rwlock_unlock(pthread_rwlock_t * lockp)219 pthread_rwlock_unlock(pthread_rwlock_t *lockp)
220 {
221 	pthread_rwlock_t lock;
222 	pthread_t thread = pthread_self();
223 	pthread_t next;
224 	int was_writer;
225 
226 	lock = *lockp;
227 
228 	_rthread_debug(5, "%p: rwlock_unlock %p\n", (void *)thread,
229 	    (void *)lock);
230 	_spinlock(&lock->lock);
231 	if (lock->owner != NULL) {
232 		assert(lock->owner == thread);
233 		was_writer = 1;
234 	} else {
235 		assert(lock->readers > 0);
236 		lock->readers--;
237 		if (lock->readers > 0)
238 			goto out;
239 		was_writer = 0;
240 	}
241 
242 	lock->owner = next = TAILQ_FIRST(&lock->writers);
243 	if (next != NULL) {
244 		/* dequeue and wake first writer */
245 		TAILQ_REMOVE(&lock->writers, next, waiting);
246 		_spinunlock(&lock->lock);
247 		__thrwakeup(next, 1);
248 		return (0);
249 	}
250 
251 	/* could there have been blocked readers?  wake them all */
252 	if (was_writer)
253 		__thrwakeup(lock, 0);
254 out:
255 	_spinunlock(&lock->lock);
256 
257 	return (0);
258 }
259