1 /* $OpenBSD: rthread_rwlock.c,v 1.6 2016/04/02 19:56:53 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 struct _spinlock rwlock_init_lock = _SPINLOCK_UNLOCKED; 35 36 int 37 pthread_rwlock_init(pthread_rwlock_t *lockp, 38 const pthread_rwlockattr_t *attrp __unused) 39 { 40 pthread_rwlock_t lock; 41 42 lock = calloc(1, sizeof(*lock)); 43 if (!lock) 44 return (errno); 45 lock->lock = _SPINLOCK_UNLOCKED_ASSIGN; 46 TAILQ_INIT(&lock->writers); 47 48 *lockp = lock; 49 50 return (0); 51 } 52 DEF_STD(pthread_rwlock_init); 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 | _USING_TICKETS, 121 abstime, &lock->lock.ticket, 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 | 184 _USING_TICKETS, abstime, 185 &lock->lock.ticket, NULL) != EWOULDBLOCK; 186 _spinlock(&lock->lock); 187 } while (lock->owner != thread && do_wait); 188 189 if (lock->owner != thread) { 190 /* timed out, sigh */ 191 TAILQ_REMOVE(&lock->writers, thread, waiting); 192 error = ETIMEDOUT; 193 } 194 } 195 _spinunlock(&lock->lock); 196 197 return (error); 198 } 199 200 int 201 pthread_rwlock_wrlock(pthread_rwlock_t *lockp) 202 { 203 return (_rthread_rwlock_wrlock(lockp, NULL, 0)); 204 } 205 206 int 207 pthread_rwlock_trywrlock(pthread_rwlock_t *lockp) 208 { 209 return (_rthread_rwlock_wrlock(lockp, NULL, 1)); 210 } 211 212 int 213 pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp, 214 const struct timespec *abstime) 215 { 216 if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || 217 abstime->tv_nsec > 1000000000) 218 return (EINVAL); 219 return (_rthread_rwlock_wrlock(lockp, abstime, 0)); 220 } 221 222 223 int 224 pthread_rwlock_unlock(pthread_rwlock_t *lockp) 225 { 226 pthread_rwlock_t lock; 227 pthread_t thread = pthread_self(); 228 pthread_t next; 229 int was_writer; 230 231 lock = *lockp; 232 233 _rthread_debug(5, "%p: rwlock_unlock %p\n", (void *)thread, 234 (void *)lock); 235 _spinlock(&lock->lock); 236 if (lock->owner != NULL) { 237 assert(lock->owner == thread); 238 was_writer = 1; 239 } else { 240 assert(lock->readers > 0); 241 lock->readers--; 242 if (lock->readers > 0) 243 goto out; 244 was_writer = 0; 245 } 246 247 lock->owner = next = TAILQ_FIRST(&lock->writers); 248 if (next != NULL) { 249 /* dequeue and wake first writer */ 250 TAILQ_REMOVE(&lock->writers, next, waiting); 251 _spinunlock(&lock->lock); 252 __thrwakeup(next, 1); 253 return (0); 254 } 255 256 /* could there have been blocked readers? wake them all */ 257 if (was_writer) 258 __thrwakeup(lock, 0); 259 out: 260 _spinunlock(&lock->lock); 261 262 return (0); 263 } 264