1 /* Read-write locks (native Windows implementation).
2 Copyright (C) 2005-2024 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* Written by Bruno Haible <bruno@clisp.org>, 2005.
18 Based on GCC's gthr-win32.h. */
23 #include "windows-rwlock.h"
28 /* Don't assume that UNICODE is not defined. */
30 #define CreateEvent CreateEventA
32 /* In this file, the waitqueues are implemented as circular arrays. */
33 #define glwthread_waitqueue_t glwthread_carray_waitqueue_t
36 glwthread_waitqueue_init (glwthread_waitqueue_t
*wq
)
44 /* Enqueues the current thread, represented by an event, in a wait queue.
45 Returns INVALID_HANDLE_VALUE if an allocation failure occurs. */
47 glwthread_waitqueue_add (glwthread_waitqueue_t
*wq
)
52 if (wq
->count
== wq
->alloc
)
54 unsigned int new_alloc
= 2 * wq
->alloc
+ 1;
56 (HANDLE
*) realloc (wq
->array
, new_alloc
* sizeof (HANDLE
));
57 if (new_array
== NULL
)
59 return INVALID_HANDLE_VALUE
;
60 /* Now is a good opportunity to rotate the array so that its contents
61 starts at offset 0. */
64 unsigned int old_count
= wq
->count
;
65 unsigned int old_alloc
= wq
->alloc
;
66 unsigned int old_offset
= wq
->offset
;
68 if (old_offset
+ old_count
> old_alloc
)
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
];
74 for (i
= 0; i
< old_count
; i
++)
75 new_array
[i
] = new_array
[old_offset
+ i
];
78 wq
->array
= new_array
;
79 wq
->alloc
= new_alloc
;
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
)
90 wq
->array
[index
] = event
;
95 /* Notifies the first thread from a wait queue and dequeues it. */
97 glwthread_waitqueue_notify_first (glwthread_waitqueue_t
*wq
)
99 SetEvent (wq
->array
[wq
->offset
+ 0]);
102 if (wq
->count
== 0 || wq
->offset
== wq
->alloc
)
106 /* Notifies all threads from a wait queue and dequeues them all. */
108 glwthread_waitqueue_notify_all (glwthread_waitqueue_t
*wq
)
112 for (i
= 0; i
< wq
->count
; i
++)
114 unsigned int index
= wq
->offset
+ i
;
115 if (index
>= wq
->alloc
)
117 SetEvent (wq
->array
[index
]);
124 glwthread_rwlock_init (glwthread_rwlock_t
*lock
)
126 InitializeCriticalSection (&lock
->lock
);
127 glwthread_waitqueue_init (&lock
->waiting_readers
);
128 glwthread_waitqueue_init (&lock
->waiting_writers
);
130 lock
->guard
.done
= 1;
134 glwthread_rwlock_rdlock (glwthread_rwlock_t
*lock
)
136 if (!lock
->guard
.done
)
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
);
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
)
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))
158 /* This thread has to wait for a while. Enqueue it among the
160 HANDLE event
= glwthread_waitqueue_add (&lock
->waiting_readers
);
161 if (event
!= INVALID_HANDLE_VALUE
)
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
)
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))
178 /* Allocation failure. Weird. */
181 LeaveCriticalSection (&lock
->lock
);
183 EnterCriticalSection (&lock
->lock
);
185 while (!(lock
->runcount
+ 1 > 0));
189 LeaveCriticalSection (&lock
->lock
);
194 glwthread_rwlock_wrlock (glwthread_rwlock_t
*lock
)
196 if (!lock
->guard
.done
)
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
);
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
)
211 EnterCriticalSection (&lock
->lock
);
212 /* Test whether no readers or writers are currently running. */
213 if (!(lock
->runcount
== 0))
215 /* This thread has to wait for a while. Enqueue it among the
217 HANDLE event
= glwthread_waitqueue_add (&lock
->waiting_writers
);
218 if (event
!= INVALID_HANDLE_VALUE
)
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
)
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))
235 /* Allocation failure. Weird. */
238 LeaveCriticalSection (&lock
->lock
);
240 EnterCriticalSection (&lock
->lock
);
242 while (!(lock
->runcount
== 0));
245 lock
->runcount
--; /* runcount becomes -1 */
246 LeaveCriticalSection (&lock
->lock
);
251 glwthread_rwlock_tryrdlock (glwthread_rwlock_t
*lock
)
253 if (!lock
->guard
.done
)
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
);
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
)
268 /* It's OK to wait for this critical section, because it is never taken for a
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))
277 /* This thread would have to wait for a while. Return instead. */
278 LeaveCriticalSection (&lock
->lock
);
282 LeaveCriticalSection (&lock
->lock
);
287 glwthread_rwlock_trywrlock (glwthread_rwlock_t
*lock
)
289 if (!lock
->guard
.done
)
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
);
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
)
304 /* It's OK to wait for this critical section, because it is never taken for a
306 EnterCriticalSection (&lock
->lock
);
307 /* Test whether no readers or writers are currently running. */
308 if (!(lock
->runcount
== 0))
310 /* This thread would have to wait for a while. Return instead. */
311 LeaveCriticalSection (&lock
->lock
);
314 lock
->runcount
--; /* runcount becomes -1 */
315 LeaveCriticalSection (&lock
->lock
);
320 glwthread_rwlock_unlock (glwthread_rwlock_t
*lock
)
322 if (!lock
->guard
.done
)
324 EnterCriticalSection (&lock
->lock
);
325 if (lock
->runcount
< 0)
327 /* Drop a writer lock. */
328 if (!(lock
->runcount
== -1))
334 /* Drop a reader lock. */
335 if (!(lock
->runcount
> 0))
337 LeaveCriticalSection (&lock
->lock
);
342 if (lock
->runcount
== 0)
344 /* POSIX recommends that "write locks shall take precedence over read
345 locks", to avoid "writer starvation". */
346 if (lock
->waiting_writers
.count
> 0)
348 /* Wake up one of the waiting writers. */
350 glwthread_waitqueue_notify_first (&lock
->waiting_writers
);
354 /* Wake up all waiting readers. */
355 lock
->runcount
+= lock
->waiting_readers
.count
;
356 glwthread_waitqueue_notify_all (&lock
->waiting_readers
);
359 LeaveCriticalSection (&lock
->lock
);
364 glwthread_rwlock_destroy (glwthread_rwlock_t
*lock
)
366 if (!lock
->guard
.done
)
368 if (lock
->runcount
!= 0)
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;