exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / windows-cond.c
blob5d04d51c43e6ee29bdde0f9282d0594575bf90ed
1 /* Condition variables (native Windows implementation).
2 Copyright (C) 2008-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 Yoann Vandoorselaere <yoann@prelude-ids.org>, 2008,
18 and Bruno Haible <bruno@clisp.org>, 2008. */
20 #include <config.h>
22 /* Specification. */
23 #include "windows-cond.h"
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <sys/time.h>
29 /* Don't assume that UNICODE is not defined. */
30 #undef CreateEvent
31 #define CreateEvent CreateEventA
33 /* In this file, the waitqueues are implemented as linked lists. */
34 #define glwthread_waitqueue_t glwthread_linked_waitqueue_t
36 /* All links of a circular list, except the anchor, are of this type, carrying
37 a payload. */
38 struct glwthread_waitqueue_element
40 struct glwthread_waitqueue_link link; /* must be the first field! */
41 HANDLE event; /* Waiting thread, represented by an event.
42 This field is immutable once initialized. */
45 static void
46 glwthread_waitqueue_init (glwthread_waitqueue_t *wq)
48 wq->wq_list.wql_next = &wq->wq_list;
49 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;
58 HANDLE event;
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. */
63 elt =
64 (struct glwthread_waitqueue_element *)
65 malloc (sizeof (struct glwthread_waitqueue_element));
66 if (elt == NULL)
67 /* No more memory. */
68 return NULL;
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. */
76 free (elt);
77 return NULL;
79 elt->event = 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;
83 return elt;
86 /* Removes the current thread, represented by a
87 'struct glwthread_waitqueue_element *', from a wait queue.
88 Returns true if is was found and removed, false if it was not present. */
89 static bool
90 glwthread_waitqueue_remove (glwthread_waitqueue_t *wq,
91 struct glwthread_waitqueue_element *elt)
93 if (elt->link.wql_next != NULL && elt->link.wql_prev != NULL)
95 /* Remove elt from the circular list. */
96 struct glwthread_waitqueue_link *prev = elt->link.wql_prev;
97 struct glwthread_waitqueue_link *next = elt->link.wql_next;
98 prev->wql_next = next;
99 next->wql_prev = prev;
100 elt->link.wql_next = NULL;
101 elt->link.wql_prev = NULL;
102 return true;
104 else
105 return false;
108 /* Notifies the first thread from a wait queue and dequeues it. */
109 static void
110 glwthread_waitqueue_notify_first (glwthread_waitqueue_t *wq)
112 if (wq->wq_list.wql_next != &wq->wq_list)
114 struct glwthread_waitqueue_element *elt =
115 (struct glwthread_waitqueue_element *) wq->wq_list.wql_next;
116 struct glwthread_waitqueue_link *prev;
117 struct glwthread_waitqueue_link *next;
119 /* Remove elt from the circular list. */
120 prev = &wq->wq_list; /* = elt->link.wql_prev; */
121 next = elt->link.wql_next;
122 prev->wql_next = next;
123 next->wql_prev = prev;
124 elt->link.wql_next = NULL;
125 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. */
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;
154 SetEvent (elt->event);
155 /* After the SetEvent, this thread cannot access *elt any more, because
156 the woken-up thread will quickly call free (elt). */
158 l = next;
160 if (!(wq->wq_list.wql_next == &wq->wq_list
161 && wq->wq_list.wql_prev == &wq->wq_list))
162 abort ();
166 glwthread_cond_init (glwthread_cond_t *cond)
168 InitializeCriticalSection (&cond->lock);
169 glwthread_waitqueue_init (&cond->waiters);
171 cond->guard.done = 1;
172 return 0;
176 glwthread_cond_wait (glwthread_cond_t *cond,
177 void *mutex, int (*mutex_lock) (void *), int (*mutex_unlock) (void *))
179 if (!cond->guard.done)
181 if (InterlockedIncrement (&cond->guard.started) == 0)
182 /* This thread is the first one to need this condition variable.
183 Initialize it. */
184 glwthread_cond_init (cond);
185 else
187 /* Don't let cond->guard.started grow and wrap around. */
188 InterlockedDecrement (&cond->guard.started);
189 /* Yield the CPU while waiting for another thread to finish
190 initializing this condition variable. */
191 while (!cond->guard.done)
192 Sleep (0);
196 EnterCriticalSection (&cond->lock);
198 struct glwthread_waitqueue_element *elt =
199 glwthread_waitqueue_add (&cond->waiters);
200 LeaveCriticalSection (&cond->lock);
201 if (elt == NULL)
203 /* Allocation failure. Weird. */
204 return EAGAIN;
206 else
208 HANDLE event = elt->event;
209 int err;
210 DWORD result;
212 /* Now release the mutex and let any other thread take it. */
213 err = mutex_unlock (mutex);
214 if (err != 0)
216 EnterCriticalSection (&cond->lock);
217 glwthread_waitqueue_remove (&cond->waiters, elt);
218 LeaveCriticalSection (&cond->lock);
219 CloseHandle (event);
220 free (elt);
221 return err;
223 /* POSIX says:
224 "If another thread is able to acquire the mutex after the
225 about-to-block thread has released it, then a subsequent call to
226 pthread_cond_broadcast() or pthread_cond_signal() in that thread
227 shall behave as if it were issued after the about-to-block thread
228 has blocked."
229 This is fulfilled here, because the thread signalling is done
230 through SetEvent, not PulseEvent. */
231 /* Wait until another thread signals this event. */
232 result = WaitForSingleObject (event, INFINITE);
233 if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
234 abort ();
235 CloseHandle (event);
236 free (elt);
237 /* The thread which signalled the event already did the bookkeeping:
238 removed us from the waiters. */
239 return mutex_lock (mutex);
245 glwthread_cond_timedwait (glwthread_cond_t *cond,
246 void *mutex, int (*mutex_lock) (void *), int (*mutex_unlock) (void *),
247 const struct timespec *abstime)
249 if (!cond->guard.done)
251 if (InterlockedIncrement (&cond->guard.started) == 0)
252 /* This thread is the first one to need this condition variable.
253 Initialize it. */
254 glwthread_cond_init (cond);
255 else
257 /* Don't let cond->guard.started grow and wrap around. */
258 InterlockedDecrement (&cond->guard.started);
259 /* Yield the CPU while waiting for another thread to finish
260 initializing this condition variable. */
261 while (!cond->guard.done)
262 Sleep (0);
267 struct timeval currtime;
269 gettimeofday (&currtime, NULL);
270 if (currtime.tv_sec > abstime->tv_sec
271 || (currtime.tv_sec == abstime->tv_sec
272 && currtime.tv_usec * 1000 >= abstime->tv_nsec))
273 return ETIMEDOUT;
275 EnterCriticalSection (&cond->lock);
277 struct glwthread_waitqueue_element *elt =
278 glwthread_waitqueue_add (&cond->waiters);
279 LeaveCriticalSection (&cond->lock);
280 if (elt == NULL)
282 /* Allocation failure. Weird. */
283 return EAGAIN;
285 else
287 HANDLE event = elt->event;
288 int err;
289 DWORD timeout;
290 DWORD result;
292 /* Now release the mutex and let any other thread take it. */
293 err = mutex_unlock (mutex);
294 if (err != 0)
296 EnterCriticalSection (&cond->lock);
297 glwthread_waitqueue_remove (&cond->waiters, elt);
298 LeaveCriticalSection (&cond->lock);
299 CloseHandle (event);
300 free (elt);
301 return err;
303 /* POSIX says:
304 "If another thread is able to acquire the mutex after the
305 about-to-block thread has released it, then a subsequent call to
306 pthread_cond_broadcast() or pthread_cond_signal() in that thread
307 shall behave as if it were issued after the about-to-block thread
308 has blocked."
309 This is fulfilled here, because the thread signalling is done
310 through SetEvent, not PulseEvent. */
311 /* Wait until another thread signals this event or until the abstime
312 passes. */
313 gettimeofday (&currtime, NULL);
314 if (currtime.tv_sec > abstime->tv_sec)
315 timeout = 0;
316 else
318 unsigned long seconds = abstime->tv_sec - currtime.tv_sec;
319 timeout = seconds * 1000;
320 if (timeout / 1000 != seconds) /* overflow? */
321 timeout = INFINITE;
322 else
324 long milliseconds =
325 abstime->tv_nsec / 1000000 - currtime.tv_usec / 1000;
326 if (milliseconds >= 0)
328 timeout += milliseconds;
329 if (timeout < milliseconds) /* overflow? */
330 timeout = INFINITE;
332 else
334 if (timeout >= - milliseconds)
335 timeout -= (- milliseconds);
336 else
337 timeout = 0;
341 result = WaitForSingleObject (event, timeout);
342 if (result == WAIT_FAILED)
343 abort ();
344 if (result == WAIT_TIMEOUT)
346 EnterCriticalSection (&cond->lock);
347 if (glwthread_waitqueue_remove (&cond->waiters, elt))
349 /* The event was not signaled between the WaitForSingleObject
350 call and the EnterCriticalSection call. */
351 if (!(WaitForSingleObject (event, 0) == WAIT_TIMEOUT))
352 abort ();
354 else
356 /* The event was signaled between the WaitForSingleObject
357 call and the EnterCriticalSection call. */
358 if (!(WaitForSingleObject (event, 0) == WAIT_OBJECT_0))
359 abort ();
360 /* Produce the right return value. */
361 result = WAIT_OBJECT_0;
363 LeaveCriticalSection (&cond->lock);
365 else
367 /* The thread which signalled the event already did the
368 bookkeeping: removed us from the waiters. */
370 CloseHandle (event);
371 free (elt);
372 /* Take the mutex again. It does not matter whether this is done
373 before or after the bookkeeping for WAIT_TIMEOUT. */
374 err = mutex_lock (mutex);
375 return (err ? err :
376 result == WAIT_OBJECT_0 ? 0 :
377 result == WAIT_TIMEOUT ? ETIMEDOUT :
378 /* WAIT_FAILED shouldn't happen */ EAGAIN);
385 glwthread_cond_signal (glwthread_cond_t *cond)
387 if (!cond->guard.done)
388 return EINVAL;
390 EnterCriticalSection (&cond->lock);
391 /* POSIX says:
392 "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
393 have no effect if there are no threads currently blocked on cond." */
394 if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
395 glwthread_waitqueue_notify_first (&cond->waiters);
396 LeaveCriticalSection (&cond->lock);
398 return 0;
402 glwthread_cond_broadcast (glwthread_cond_t *cond)
404 if (!cond->guard.done)
405 return EINVAL;
407 EnterCriticalSection (&cond->lock);
408 /* POSIX says:
409 "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
410 have no effect if there are no threads currently blocked on cond."
411 glwthread_waitqueue_notify_all is a nop in this case. */
412 glwthread_waitqueue_notify_all (&cond->waiters);
413 LeaveCriticalSection (&cond->lock);
415 return 0;
419 glwthread_cond_destroy (glwthread_cond_t *cond)
421 if (!cond->guard.done)
422 return EINVAL;
423 if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
424 return EBUSY;
425 DeleteCriticalSection (&cond->lock);
426 cond->guard.done = 0;
427 return 0;