1/* Read-write locks (native Windows implementation). 2 Copyright (C) 2005-2022 Free Software Foundation, Inc. 3 4 This file is free software: you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 2.1 of the 7 License, or (at your option) any later version. 8 9 This file is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU Lesser General Public License for more details. 13 14 You should have received a copy of the GNU Lesser General Public License 15 along with this program. If not, see <https://www.gnu.org/licenses/>. */ 16 17/* Written by Bruno Haible <bruno@clisp.org>, 2005. 18 Based on GCC's gthr-win32.h. */ 19 20#include <config.h> 21 22/* Specification. */ 23#include "windows-rwlock.h" 24 25#include <errno.h> 26#include <stdlib.h> 27 28/* Don't assume that UNICODE is not defined. */ 29#undef CreateEvent 30#define CreateEvent CreateEventA 31 32/* In this file, the waitqueues are implemented as circular arrays. */ 33#define glwthread_waitqueue_t glwthread_carray_waitqueue_t 34 35static void 36glwthread_waitqueue_init (glwthread_waitqueue_t *wq) 37{ 38 wq->array = NULL; 39 wq->count = 0; 40 wq->alloc = 0; 41 wq->offset = 0; 42} 43 44/* Enqueues the current thread, represented by an event, in a wait queue. 45 Returns INVALID_HANDLE_VALUE if an allocation failure occurs. */ 46static HANDLE 47glwthread_waitqueue_add (glwthread_waitqueue_t *wq) 48{ 49 HANDLE event; 50 unsigned int index; 51 52 if (wq->count == wq->alloc) 53 { 54 unsigned int new_alloc = 2 * wq->alloc + 1; 55 HANDLE *new_array = 56 (HANDLE *) realloc (wq->array, new_alloc * sizeof (HANDLE)); 57 if (new_array == NULL) 58 /* No more memory. */ 59 return INVALID_HANDLE_VALUE; 60 /* Now is a good opportunity to rotate the array so that its contents 61 starts at offset 0. */ 62 if (wq->offset > 0) 63 { 64 unsigned int old_count = wq->count; 65 unsigned int old_alloc = wq->alloc; 66 unsigned int old_offset = wq->offset; 67 unsigned int i; 68 if (old_offset + old_count > old_alloc) 69 { 70 unsigned int limit = old_offset + old_count - old_alloc; 71 for (i = 0; i < limit; i++) 72 new_array[old_alloc + i] = new_array[i]; 73 } 74 for (i = 0; i < old_count; i++) 75 new_array[i] = new_array[old_offset + i]; 76 wq->offset = 0; 77 } 78 wq->array = new_array; 79 wq->alloc = new_alloc; 80 } 81 /* Whether the created event is a manual-reset one or an auto-reset one, 82 does not matter, since we will wait on it only once. */ 83 event = CreateEvent (NULL, TRUE, FALSE, NULL); 84 if (event == INVALID_HANDLE_VALUE) 85 /* No way to allocate an event. */ 86 return INVALID_HANDLE_VALUE; 87 index = wq->offset + wq->count; 88 if (index >= wq->alloc) 89 index -= wq->alloc; 90 wq->array[index] = event; 91 wq->count++; 92 return event; 93} 94 95/* Notifies the first thread from a wait queue and dequeues it. */ 96static void 97glwthread_waitqueue_notify_first (glwthread_waitqueue_t *wq) 98{ 99 SetEvent (wq->array[wq->offset + 0]); 100 wq->offset++; 101 wq->count--; 102 if (wq->count == 0 || wq->offset == wq->alloc) 103 wq->offset = 0; 104} 105 106/* Notifies all threads from a wait queue and dequeues them all. */ 107static void 108glwthread_waitqueue_notify_all (glwthread_waitqueue_t *wq) 109{ 110 unsigned int i; 111 112 for (i = 0; i < wq->count; i++) 113 { 114 unsigned int index = wq->offset + i; 115 if (index >= wq->alloc) 116 index -= wq->alloc; 117 SetEvent (wq->array[index]); 118 } 119 wq->count = 0; 120 wq->offset = 0; 121} 122 123void 124glwthread_rwlock_init (glwthread_rwlock_t *lock) 125{ 126 InitializeCriticalSection (&lock->lock); 127 glwthread_waitqueue_init (&lock->waiting_readers); 128 glwthread_waitqueue_init (&lock->waiting_writers); 129 lock->runcount = 0; 130 lock->guard.done = 1; 131} 132 133int 134glwthread_rwlock_rdlock (glwthread_rwlock_t *lock) 135{ 136 if (!lock->guard.done) 137 { 138 if (InterlockedIncrement (&lock->guard.started) == 0) 139 /* This thread is the first one to need this lock. Initialize it. */ 140 glwthread_rwlock_init (lock); 141 else 142 { 143 /* Don't let lock->guard.started grow and wrap around. */ 144 InterlockedDecrement (&lock->guard.started); 145 /* Yield the CPU while waiting for another thread to finish 146 initializing this lock. */ 147 while (!lock->guard.done) 148 Sleep (0); 149 } 150 } 151 EnterCriticalSection (&lock->lock); 152 /* Test whether only readers are currently running, and whether the runcount 153 field will not overflow, and whether no writer is waiting. The latter 154 condition is because POSIX recommends that "write locks shall take 155 precedence over read locks", to avoid "writer starvation". */ 156 if (!(lock->runcount + 1 > 0 && lock->waiting_writers.count == 0)) 157 { 158 /* This thread has to wait for a while. Enqueue it among the 159 waiting_readers. */ 160 HANDLE event = glwthread_waitqueue_add (&lock->waiting_readers); 161 if (event != INVALID_HANDLE_VALUE) 162 { 163 DWORD result; 164 LeaveCriticalSection (&lock->lock); 165 /* Wait until another thread signals this event. */ 166 result = WaitForSingleObject (event, INFINITE); 167 if (result == WAIT_FAILED || result == WAIT_TIMEOUT) 168 abort (); 169 CloseHandle (event); 170 /* The thread which signalled the event already did the bookkeeping: 171 removed us from the waiting_readers, incremented lock->runcount. */ 172 if (!(lock->runcount > 0)) 173 abort (); 174 return 0; 175 } 176 else 177 { 178 /* Allocation failure. Weird. */ 179 do 180 { 181 LeaveCriticalSection (&lock->lock); 182 Sleep (1); 183 EnterCriticalSection (&lock->lock); 184 } 185 while (!(lock->runcount + 1 > 0)); 186 } 187 } 188 lock->runcount++; 189 LeaveCriticalSection (&lock->lock); 190 return 0; 191} 192 193int 194glwthread_rwlock_wrlock (glwthread_rwlock_t *lock) 195{ 196 if (!lock->guard.done) 197 { 198 if (InterlockedIncrement (&lock->guard.started) == 0) 199 /* This thread is the first one to need this lock. Initialize it. */ 200 glwthread_rwlock_init (lock); 201 else 202 { 203 /* Don't let lock->guard.started grow and wrap around. */ 204 InterlockedDecrement (&lock->guard.started); 205 /* Yield the CPU while waiting for another thread to finish 206 initializing this lock. */ 207 while (!lock->guard.done) 208 Sleep (0); 209 } 210 } 211 EnterCriticalSection (&lock->lock); 212 /* Test whether no readers or writers are currently running. */ 213 if (!(lock->runcount == 0)) 214 { 215 /* This thread has to wait for a while. Enqueue it among the 216 waiting_writers. */ 217 HANDLE event = glwthread_waitqueue_add (&lock->waiting_writers); 218 if (event != INVALID_HANDLE_VALUE) 219 { 220 DWORD result; 221 LeaveCriticalSection (&lock->lock); 222 /* Wait until another thread signals this event. */ 223 result = WaitForSingleObject (event, INFINITE); 224 if (result == WAIT_FAILED || result == WAIT_TIMEOUT) 225 abort (); 226 CloseHandle (event); 227 /* The thread which signalled the event already did the bookkeeping: 228 removed us from the waiting_writers, set lock->runcount = -1. */ 229 if (!(lock->runcount == -1)) 230 abort (); 231 return 0; 232 } 233 else 234 { 235 /* Allocation failure. Weird. */ 236 do 237 { 238 LeaveCriticalSection (&lock->lock); 239 Sleep (1); 240 EnterCriticalSection (&lock->lock); 241 } 242 while (!(lock->runcount == 0)); 243 } 244 } 245 lock->runcount--; /* runcount becomes -1 */ 246 LeaveCriticalSection (&lock->lock); 247 return 0; 248} 249 250int 251glwthread_rwlock_tryrdlock (glwthread_rwlock_t *lock) 252{ 253 if (!lock->guard.done) 254 { 255 if (InterlockedIncrement (&lock->guard.started) == 0) 256 /* This thread is the first one to need this lock. Initialize it. */ 257 glwthread_rwlock_init (lock); 258 else 259 { 260 /* Don't let lock->guard.started grow and wrap around. */ 261 InterlockedDecrement (&lock->guard.started); 262 /* Yield the CPU while waiting for another thread to finish 263 initializing this lock. */ 264 while (!lock->guard.done) 265 Sleep (0); 266 } 267 } 268 /* It's OK to wait for this critical section, because it is never taken for a 269 long time. */ 270 EnterCriticalSection (&lock->lock); 271 /* Test whether only readers are currently running, and whether the runcount 272 field will not overflow, and whether no writer is waiting. The latter 273 condition is because POSIX recommends that "write locks shall take 274 precedence over read locks", to avoid "writer starvation". */ 275 if (!(lock->runcount + 1 > 0 && lock->waiting_writers.count == 0)) 276 { 277 /* This thread would have to wait for a while. Return instead. */ 278 LeaveCriticalSection (&lock->lock); 279 return EBUSY; 280 } 281 lock->runcount++; 282 LeaveCriticalSection (&lock->lock); 283 return 0; 284} 285 286int 287glwthread_rwlock_trywrlock (glwthread_rwlock_t *lock) 288{ 289 if (!lock->guard.done) 290 { 291 if (InterlockedIncrement (&lock->guard.started) == 0) 292 /* This thread is the first one to need this lock. Initialize it. */ 293 glwthread_rwlock_init (lock); 294 else 295 { 296 /* Don't let lock->guard.started grow and wrap around. */ 297 InterlockedDecrement (&lock->guard.started); 298 /* Yield the CPU while waiting for another thread to finish 299 initializing this lock. */ 300 while (!lock->guard.done) 301 Sleep (0); 302 } 303 } 304 /* It's OK to wait for this critical section, because it is never taken for a 305 long time. */ 306 EnterCriticalSection (&lock->lock); 307 /* Test whether no readers or writers are currently running. */ 308 if (!(lock->runcount == 0)) 309 { 310 /* This thread would have to wait for a while. Return instead. */ 311 LeaveCriticalSection (&lock->lock); 312 return EBUSY; 313 } 314 lock->runcount--; /* runcount becomes -1 */ 315 LeaveCriticalSection (&lock->lock); 316 return 0; 317} 318 319int 320glwthread_rwlock_unlock (glwthread_rwlock_t *lock) 321{ 322 if (!lock->guard.done) 323 return EINVAL; 324 EnterCriticalSection (&lock->lock); 325 if (lock->runcount < 0) 326 { 327 /* Drop a writer lock. */ 328 if (!(lock->runcount == -1)) 329 abort (); 330 lock->runcount = 0; 331 } 332 else 333 { 334 /* Drop a reader lock. */ 335 if (!(lock->runcount > 0)) 336 { 337 LeaveCriticalSection (&lock->lock); 338 return EPERM; 339 } 340 lock->runcount--; 341 } 342 if (lock->runcount == 0) 343 { 344 /* POSIX recommends that "write locks shall take precedence over read 345 locks", to avoid "writer starvation". */ 346 if (lock->waiting_writers.count > 0) 347 { 348 /* Wake up one of the waiting writers. */ 349 lock->runcount--; 350 glwthread_waitqueue_notify_first (&lock->waiting_writers); 351 } 352 else 353 { 354 /* Wake up all waiting readers. */ 355 lock->runcount += lock->waiting_readers.count; 356 glwthread_waitqueue_notify_all (&lock->waiting_readers); 357 } 358 } 359 LeaveCriticalSection (&lock->lock); 360 return 0; 361} 362 363int 364glwthread_rwlock_destroy (glwthread_rwlock_t *lock) 365{ 366 if (!lock->guard.done) 367 return EINVAL; 368 if (lock->runcount != 0) 369 return EBUSY; 370 DeleteCriticalSection (&lock->lock); 371 if (lock->waiting_readers.array != NULL) 372 free (lock->waiting_readers.array); 373 if (lock->waiting_writers.array != NULL) 374 free (lock->waiting_writers.array); 375 lock->guard.done = 0; 376 return 0; 377} 378