fopen-gnu: New module.
[gnulib.git] / lib / windows-timedrwlock.c
blob793e8b76b2eb4a065f5b08a76101d1a1dc09476d
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)
7 any later version.
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. */
19 #include <config.h>
21 /* Specification. */
22 #include "windows-timedrwlock.h"
24 #include <errno.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <sys/time.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
33 a payload. */
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. */
41 static void
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;
46 wq->count = 0;
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;
55 HANDLE event;
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. */
60 elt =
61 (struct glwthread_waitqueue_element *)
62 malloc (sizeof (struct glwthread_waitqueue_element));
63 if (elt == NULL)
64 /* No more memory. */
65 return NULL;
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. */
73 free (elt);
74 return NULL;
76 elt->event = 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;
80 wq->count++;
81 return elt;
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. */
87 static bool
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;
100 wq->count--;
101 return true;
103 else
104 return false;
107 /* Notifies the first thread from a wait queue and dequeues it. */
108 static void
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;
125 wq->count--;
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. */
134 static void
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;
153 wq->count--;
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). */
159 l = next;
161 if (!(wq->wq_list.wql_next == &wq->wq_list
162 && wq->wq_list.wql_prev == &wq->wq_list
163 && wq->count == 0))
164 abort ();
167 void
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);
173 lock->runcount = 0;
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);
185 else
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)
192 Sleep (0);
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
203 waiting_readers. */
204 struct glwthread_waitqueue_element *elt =
205 glwthread_waitqueue_add (&lock->waiting_readers);
206 if (elt != NULL)
208 HANDLE event = elt->event;
209 DWORD result;
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)
214 abort ();
215 CloseHandle (event);
216 free (elt);
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))
220 abort ();
221 return 0;
223 else
225 /* Allocation failure. Weird. */
228 LeaveCriticalSection (&lock->lock);
229 Sleep (1);
230 EnterCriticalSection (&lock->lock);
232 while (!(lock->runcount + 1 > 0));
235 lock->runcount++;
236 LeaveCriticalSection (&lock->lock);
237 return 0;
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);
248 else
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)
255 Sleep (0);
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
263 waiting_writers. */
264 struct glwthread_waitqueue_element *elt =
265 glwthread_waitqueue_add (&lock->waiting_writers);
266 if (elt != NULL)
268 HANDLE event = elt->event;
269 DWORD result;
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)
274 abort ();
275 CloseHandle (event);
276 free (elt);
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))
280 abort ();
281 return 0;
283 else
285 /* Allocation failure. Weird. */
288 LeaveCriticalSection (&lock->lock);
289 Sleep (1);
290 EnterCriticalSection (&lock->lock);
292 while (!(lock->runcount == 0));
295 lock->runcount--; /* runcount becomes -1 */
296 LeaveCriticalSection (&lock->lock);
297 return 0;
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);
308 else
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)
315 Sleep (0);
318 /* It's OK to wait for this critical section, because it is never taken for a
319 long time. */
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);
329 return EBUSY;
331 lock->runcount++;
332 LeaveCriticalSection (&lock->lock);
333 return 0;
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);
344 else
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)
351 Sleep (0);
354 /* It's OK to wait for this critical section, because it is never taken for a
355 long time. */
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);
362 return EBUSY;
364 lock->runcount--; /* runcount becomes -1 */
365 LeaveCriticalSection (&lock->lock);
366 return 0;
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);
378 else
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)
385 Sleep (0);
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
396 waiting_readers. */
397 struct glwthread_waitqueue_element *elt =
398 glwthread_waitqueue_add (&lock->waiting_readers);
399 if (elt != NULL)
401 HANDLE event = elt->event;
402 struct timeval currtime;
403 DWORD timeout;
404 DWORD result;
405 int retval;
407 LeaveCriticalSection (&lock->lock);
409 gettimeofday (&currtime, NULL);
411 /* Wait until another thread signals this event or until the
412 abstime passes. */
413 if (currtime.tv_sec > abstime->tv_sec)
414 timeout = 0;
415 else
417 unsigned long seconds = abstime->tv_sec - currtime.tv_sec;
418 timeout = seconds * 1000;
419 if (timeout / 1000 != seconds) /* overflow? */
420 timeout = INFINITE;
421 else
423 long milliseconds =
424 abstime->tv_nsec / 1000000 - currtime.tv_usec / 1000;
425 if (milliseconds >= 0)
427 timeout += milliseconds;
428 if (timeout < milliseconds) /* overflow? */
429 timeout = INFINITE;
431 else
433 if (timeout >= - milliseconds)
434 timeout -= (- milliseconds);
435 else
436 timeout = 0;
440 if (timeout != 0)
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)
446 abort ();
447 if (result != WAIT_TIMEOUT)
449 CloseHandle (event);
450 free (elt);
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))
455 abort ();
456 return 0;
459 EnterCriticalSection (&lock->lock);
460 /* Remove ourselves from the waiting_readers. */
461 if (glwthread_waitqueue_remove (&lock->waiting_readers, elt))
462 retval = ETIMEDOUT;
463 else
464 /* The event was signalled just now. */
465 retval = 0;
466 LeaveCriticalSection (&lock->lock);
467 CloseHandle (event);
468 free (elt);
469 if (retval == 0)
470 /* Same assertion as above. */
471 if (!(lock->runcount > 0))
472 abort ();
473 return retval;
475 else
477 /* Allocation failure. Weird. */
480 LeaveCriticalSection (&lock->lock);
481 Sleep (1);
482 EnterCriticalSection (&lock->lock);
484 while (!(lock->runcount + 1 > 0));
487 lock->runcount++;
488 LeaveCriticalSection (&lock->lock);
489 return 0;
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);
501 else
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)
508 Sleep (0);
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
516 waiting_writers. */
517 struct glwthread_waitqueue_element *elt =
518 glwthread_waitqueue_add (&lock->waiting_writers);
519 if (elt != NULL)
521 HANDLE event = elt->event;
522 struct timeval currtime;
523 DWORD timeout;
524 DWORD result;
525 int retval;
527 LeaveCriticalSection (&lock->lock);
529 gettimeofday (&currtime, NULL);
531 /* Wait until another thread signals this event or until the
532 abstime passes. */
533 if (currtime.tv_sec > abstime->tv_sec)
534 timeout = 0;
535 else
537 unsigned long seconds = abstime->tv_sec - currtime.tv_sec;
538 timeout = seconds * 1000;
539 if (timeout / 1000 != seconds) /* overflow? */
540 timeout = INFINITE;
541 else
543 long milliseconds =
544 abstime->tv_nsec / 1000000 - currtime.tv_usec / 1000;
545 if (milliseconds >= 0)
547 timeout += milliseconds;
548 if (timeout < milliseconds) /* overflow? */
549 timeout = INFINITE;
551 else
553 if (timeout >= - milliseconds)
554 timeout -= (- milliseconds);
555 else
556 timeout = 0;
560 if (timeout != 0)
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)
566 abort ();
567 if (result != WAIT_TIMEOUT)
569 CloseHandle (event);
570 free (elt);
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))
575 abort ();
576 return 0;
579 EnterCriticalSection (&lock->lock);
580 /* Remove ourselves from the waiting_writers. */
581 if (glwthread_waitqueue_remove (&lock->waiting_writers, elt))
582 retval = ETIMEDOUT;
583 else
584 /* The event was signalled just now. */
585 retval = 0;
586 LeaveCriticalSection (&lock->lock);
587 CloseHandle (event);
588 free (elt);
589 if (retval == 0)
590 /* Same assertion as above. */
591 if (!(lock->runcount == -1))
592 abort ();
593 return retval;
595 else
597 /* Allocation failure. Weird. */
600 LeaveCriticalSection (&lock->lock);
601 Sleep (1);
602 EnterCriticalSection (&lock->lock);
604 while (!(lock->runcount == 0));
607 lock->runcount--; /* runcount becomes -1 */
608 LeaveCriticalSection (&lock->lock);
609 return 0;
613 glwthread_timedrwlock_unlock (glwthread_timedrwlock_t *lock)
615 if (!lock->guard.done)
616 return EINVAL;
617 EnterCriticalSection (&lock->lock);
618 if (lock->runcount < 0)
620 /* Drop a writer lock. */
621 if (!(lock->runcount == -1))
622 abort ();
623 lock->runcount = 0;
625 else
627 /* Drop a reader lock. */
628 if (!(lock->runcount > 0))
630 LeaveCriticalSection (&lock->lock);
631 return EPERM;
633 lock->runcount--;
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. */
642 lock->runcount--;
643 glwthread_waitqueue_notify_first (&lock->waiting_writers);
645 else
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);
653 return 0;
657 glwthread_timedrwlock_destroy (glwthread_timedrwlock_t *lock)
659 if (!lock->guard.done)
660 return EINVAL;
661 if (lock->runcount != 0)
662 return EBUSY;
663 DeleteCriticalSection (&lock->lock);
664 lock->guard.done = 0;
665 return 0;