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