1 /* Timed 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>, 2019. */
22 #include "windows-timedrwlock.h"
28 /* Don't assume that UNICODE is not defined. */
30 #define CreateEvent CreateEventA
32 /* In this file, the waitqueues are implemented as linked lists. */
33 #define glwthread_waitqueue_t glwthread_clinked_waitqueue_t
35 /* All links of a circular list, except the anchor, are of this type, carrying
37 struct glwthread_waitqueue_element
39 struct glwthread_waitqueue_link link
; /* must be the first field! */
40 HANDLE event
; /* Waiting thread, represented by an event.
41 This field is immutable once initialized. */
45 glwthread_waitqueue_init (glwthread_waitqueue_t
*wq
)
47 wq
->wq_list
.wql_next
= &wq
->wq_list
;
48 wq
->wq_list
.wql_prev
= &wq
->wq_list
;
52 /* Enqueues the current thread, represented by an event, in a wait queue.
53 Returns NULL if an allocation failure occurs. */
54 static struct glwthread_waitqueue_element
*
55 glwthread_waitqueue_add (glwthread_waitqueue_t
*wq
)
57 struct glwthread_waitqueue_element
*elt
;
60 /* Allocate the memory for the waitqueue element on the heap, not on the
61 thread's stack. If the thread exits unexpectedly, we prefer to leak
62 some memory rather than to access unavailable memory and crash. */
64 (struct glwthread_waitqueue_element
*)
65 malloc (sizeof (struct glwthread_waitqueue_element
));
70 /* Whether the created event is a manual-reset one or an auto-reset one,
71 does not matter, since we will wait on it only once. */
72 event
= CreateEvent (NULL
, TRUE
, FALSE
, NULL
);
73 if (event
== INVALID_HANDLE_VALUE
)
75 /* No way to allocate an event. */
80 /* Insert elt at the end of the circular list. */
81 (elt
->link
.wql_prev
= wq
->wq_list
.wql_prev
)->wql_next
= &elt
->link
;
82 (elt
->link
.wql_next
= &wq
->wq_list
)->wql_prev
= &elt
->link
;
87 /* Removes the current thread, represented by a
88 'struct glwthread_waitqueue_element *', from a wait queue.
89 Returns true if is was found and removed, false if it was not present. */
91 glwthread_waitqueue_remove (glwthread_waitqueue_t
*wq
,
92 struct glwthread_waitqueue_element
*elt
)
94 if (elt
->link
.wql_next
!= NULL
&& elt
->link
.wql_prev
!= NULL
)
96 /* Remove elt from the circular list. */
97 struct glwthread_waitqueue_link
*prev
= elt
->link
.wql_prev
;
98 struct glwthread_waitqueue_link
*next
= elt
->link
.wql_next
;
99 prev
->wql_next
= next
;
100 next
->wql_prev
= prev
;
101 elt
->link
.wql_next
= NULL
;
102 elt
->link
.wql_prev
= NULL
;
110 /* Notifies the first thread from a wait queue and dequeues it. */
112 glwthread_waitqueue_notify_first (glwthread_waitqueue_t
*wq
)
114 if (wq
->wq_list
.wql_next
!= &wq
->wq_list
)
116 struct glwthread_waitqueue_element
*elt
=
117 (struct glwthread_waitqueue_element
*) wq
->wq_list
.wql_next
;
118 struct glwthread_waitqueue_link
*prev
;
119 struct glwthread_waitqueue_link
*next
;
121 /* Remove elt from the circular list. */
122 prev
= &wq
->wq_list
; /* = elt->link.wql_prev; */
123 next
= elt
->link
.wql_next
;
124 prev
->wql_next
= next
;
125 next
->wql_prev
= prev
;
126 elt
->link
.wql_next
= NULL
;
127 elt
->link
.wql_prev
= NULL
;
130 SetEvent (elt
->event
);
131 /* After the SetEvent, this thread cannot access *elt any more, because
132 the woken-up thread will quickly call free (elt). */
136 /* Notifies all threads from a wait queue and dequeues them all. */
138 glwthread_waitqueue_notify_all (glwthread_waitqueue_t
*wq
)
140 struct glwthread_waitqueue_link
*l
;
142 for (l
= wq
->wq_list
.wql_next
; l
!= &wq
->wq_list
; )
144 struct glwthread_waitqueue_element
*elt
=
145 (struct glwthread_waitqueue_element
*) l
;
146 struct glwthread_waitqueue_link
*prev
;
147 struct glwthread_waitqueue_link
*next
;
149 /* Remove elt from the circular list. */
150 prev
= &wq
->wq_list
; /* = elt->link.wql_prev; */
151 next
= elt
->link
.wql_next
;
152 prev
->wql_next
= next
;
153 next
->wql_prev
= prev
;
154 elt
->link
.wql_next
= NULL
;
155 elt
->link
.wql_prev
= NULL
;
158 SetEvent (elt
->event
);
159 /* After the SetEvent, this thread cannot access *elt any more, because
160 the woken-up thread will quickly call free (elt). */
164 if (!(wq
->wq_list
.wql_next
== &wq
->wq_list
165 && wq
->wq_list
.wql_prev
== &wq
->wq_list
171 glwthread_timedrwlock_init (glwthread_timedrwlock_t
*lock
)
173 InitializeCriticalSection (&lock
->lock
);
174 glwthread_waitqueue_init (&lock
->waiting_readers
);
175 glwthread_waitqueue_init (&lock
->waiting_writers
);
177 lock
->guard
.done
= 1;
181 glwthread_timedrwlock_rdlock (glwthread_timedrwlock_t
*lock
)
183 if (!lock
->guard
.done
)
185 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
186 /* This thread is the first one to need this lock. Initialize it. */
187 glwthread_timedrwlock_init (lock
);
190 /* Don't let lock->guard.started grow and wrap around. */
191 InterlockedDecrement (&lock
->guard
.started
);
192 /* Yield the CPU while waiting for another thread to finish
193 initializing this lock. */
194 while (!lock
->guard
.done
)
198 EnterCriticalSection (&lock
->lock
);
199 /* Test whether only readers are currently running, and whether the runcount
200 field will not overflow, and whether no writer is waiting. The latter
201 condition is because POSIX recommends that "write locks shall take
202 precedence over read locks", to avoid "writer starvation". */
203 if (!(lock
->runcount
+ 1 > 0 && lock
->waiting_writers
.count
== 0))
205 /* This thread has to wait for a while. Enqueue it among the
207 struct glwthread_waitqueue_element
*elt
=
208 glwthread_waitqueue_add (&lock
->waiting_readers
);
211 HANDLE event
= elt
->event
;
213 LeaveCriticalSection (&lock
->lock
);
214 /* Wait until another thread signals this event. */
215 result
= WaitForSingleObject (event
, INFINITE
);
216 if (result
== WAIT_FAILED
|| result
== WAIT_TIMEOUT
)
220 /* The thread which signalled the event already did the bookkeeping:
221 removed us from the waiting_readers, incremented lock->runcount. */
222 if (!(lock
->runcount
> 0))
228 /* Allocation failure. Weird. */
231 LeaveCriticalSection (&lock
->lock
);
233 EnterCriticalSection (&lock
->lock
);
235 while (!(lock
->runcount
+ 1 > 0));
239 LeaveCriticalSection (&lock
->lock
);
244 glwthread_timedrwlock_wrlock (glwthread_timedrwlock_t
*lock
)
246 if (!lock
->guard
.done
)
248 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
249 /* This thread is the first one to need this lock. Initialize it. */
250 glwthread_timedrwlock_init (lock
);
253 /* Don't let lock->guard.started grow and wrap around. */
254 InterlockedDecrement (&lock
->guard
.started
);
255 /* Yield the CPU while waiting for another thread to finish
256 initializing this lock. */
257 while (!lock
->guard
.done
)
261 EnterCriticalSection (&lock
->lock
);
262 /* Test whether no readers or writers are currently running. */
263 if (!(lock
->runcount
== 0))
265 /* This thread has to wait for a while. Enqueue it among the
267 struct glwthread_waitqueue_element
*elt
=
268 glwthread_waitqueue_add (&lock
->waiting_writers
);
271 HANDLE event
= elt
->event
;
273 LeaveCriticalSection (&lock
->lock
);
274 /* Wait until another thread signals this event. */
275 result
= WaitForSingleObject (event
, INFINITE
);
276 if (result
== WAIT_FAILED
|| result
== WAIT_TIMEOUT
)
280 /* The thread which signalled the event already did the bookkeeping:
281 removed us from the waiting_writers, set lock->runcount = -1. */
282 if (!(lock
->runcount
== -1))
288 /* Allocation failure. Weird. */
291 LeaveCriticalSection (&lock
->lock
);
293 EnterCriticalSection (&lock
->lock
);
295 while (!(lock
->runcount
== 0));
298 lock
->runcount
--; /* runcount becomes -1 */
299 LeaveCriticalSection (&lock
->lock
);
304 glwthread_timedrwlock_tryrdlock (glwthread_timedrwlock_t
*lock
)
306 if (!lock
->guard
.done
)
308 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
309 /* This thread is the first one to need this lock. Initialize it. */
310 glwthread_timedrwlock_init (lock
);
313 /* Don't let lock->guard.started grow and wrap around. */
314 InterlockedDecrement (&lock
->guard
.started
);
315 /* Yield the CPU while waiting for another thread to finish
316 initializing this lock. */
317 while (!lock
->guard
.done
)
321 /* It's OK to wait for this critical section, because it is never taken for a
323 EnterCriticalSection (&lock
->lock
);
324 /* Test whether only readers are currently running, and whether the runcount
325 field will not overflow, and whether no writer is waiting. The latter
326 condition is because POSIX recommends that "write locks shall take
327 precedence over read locks", to avoid "writer starvation". */
328 if (!(lock
->runcount
+ 1 > 0 && lock
->waiting_writers
.count
== 0))
330 /* This thread would have to wait for a while. Return instead. */
331 LeaveCriticalSection (&lock
->lock
);
335 LeaveCriticalSection (&lock
->lock
);
340 glwthread_timedrwlock_trywrlock (glwthread_timedrwlock_t
*lock
)
342 if (!lock
->guard
.done
)
344 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
345 /* This thread is the first one to need this lock. Initialize it. */
346 glwthread_timedrwlock_init (lock
);
349 /* Don't let lock->guard.started grow and wrap around. */
350 InterlockedDecrement (&lock
->guard
.started
);
351 /* Yield the CPU while waiting for another thread to finish
352 initializing this lock. */
353 while (!lock
->guard
.done
)
357 /* It's OK to wait for this critical section, because it is never taken for a
359 EnterCriticalSection (&lock
->lock
);
360 /* Test whether no readers or writers are currently running. */
361 if (!(lock
->runcount
== 0))
363 /* This thread would have to wait for a while. Return instead. */
364 LeaveCriticalSection (&lock
->lock
);
367 lock
->runcount
--; /* runcount becomes -1 */
368 LeaveCriticalSection (&lock
->lock
);
373 glwthread_timedrwlock_timedrdlock (glwthread_timedrwlock_t
*lock
,
374 const struct timespec
*abstime
)
376 if (!lock
->guard
.done
)
378 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
379 /* This thread is the first one to need this lock. Initialize it. */
380 glwthread_timedrwlock_init (lock
);
383 /* Don't let lock->guard.started grow and wrap around. */
384 InterlockedDecrement (&lock
->guard
.started
);
385 /* Yield the CPU while waiting for another thread to finish
386 initializing this lock. */
387 while (!lock
->guard
.done
)
391 EnterCriticalSection (&lock
->lock
);
392 /* Test whether only readers are currently running, and whether the runcount
393 field will not overflow, and whether no writer is waiting. The latter
394 condition is because POSIX recommends that "write locks shall take
395 precedence over read locks", to avoid "writer starvation". */
396 if (!(lock
->runcount
+ 1 > 0 && lock
->waiting_writers
.count
== 0))
398 /* This thread has to wait for a while. Enqueue it among the
400 struct glwthread_waitqueue_element
*elt
=
401 glwthread_waitqueue_add (&lock
->waiting_readers
);
404 HANDLE event
= elt
->event
;
405 struct timeval currtime
;
410 LeaveCriticalSection (&lock
->lock
);
412 gettimeofday (&currtime
, NULL
);
414 /* Wait until another thread signals this event or until the
416 if (currtime
.tv_sec
> abstime
->tv_sec
)
420 unsigned long seconds
= abstime
->tv_sec
- currtime
.tv_sec
;
421 timeout
= seconds
* 1000;
422 if (timeout
/ 1000 != seconds
) /* overflow? */
427 abstime
->tv_nsec
/ 1000000 - currtime
.tv_usec
/ 1000;
428 if (milliseconds
>= 0)
430 timeout
+= milliseconds
;
431 if (timeout
< milliseconds
) /* overflow? */
436 if (timeout
>= - milliseconds
)
437 timeout
-= (- milliseconds
);
445 /* WaitForSingleObject
446 <https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject> */
447 result
= WaitForSingleObject (event
, timeout
);
448 if (result
== WAIT_FAILED
)
450 if (result
!= WAIT_TIMEOUT
)
454 /* The thread which signalled the event already did the
455 bookkeeping: removed us from the waiting_readers,
456 incremented lock->runcount. */
457 if (!(lock
->runcount
> 0))
462 EnterCriticalSection (&lock
->lock
);
463 /* Remove ourselves from the waiting_readers. */
464 if (glwthread_waitqueue_remove (&lock
->waiting_readers
, elt
))
467 /* The event was signalled just now. */
469 LeaveCriticalSection (&lock
->lock
);
473 /* Same assertion as above. */
474 if (!(lock
->runcount
> 0))
480 /* Allocation failure. Weird. */
483 LeaveCriticalSection (&lock
->lock
);
485 EnterCriticalSection (&lock
->lock
);
487 while (!(lock
->runcount
+ 1 > 0));
491 LeaveCriticalSection (&lock
->lock
);
496 glwthread_timedrwlock_timedwrlock (glwthread_timedrwlock_t
*lock
,
497 const struct timespec
*abstime
)
499 if (!lock
->guard
.done
)
501 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
502 /* This thread is the first one to need this lock. Initialize it. */
503 glwthread_timedrwlock_init (lock
);
506 /* Don't let lock->guard.started grow and wrap around. */
507 InterlockedDecrement (&lock
->guard
.started
);
508 /* Yield the CPU while waiting for another thread to finish
509 initializing this lock. */
510 while (!lock
->guard
.done
)
514 EnterCriticalSection (&lock
->lock
);
515 /* Test whether no readers or writers are currently running. */
516 if (!(lock
->runcount
== 0))
518 /* This thread has to wait for a while. Enqueue it among the
520 struct glwthread_waitqueue_element
*elt
=
521 glwthread_waitqueue_add (&lock
->waiting_writers
);
524 HANDLE event
= elt
->event
;
525 struct timeval currtime
;
530 LeaveCriticalSection (&lock
->lock
);
532 gettimeofday (&currtime
, NULL
);
534 /* Wait until another thread signals this event or until the
536 if (currtime
.tv_sec
> abstime
->tv_sec
)
540 unsigned long seconds
= abstime
->tv_sec
- currtime
.tv_sec
;
541 timeout
= seconds
* 1000;
542 if (timeout
/ 1000 != seconds
) /* overflow? */
547 abstime
->tv_nsec
/ 1000000 - currtime
.tv_usec
/ 1000;
548 if (milliseconds
>= 0)
550 timeout
+= milliseconds
;
551 if (timeout
< milliseconds
) /* overflow? */
556 if (timeout
>= - milliseconds
)
557 timeout
-= (- milliseconds
);
565 /* WaitForSingleObject
566 <https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject> */
567 result
= WaitForSingleObject (event
, timeout
);
568 if (result
== WAIT_FAILED
)
570 if (result
!= WAIT_TIMEOUT
)
574 /* The thread which signalled the event already did the
575 bookkeeping: removed us from the waiting_writers, set
576 lock->runcount = -1. */
577 if (!(lock
->runcount
== -1))
582 EnterCriticalSection (&lock
->lock
);
583 /* Remove ourselves from the waiting_writers. */
584 if (glwthread_waitqueue_remove (&lock
->waiting_writers
, elt
))
587 /* The event was signalled just now. */
589 LeaveCriticalSection (&lock
->lock
);
593 /* Same assertion as above. */
594 if (!(lock
->runcount
== -1))
600 /* Allocation failure. Weird. */
603 LeaveCriticalSection (&lock
->lock
);
605 EnterCriticalSection (&lock
->lock
);
607 while (!(lock
->runcount
== 0));
610 lock
->runcount
--; /* runcount becomes -1 */
611 LeaveCriticalSection (&lock
->lock
);
616 glwthread_timedrwlock_unlock (glwthread_timedrwlock_t
*lock
)
618 if (!lock
->guard
.done
)
620 EnterCriticalSection (&lock
->lock
);
621 if (lock
->runcount
< 0)
623 /* Drop a writer lock. */
624 if (!(lock
->runcount
== -1))
630 /* Drop a reader lock. */
631 if (!(lock
->runcount
> 0))
633 LeaveCriticalSection (&lock
->lock
);
638 if (lock
->runcount
== 0)
640 /* POSIX recommends that "write locks shall take precedence over read
641 locks", to avoid "writer starvation". */
642 if (lock
->waiting_writers
.count
> 0)
644 /* Wake up one of the waiting writers. */
646 glwthread_waitqueue_notify_first (&lock
->waiting_writers
);
650 /* Wake up all waiting readers. */
651 lock
->runcount
+= lock
->waiting_readers
.count
;
652 glwthread_waitqueue_notify_all (&lock
->waiting_readers
);
655 LeaveCriticalSection (&lock
->lock
);
660 glwthread_timedrwlock_destroy (glwthread_timedrwlock_t
*lock
)
662 if (!lock
->guard
.done
)
664 if (lock
->runcount
!= 0)
666 DeleteCriticalSection (&lock
->lock
);
667 lock
->guard
.done
= 0;