rwlock.c (8760B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include <pthread.h> 21#include <stdint.h> 22#include "guacamole/error.h" 23#include "guacamole/rwlock.h" 24 25/** 26 * The value indicating that the current thread holds neither the read or write 27 * locks. 28 */ 29#define GUAC_REENTRANT_LOCK_NO_LOCK 0 30 31/** 32 * The value indicating that the current thread holds the read lock. 33 */ 34#define GUAC_REENTRANT_LOCK_READ_LOCK 1 35 36/** 37 * The value indicating that the current thread holds the write lock. 38 */ 39#define GUAC_REENTRANT_LOCK_WRITE_LOCK 2 40 41void guac_rwlock_init(guac_rwlock* lock) { 42 43 /* Configure to allow sharing this lock with child processes */ 44 pthread_rwlockattr_t lock_attributes; 45 pthread_rwlockattr_init(&lock_attributes); 46 pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); 47 48 /* Initialize the rwlock */ 49 pthread_rwlock_init(&(lock->lock), &lock_attributes); 50 51 /* Initialize the flags to 0, as threads won't have acquired it yet */ 52 pthread_key_create(&(lock->key), (void *) 0); 53 54} 55 56void guac_rwlock_destroy(guac_rwlock* lock) { 57 58 /* Destroy the rwlock */ 59 pthread_rwlock_destroy(&(lock->lock)); 60 61 /* Destroy the thread-local key */ 62 pthread_key_delete(lock->key); 63 64} 65 66/** 67 * Clean up and destroy the provided guac reentrant rwlock. 68 * 69 * @param lock 70 * The guac reentrant rwlock to be destroyed. 71 */ 72void guac_rwlock_destroy(guac_rwlock* lock); 73 74/** 75 * Extract and return the flag indicating which lock is held, if any, from the 76 * provided key value. The flag is always stored in the least-significant 77 * nibble of the value. 78 * 79 * @param value 80 * The key value containing the flag. 81 * 82 * @return 83 * The flag indicating which lock is held, if any. 84 */ 85static uintptr_t get_lock_flag(uintptr_t value) { 86 return value & 0xF; 87} 88 89/** 90 * Extract and return the lock count from the provided key. This returned value 91 * is the difference between the number of lock and unlock requests made by the 92 * current thread. This count is always stored in the remaining value after the 93 * least-significant nibble where the flag is stored. 94 * 95 * @param value 96 * The key value containing the count. 97 * 98 * @return 99 * The difference between the number of lock and unlock requests made by 100 * the current thread. 101 */ 102static uintptr_t get_lock_count(uintptr_t value) { 103 return value >> 4; 104} 105 106/** 107 * Given a flag indicating if and how the current thread controls a lock, and 108 * a count of the depth of lock requests, return a value containing the flag 109 * in the least-significant nibble, and the count in the rest. 110 * 111 * @param flag 112 * A flag indiciating which lock, if any, is held by the current thread. 113 * 114 * @param count 115 * The depth of the lock attempt by the current thread, i.e. the number of 116 * lock requests minus unlock requests. 117 * 118 * @return 119 * A value containing the flag in the least-significant nibble, and the 120 * count in the rest, cast to a void* for thread-local storage. 121 */ 122static void* get_value_from_flag_and_count( 123 uintptr_t flag, uintptr_t count) { 124 return (void*) ((flag & 0xF) | count << 4); 125} 126 127/** 128 * Return zero if adding one to the current count would overflow the storage 129 * allocated to the count, or a non-zero value otherwise. 130 * 131 * @param current_count 132 * The current count for a lock that the current thread is trying to 133 * reentrantly acquire. 134 * 135 * @return 136 * Zero if adding one to the current count would overflow the storage 137 * allocated to the count, or a non-zero value otherwise. 138 */ 139static int would_overflow_count(uintptr_t current_count) { 140 141 /** 142 * The count will overflow if it's already equal or greated to the maximum 143 * possible value that can be stored in a uintptr_t excluding the first nibble. 144 */ 145 return current_count >= (UINTPTR_MAX >> 4); 146 147} 148 149int guac_rwlock_acquire_write_lock(guac_rwlock* reentrant_rwlock) { 150 151 uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key); 152 uintptr_t flag = get_lock_flag(key_value); 153 uintptr_t count = get_lock_count(key_value); 154 155 /* If acquiring this lock again would overflow the counter storage */ 156 if (would_overflow_count(count)) { 157 158 guac_error = GUAC_STATUS_TOO_MANY; 159 guac_error_message = "Unable to acquire write lock because there's" 160 " insufficient space to store another level of lock depth"; 161 162 return 1; 163 164 } 165 166 /* If the current thread already holds the write lock, increment the count */ 167 if (flag == GUAC_REENTRANT_LOCK_WRITE_LOCK) { 168 pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( 169 flag, count + 1)); 170 171 /* This thread already has the lock */ 172 return 0; 173 } 174 175 /* 176 * The read lock must be released before the write lock can be acquired. 177 * This is a little odd because it may mean that a function further down 178 * the stack may have requested a read lock, which will get upgraded to a 179 * write lock by another function without the caller knowing about it. This 180 * shouldn't cause any issues, however. 181 */ 182 if (key_value == GUAC_REENTRANT_LOCK_READ_LOCK) 183 pthread_rwlock_unlock(&(reentrant_rwlock->lock)); 184 185 /* Acquire the write lock */ 186 pthread_rwlock_wrlock(&(reentrant_rwlock->lock)); 187 188 /* Mark that the current thread has the lock, and increment the count */ 189 pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( 190 GUAC_REENTRANT_LOCK_WRITE_LOCK, count + 1)); 191 192 return 0; 193 194} 195 196int guac_rwlock_acquire_read_lock(guac_rwlock* reentrant_rwlock) { 197 198 uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key); 199 uintptr_t flag = get_lock_flag(key_value); 200 uintptr_t count = get_lock_count(key_value); 201 202 /* If acquiring this lock again would overflow the counter storage */ 203 if (would_overflow_count(count)) { 204 205 guac_error = GUAC_STATUS_TOO_MANY; 206 guac_error_message = "Unable to acquire read lock because there's" 207 " insufficient space to store another level of lock depth"; 208 209 return 1; 210 211 } 212 213 /* The current thread may read if either the read or write lock is held */ 214 if ( 215 flag == GUAC_REENTRANT_LOCK_READ_LOCK || 216 flag == GUAC_REENTRANT_LOCK_WRITE_LOCK 217 ) { 218 219 /* Increment the depth counter */ 220 pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( 221 flag, count + 1)); 222 223 /* This thread already has the lock */ 224 return 0; 225 } 226 227 /* Acquire the lock */ 228 pthread_rwlock_rdlock(&(reentrant_rwlock->lock)); 229 230 /* Set the flag that the current thread has the read lock */ 231 pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( 232 GUAC_REENTRANT_LOCK_READ_LOCK, 1)); 233 234 return 0; 235 236} 237 238int guac_rwlock_release_lock(guac_rwlock* reentrant_rwlock) { 239 240 uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key); 241 uintptr_t flag = get_lock_flag(key_value); 242 uintptr_t count = get_lock_count(key_value); 243 244 /* 245 * Return an error if an attempt is made to release a lock that the current 246 * thread does not control. 247 */ 248 if (count <= 0) { 249 250 guac_error = GUAC_STATUS_INVALID_ARGUMENT; 251 guac_error_message = "Unable to free rwlock because it's not held by" 252 " the current thread"; 253 254 return 1; 255 256 } 257 258 /* Release the lock if this is the last locked level */ 259 if (count == 1) { 260 261 pthread_rwlock_unlock(&(reentrant_rwlock->lock)); 262 263 /* Set the flag that the current thread holds no locks */ 264 pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( 265 GUAC_REENTRANT_LOCK_NO_LOCK, 0)); 266 267 return 0; 268 } 269 270 /* Do not release the lock since it's still in use - just decrement */ 271 pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( 272 flag, count - 1)); 273 274 return 0; 275 276}