1 /* $OpenBSD: rthread_rwlock.c,v 1.2 2012/01/17 02:34:18 guenther 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 24 #include <assert.h> 25 #include <stdlib.h> 26 #include <unistd.h> 27 #include <errno.h> 28 29 #include <pthread.h> 30 31 #include "rthread.h" 32 33 34 static _spinlock_lock_t rwlock_init_lock = _SPINLOCK_UNLOCKED; 35 36 /* ARGSUSED1 */ 37 int 38 pthread_rwlock_init(pthread_rwlock_t *lockp, 39 const pthread_rwlockattr_t *attrp __unused) 40 { 41 pthread_rwlock_t lock; 42 43 lock = calloc(1, sizeof(*lock)); 44 if (!lock) 45 return (errno); 46 lock->lock = _SPINLOCK_UNLOCKED; 47 TAILQ_INIT(&lock->writers); 48 49 *lockp = lock; 50 51 return (0); 52 } 53 54 int 55 pthread_rwlock_destroy(pthread_rwlock_t *lockp) 56 { 57 pthread_rwlock_t lock; 58 59 assert(lockp); 60 lock = *lockp; 61 if (lock) { 62 if (lock->readers || !TAILQ_EMPTY(&lock->writers)) { 63 #define MSG "pthread_rwlock_destroy on rwlock with waiters!\n" 64 write(2, MSG, sizeof(MSG) - 1); 65 #undef MSG 66 return (EBUSY); 67 } 68 free(lock); 69 } 70 *lockp = NULL; 71 72 return (0); 73 } 74 75 static int 76 _rthread_rwlock_ensure_init(pthread_rwlock_t *lockp) 77 { 78 int ret = 0; 79 80 /* 81 * If the rwlock is statically initialized, perform the dynamic 82 * initialization. 83 */ 84 if (*lockp == NULL) 85 { 86 _spinlock(&rwlock_init_lock); 87 if (*lockp == NULL) 88 ret = pthread_rwlock_init(lockp, NULL); 89 _spinunlock(&rwlock_init_lock); 90 } 91 return (ret); 92 } 93 94 95 static int 96 _rthread_rwlock_rdlock(pthread_rwlock_t *lockp, const struct timespec *abstime, 97 int try) 98 { 99 pthread_rwlock_t lock; 100 pthread_t thread = pthread_self(); 101 int error; 102 103 if ((error = _rthread_rwlock_ensure_init(lockp))) 104 return (error); 105 106 lock = *lockp; 107 _rthread_debug(5, "%p: rwlock_rdlock %p\n", (void *)thread, 108 (void *)lock); 109 _spinlock(&lock->lock); 110 111 /* writers have precedence */ 112 if (lock->owner == NULL && TAILQ_EMPTY(&lock->writers)) 113 lock->readers++; 114 else if (try) 115 error = EBUSY; 116 else if (lock->owner == thread) 117 error = EDEADLK; 118 else { 119 do { 120 if (__thrsleep(lock, CLOCK_REALTIME, abstime, 121 &lock->lock, NULL) == EWOULDBLOCK) 122 return (ETIMEDOUT); 123 _spinlock(&lock->lock); 124 } while (lock->owner != NULL || !TAILQ_EMPTY(&lock->writers)); 125 lock->readers++; 126 } 127 _spinunlock(&lock->lock); 128 129 return (error); 130 } 131 132 int 133 pthread_rwlock_rdlock(pthread_rwlock_t *lockp) 134 { 135 return (_rthread_rwlock_rdlock(lockp, NULL, 0)); 136 } 137 138 int 139 pthread_rwlock_tryrdlock(pthread_rwlock_t *lockp) 140 { 141 return (_rthread_rwlock_rdlock(lockp, NULL, 1)); 142 } 143 144 int 145 pthread_rwlock_timedrdlock(pthread_rwlock_t *lockp, 146 const struct timespec *abstime) 147 { 148 if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || 149 abstime->tv_nsec > 1000000000) 150 return (EINVAL); 151 return (_rthread_rwlock_rdlock(lockp, abstime, 0)); 152 } 153 154 155 static int 156 _rthread_rwlock_wrlock(pthread_rwlock_t *lockp, const struct timespec *abstime, 157 int try) 158 { 159 pthread_rwlock_t lock; 160 pthread_t thread = pthread_self(); 161 int error; 162 163 if ((error = _rthread_rwlock_ensure_init(lockp))) 164 return (error); 165 166 lock = *lockp; 167 168 _rthread_debug(5, "%p: rwlock_timedwrlock %p\n", (void *)thread, 169 (void *)lock); 170 _spinlock(&lock->lock); 171 if (lock->readers == 0 && lock->owner == NULL) 172 lock->owner = thread; 173 else if (try) 174 error = EBUSY; 175 else if (lock->owner == thread) 176 error = EDEADLK; 177 else { 178 int do_wait; 179 180 /* gotta block */ 181 TAILQ_INSERT_TAIL(&lock->writers, thread, waiting); 182 do { 183 do_wait = __thrsleep(thread, CLOCK_REALTIME, abstime, 184 &lock->lock, NULL) != EWOULDBLOCK; 185 _spinlock(&lock->lock); 186 } while (lock->owner != thread && do_wait); 187 188 if (lock->owner != thread) { 189 /* timed out, sigh */ 190 TAILQ_REMOVE(&lock->writers, thread, waiting); 191 error = ETIMEDOUT; 192 } 193 } 194 _spinunlock(&lock->lock); 195 196 return (error); 197 } 198 199 int 200 pthread_rwlock_wrlock(pthread_rwlock_t *lockp) 201 { 202 return (_rthread_rwlock_wrlock(lockp, NULL, 0)); 203 } 204 205 int 206 pthread_rwlock_trywrlock(pthread_rwlock_t *lockp) 207 { 208 return (_rthread_rwlock_wrlock(lockp, NULL, 1)); 209 } 210 211 int 212 pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp, 213 const struct timespec *abstime) 214 { 215 if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || 216 abstime->tv_nsec > 1000000000) 217 return (EINVAL); 218 return (_rthread_rwlock_wrlock(lockp, abstime, 0)); 219 } 220 221 222 int 223 pthread_rwlock_unlock(pthread_rwlock_t *lockp) 224 { 225 pthread_rwlock_t lock; 226 pthread_t thread = pthread_self(); 227 pthread_t next; 228 int was_writer; 229 230 lock = *lockp; 231 232 _rthread_debug(5, "%p: rwlock_unlock %p\n", (void *)thread, 233 (void *)lock); 234 _spinlock(&lock->lock); 235 if (lock->owner != NULL) { 236 assert(lock->owner == thread); 237 was_writer = 1; 238 } else { 239 assert(lock->readers > 0); 240 lock->readers--; 241 if (lock->readers > 0) 242 goto out; 243 was_writer = 0; 244 } 245 246 lock->owner = next = TAILQ_FIRST(&lock->writers); 247 if (next != NULL) { 248 /* dequeue and wake first writer */ 249 TAILQ_REMOVE(&lock->writers, next, waiting); 250 _spinunlock(&lock->lock); 251 __thrwakeup(next, 1); 252 return (0); 253 } 254 255 /* could there have been blocked readers? wake them all */ 256 if (was_writer) 257 __thrwakeup(lock, 0); 258 out: 259 _spinunlock(&lock->lock); 260 261 return (0); 262 } 263