1 /* Timed read-write locks (native Windows implementation).
2 Copyright (C) 2005-2020 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
9 This program 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 General Public License for more details.
14 You should have received a copy of the GNU 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"
29 /* In this file, the waitqueues are implemented as linked lists. */
30 #define glwthread_waitqueue_t glwthread_clinked_waitqueue_t
32 /* All links of a circular list, except the anchor, are of this type, carrying
34 struct glwthread_waitqueue_element
36 struct glwthread_waitqueue_link link
; /* must be the first field! */
37 HANDLE event
; /* Waiting thread, represented by an event.
38 This field is immutable once initialized. */
42 glwthread_waitqueue_init (glwthread_waitqueue_t
*wq
)
44 wq
->wq_list
.wql_next
= &wq
->wq_list
;
45 wq
->wq_list
.wql_prev
= &wq
->wq_list
;
49 /* Enqueues the current thread, represented by an event, in a wait queue.
50 Returns NULL if an allocation failure occurs. */
51 static struct glwthread_waitqueue_element
*
52 glwthread_waitqueue_add (glwthread_waitqueue_t
*wq
)
54 struct glwthread_waitqueue_element
*elt
;
57 /* Allocate the memory for the waitqueue element on the heap, not on the
58 thread's stack. If the thread exits unexpectedly, we prefer to leak
59 some memory rather than to access unavailable memory and crash. */
61 (struct glwthread_waitqueue_element
*)
62 malloc (sizeof (struct glwthread_waitqueue_element
));
67 /* Whether the created event is a manual-reset one or an auto-reset one,
68 does not matter, since we will wait on it only once. */
69 event
= CreateEvent (NULL
, TRUE
, FALSE
, NULL
);
70 if (event
== INVALID_HANDLE_VALUE
)
72 /* No way to allocate an event. */
77 /* Insert elt at the end of the circular list. */
78 (elt
->link
.wql_prev
= wq
->wq_list
.wql_prev
)->wql_next
= &elt
->link
;
79 (elt
->link
.wql_next
= &wq
->wq_list
)->wql_prev
= &elt
->link
;
84 /* Removes the current thread, represented by a
85 'struct glwthread_waitqueue_element *', from a wait queue.
86 Returns true if is was found and removed, false if it was not present. */
88 glwthread_waitqueue_remove (glwthread_waitqueue_t
*wq
,
89 struct glwthread_waitqueue_element
*elt
)
91 if (elt
->link
.wql_next
!= NULL
&& elt
->link
.wql_prev
!= NULL
)
93 /* Remove elt from the circular list. */
94 struct glwthread_waitqueue_link
*prev
= elt
->link
.wql_prev
;
95 struct glwthread_waitqueue_link
*next
= elt
->link
.wql_next
;
96 prev
->wql_next
= next
;
97 next
->wql_prev
= prev
;
98 elt
->link
.wql_next
= NULL
;
99 elt
->link
.wql_prev
= NULL
;
107 /* Notifies the first thread from a wait queue and dequeues it. */
109 glwthread_waitqueue_notify_first (glwthread_waitqueue_t
*wq
)
111 if (wq
->wq_list
.wql_next
!= &wq
->wq_list
)
113 struct glwthread_waitqueue_element
*elt
=
114 (struct glwthread_waitqueue_element
*) wq
->wq_list
.wql_next
;
115 struct glwthread_waitqueue_link
*prev
;
116 struct glwthread_waitqueue_link
*next
;
118 /* Remove elt from the circular list. */
119 prev
= &wq
->wq_list
; /* = elt->link.wql_prev; */
120 next
= elt
->link
.wql_next
;
121 prev
->wql_next
= next
;
122 next
->wql_prev
= prev
;
123 elt
->link
.wql_next
= NULL
;
124 elt
->link
.wql_prev
= NULL
;
127 SetEvent (elt
->event
);
128 /* After the SetEvent, this thread cannot access *elt any more, because
129 the woken-up thread will quickly call free (elt). */
133 /* Notifies all threads from a wait queue and dequeues them all. */
135 glwthread_waitqueue_notify_all (glwthread_waitqueue_t
*wq
)
137 struct glwthread_waitqueue_link
*l
;
139 for (l
= wq
->wq_list
.wql_next
; l
!= &wq
->wq_list
; )
141 struct glwthread_waitqueue_element
*elt
=
142 (struct glwthread_waitqueue_element
*) l
;
143 struct glwthread_waitqueue_link
*prev
;
144 struct glwthread_waitqueue_link
*next
;
146 /* Remove elt from the circular list. */
147 prev
= &wq
->wq_list
; /* = elt->link.wql_prev; */
148 next
= elt
->link
.wql_next
;
149 prev
->wql_next
= next
;
150 next
->wql_prev
= prev
;
151 elt
->link
.wql_next
= NULL
;
152 elt
->link
.wql_prev
= NULL
;
155 SetEvent (elt
->event
);
156 /* After the SetEvent, this thread cannot access *elt any more, because
157 the woken-up thread will quickly call free (elt). */
161 if (!(wq
->wq_list
.wql_next
== &wq
->wq_list
162 && wq
->wq_list
.wql_prev
== &wq
->wq_list
168 glwthread_timedrwlock_init (glwthread_timedrwlock_t
*lock
)
170 InitializeCriticalSection (&lock
->lock
);
171 glwthread_waitqueue_init (&lock
->waiting_readers
);
172 glwthread_waitqueue_init (&lock
->waiting_writers
);
174 lock
->guard
.done
= 1;
178 glwthread_timedrwlock_rdlock (glwthread_timedrwlock_t
*lock
)
180 if (!lock
->guard
.done
)
182 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
183 /* This thread is the first one to need this lock. Initialize it. */
184 glwthread_timedrwlock_init (lock
);
187 /* Don't let lock->guard.started grow and wrap around. */
188 InterlockedDecrement (&lock
->guard
.started
);
189 /* Yield the CPU while waiting for another thread to finish
190 initializing this lock. */
191 while (!lock
->guard
.done
)
195 EnterCriticalSection (&lock
->lock
);
196 /* Test whether only readers are currently running, and whether the runcount
197 field will not overflow, and whether no writer is waiting. The latter
198 condition is because POSIX recommends that "write locks shall take
199 precedence over read locks", to avoid "writer starvation". */
200 if (!(lock
->runcount
+ 1 > 0 && lock
->waiting_writers
.count
== 0))
202 /* This thread has to wait for a while. Enqueue it among the
204 struct glwthread_waitqueue_element
*elt
=
205 glwthread_waitqueue_add (&lock
->waiting_readers
);
208 HANDLE event
= elt
->event
;
210 LeaveCriticalSection (&lock
->lock
);
211 /* Wait until another thread signals this event. */
212 result
= WaitForSingleObject (event
, INFINITE
);
213 if (result
== WAIT_FAILED
|| result
== WAIT_TIMEOUT
)
217 /* The thread which signalled the event already did the bookkeeping:
218 removed us from the waiting_readers, incremented lock->runcount. */
219 if (!(lock
->runcount
> 0))
225 /* Allocation failure. Weird. */
228 LeaveCriticalSection (&lock
->lock
);
230 EnterCriticalSection (&lock
->lock
);
232 while (!(lock
->runcount
+ 1 > 0));
236 LeaveCriticalSection (&lock
->lock
);
241 glwthread_timedrwlock_wrlock (glwthread_timedrwlock_t
*lock
)
243 if (!lock
->guard
.done
)
245 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
246 /* This thread is the first one to need this lock. Initialize it. */
247 glwthread_timedrwlock_init (lock
);
250 /* Don't let lock->guard.started grow and wrap around. */
251 InterlockedDecrement (&lock
->guard
.started
);
252 /* Yield the CPU while waiting for another thread to finish
253 initializing this lock. */
254 while (!lock
->guard
.done
)
258 EnterCriticalSection (&lock
->lock
);
259 /* Test whether no readers or writers are currently running. */
260 if (!(lock
->runcount
== 0))
262 /* This thread has to wait for a while. Enqueue it among the
264 struct glwthread_waitqueue_element
*elt
=
265 glwthread_waitqueue_add (&lock
->waiting_writers
);
268 HANDLE event
= elt
->event
;
270 LeaveCriticalSection (&lock
->lock
);
271 /* Wait until another thread signals this event. */
272 result
= WaitForSingleObject (event
, INFINITE
);
273 if (result
== WAIT_FAILED
|| result
== WAIT_TIMEOUT
)
277 /* The thread which signalled the event already did the bookkeeping:
278 removed us from the waiting_writers, set lock->runcount = -1. */
279 if (!(lock
->runcount
== -1))
285 /* Allocation failure. Weird. */
288 LeaveCriticalSection (&lock
->lock
);
290 EnterCriticalSection (&lock
->lock
);
292 while (!(lock
->runcount
== 0));
295 lock
->runcount
--; /* runcount becomes -1 */
296 LeaveCriticalSection (&lock
->lock
);
301 glwthread_timedrwlock_tryrdlock (glwthread_timedrwlock_t
*lock
)
303 if (!lock
->guard
.done
)
305 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
306 /* This thread is the first one to need this lock. Initialize it. */
307 glwthread_timedrwlock_init (lock
);
310 /* Don't let lock->guard.started grow and wrap around. */
311 InterlockedDecrement (&lock
->guard
.started
);
312 /* Yield the CPU while waiting for another thread to finish
313 initializing this lock. */
314 while (!lock
->guard
.done
)
318 /* It's OK to wait for this critical section, because it is never taken for a
320 EnterCriticalSection (&lock
->lock
);
321 /* Test whether only readers are currently running, and whether the runcount
322 field will not overflow, and whether no writer is waiting. The latter
323 condition is because POSIX recommends that "write locks shall take
324 precedence over read locks", to avoid "writer starvation". */
325 if (!(lock
->runcount
+ 1 > 0 && lock
->waiting_writers
.count
== 0))
327 /* This thread would have to wait for a while. Return instead. */
328 LeaveCriticalSection (&lock
->lock
);
332 LeaveCriticalSection (&lock
->lock
);
337 glwthread_timedrwlock_trywrlock (glwthread_timedrwlock_t
*lock
)
339 if (!lock
->guard
.done
)
341 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
342 /* This thread is the first one to need this lock. Initialize it. */
343 glwthread_timedrwlock_init (lock
);
346 /* Don't let lock->guard.started grow and wrap around. */
347 InterlockedDecrement (&lock
->guard
.started
);
348 /* Yield the CPU while waiting for another thread to finish
349 initializing this lock. */
350 while (!lock
->guard
.done
)
354 /* It's OK to wait for this critical section, because it is never taken for a
356 EnterCriticalSection (&lock
->lock
);
357 /* Test whether no readers or writers are currently running. */
358 if (!(lock
->runcount
== 0))
360 /* This thread would have to wait for a while. Return instead. */
361 LeaveCriticalSection (&lock
->lock
);
364 lock
->runcount
--; /* runcount becomes -1 */
365 LeaveCriticalSection (&lock
->lock
);
370 glwthread_timedrwlock_timedrdlock (glwthread_timedrwlock_t
*lock
,
371 const struct timespec
*abstime
)
373 if (!lock
->guard
.done
)
375 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
376 /* This thread is the first one to need this lock. Initialize it. */
377 glwthread_timedrwlock_init (lock
);
380 /* Don't let lock->guard.started grow and wrap around. */
381 InterlockedDecrement (&lock
->guard
.started
);
382 /* Yield the CPU while waiting for another thread to finish
383 initializing this lock. */
384 while (!lock
->guard
.done
)
388 EnterCriticalSection (&lock
->lock
);
389 /* Test whether only readers are currently running, and whether the runcount
390 field will not overflow, and whether no writer is waiting. The latter
391 condition is because POSIX recommends that "write locks shall take
392 precedence over read locks", to avoid "writer starvation". */
393 if (!(lock
->runcount
+ 1 > 0 && lock
->waiting_writers
.count
== 0))
395 /* This thread has to wait for a while. Enqueue it among the
397 struct glwthread_waitqueue_element
*elt
=
398 glwthread_waitqueue_add (&lock
->waiting_readers
);
401 HANDLE event
= elt
->event
;
402 struct timeval currtime
;
407 LeaveCriticalSection (&lock
->lock
);
409 gettimeofday (&currtime
, NULL
);
411 /* Wait until another thread signals this event or until the
413 if (currtime
.tv_sec
> abstime
->tv_sec
)
417 unsigned long seconds
= abstime
->tv_sec
- currtime
.tv_sec
;
418 timeout
= seconds
* 1000;
419 if (timeout
/ 1000 != seconds
) /* overflow? */
424 abstime
->tv_nsec
/ 1000000 - currtime
.tv_usec
/ 1000;
425 if (milliseconds
>= 0)
427 timeout
+= milliseconds
;
428 if (timeout
< milliseconds
) /* overflow? */
433 if (timeout
>= - milliseconds
)
434 timeout
-= (- milliseconds
);
442 /* WaitForSingleObject
443 <https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject> */
444 result
= WaitForSingleObject (event
, timeout
);
445 if (result
== WAIT_FAILED
)
447 if (result
!= WAIT_TIMEOUT
)
451 /* The thread which signalled the event already did the
452 bookkeeping: removed us from the waiting_readers,
453 incremented lock->runcount. */
454 if (!(lock
->runcount
> 0))
459 EnterCriticalSection (&lock
->lock
);
460 /* Remove ourselves from the waiting_readers. */
461 if (glwthread_waitqueue_remove (&lock
->waiting_readers
, elt
))
464 /* The event was signalled just now. */
466 LeaveCriticalSection (&lock
->lock
);
470 /* Same assertion as above. */
471 if (!(lock
->runcount
> 0))
477 /* Allocation failure. Weird. */
480 LeaveCriticalSection (&lock
->lock
);
482 EnterCriticalSection (&lock
->lock
);
484 while (!(lock
->runcount
+ 1 > 0));
488 LeaveCriticalSection (&lock
->lock
);
493 glwthread_timedrwlock_timedwrlock (glwthread_timedrwlock_t
*lock
,
494 const struct timespec
*abstime
)
496 if (!lock
->guard
.done
)
498 if (InterlockedIncrement (&lock
->guard
.started
) == 0)
499 /* This thread is the first one to need this lock. Initialize it. */
500 glwthread_timedrwlock_init (lock
);
503 /* Don't let lock->guard.started grow and wrap around. */
504 InterlockedDecrement (&lock
->guard
.started
);
505 /* Yield the CPU while waiting for another thread to finish
506 initializing this lock. */
507 while (!lock
->guard
.done
)
511 EnterCriticalSection (&lock
->lock
);
512 /* Test whether no readers or writers are currently running. */
513 if (!(lock
->runcount
== 0))
515 /* This thread has to wait for a while. Enqueue it among the
517 struct glwthread_waitqueue_element
*elt
=
518 glwthread_waitqueue_add (&lock
->waiting_writers
);
521 HANDLE event
= elt
->event
;
522 struct timeval currtime
;
527 LeaveCriticalSection (&lock
->lock
);
529 gettimeofday (&currtime
, NULL
);
531 /* Wait until another thread signals this event or until the
533 if (currtime
.tv_sec
> abstime
->tv_sec
)
537 unsigned long seconds
= abstime
->tv_sec
- currtime
.tv_sec
;
538 timeout
= seconds
* 1000;
539 if (timeout
/ 1000 != seconds
) /* overflow? */
544 abstime
->tv_nsec
/ 1000000 - currtime
.tv_usec
/ 1000;
545 if (milliseconds
>= 0)
547 timeout
+= milliseconds
;
548 if (timeout
< milliseconds
) /* overflow? */
553 if (timeout
>= - milliseconds
)
554 timeout
-= (- milliseconds
);
562 /* WaitForSingleObject
563 <https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject> */
564 result
= WaitForSingleObject (event
, timeout
);
565 if (result
== WAIT_FAILED
)
567 if (result
!= WAIT_TIMEOUT
)
571 /* The thread which signalled the event already did the
572 bookkeeping: removed us from the waiting_writers, set
573 lock->runcount = -1. */
574 if (!(lock
->runcount
== -1))
579 EnterCriticalSection (&lock
->lock
);
580 /* Remove ourselves from the waiting_writers. */
581 if (glwthread_waitqueue_remove (&lock
->waiting_writers
, elt
))
584 /* The event was signalled just now. */
586 LeaveCriticalSection (&lock
->lock
);
590 /* Same assertion as above. */
591 if (!(lock
->runcount
== -1))
597 /* Allocation failure. Weird. */
600 LeaveCriticalSection (&lock
->lock
);
602 EnterCriticalSection (&lock
->lock
);
604 while (!(lock
->runcount
== 0));
607 lock
->runcount
--; /* runcount becomes -1 */
608 LeaveCriticalSection (&lock
->lock
);
613 glwthread_timedrwlock_unlock (glwthread_timedrwlock_t
*lock
)
615 if (!lock
->guard
.done
)
617 EnterCriticalSection (&lock
->lock
);
618 if (lock
->runcount
< 0)
620 /* Drop a writer lock. */
621 if (!(lock
->runcount
== -1))
627 /* Drop a reader lock. */
628 if (!(lock
->runcount
> 0))
630 LeaveCriticalSection (&lock
->lock
);
635 if (lock
->runcount
== 0)
637 /* POSIX recommends that "write locks shall take precedence over read
638 locks", to avoid "writer starvation". */
639 if (lock
->waiting_writers
.count
> 0)
641 /* Wake up one of the waiting writers. */
643 glwthread_waitqueue_notify_first (&lock
->waiting_writers
);
647 /* Wake up all waiting readers. */
648 lock
->runcount
+= lock
->waiting_readers
.count
;
649 glwthread_waitqueue_notify_all (&lock
->waiting_readers
);
652 LeaveCriticalSection (&lock
->lock
);
657 glwthread_timedrwlock_destroy (glwthread_timedrwlock_t
*lock
)
659 if (!lock
->guard
.done
)
661 if (lock
->runcount
!= 0)
663 DeleteCriticalSection (&lock
->lock
);
664 lock
->guard
.done
= 0;