all: prefer https: URLs
[gnulib.git] / lib / glthread / cond.c
blobade8ebf341ca013861e2961d216d18f5a389ecdd
1 /* Condition variables for multithreading.
2 Copyright (C) 2008-2017 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 Yoann Vandoorselaere <yoann@prelude-ids.org>, 2008,
18 and Bruno Haible <bruno@clisp.org>, 2008. */
20 #include <config.h>
22 #define _GLTHREAD_COND_INLINE _GL_EXTERN_INLINE
23 #include "glthread/cond.h"
25 /* ========================================================================= */
27 #if USE_PTH_THREADS
29 /* -------------------------- gl_cond_t datatype -------------------------- */
31 int
32 glthread_cond_timedwait_multithreaded (gl_cond_t *cond,
33 gl_lock_t *lock,
34 struct timespec *abstime)
36 int ret, status;
37 pth_event_t ev;
39 ev = pth_event (PTH_EVENT_TIME, pth_time (abstime->tv_sec, abstime->tv_nsec / 1000));
40 ret = pth_cond_await (cond, lock, ev);
42 status = pth_event_status (ev);
43 pth_event_free (ev, PTH_FREE_THIS);
45 if (status == PTH_STATUS_OCCURRED)
46 return ETIMEDOUT;
48 return ret;
51 #endif
53 /* ========================================================================= */
55 #if USE_SOLARIS_THREADS
57 /* -------------------------- gl_cond_t datatype -------------------------- */
59 int
60 glthread_cond_timedwait_multithreaded (gl_cond_t *cond,
61 gl_lock_t *lock,
62 struct timespec *abstime)
64 int ret;
66 ret = cond_timedwait (cond, lock, abstime);
67 if (ret == ETIME)
68 return ETIMEDOUT;
69 return ret;
72 #endif
74 /* ========================================================================= */
76 #if USE_WINDOWS_THREADS
78 #include <sys/time.h>
80 /* -------------------------- gl_cond_t datatype -------------------------- */
82 /* In this file, the waitqueues are implemented as linked lists. */
83 #define gl_waitqueue_t gl_linked_waitqueue_t
85 /* All links of a circular list, except the anchor, are of this type, carrying
86 a payload. */
87 struct gl_waitqueue_element
89 struct gl_waitqueue_link link; /* must be the first field! */
90 HANDLE event; /* Waiting thread, represented by an event.
91 This field is immutable once initialized. */
94 static void
95 gl_waitqueue_init (gl_waitqueue_t *wq)
97 wq->wq_list.wql_next = &wq->wq_list;
98 wq->wq_list.wql_prev = &wq->wq_list;
101 /* Enqueues the current thread, represented by an event, in a wait queue.
102 Returns NULL if an allocation failure occurs. */
103 static struct gl_waitqueue_element *
104 gl_waitqueue_add (gl_waitqueue_t *wq)
106 struct gl_waitqueue_element *elt;
107 HANDLE event;
109 /* Allocate the memory for the waitqueue element on the heap, not on the
110 thread's stack. If the thread exits unexpectedly, we prefer to leak
111 some memory rather than to access unavailable memory and crash. */
112 elt =
113 (struct gl_waitqueue_element *)
114 malloc (sizeof (struct gl_waitqueue_element));
115 if (elt == NULL)
116 /* No more memory. */
117 return NULL;
119 /* Whether the created event is a manual-reset one or an auto-reset one,
120 does not matter, since we will wait on it only once. */
121 event = CreateEvent (NULL, TRUE, FALSE, NULL);
122 if (event == INVALID_HANDLE_VALUE)
124 /* No way to allocate an event. */
125 free (elt);
126 return NULL;
128 elt->event = event;
129 /* Insert elt at the end of the circular list. */
130 (elt->link.wql_prev = wq->wq_list.wql_prev)->wql_next = &elt->link;
131 (elt->link.wql_next = &wq->wq_list)->wql_prev = &elt->link;
132 return elt;
135 /* Removes the current thread, represented by a 'struct gl_waitqueue_element *',
136 from a wait queue.
137 Returns true if is was found and removed, false if it was not present. */
138 static bool
139 gl_waitqueue_remove (gl_waitqueue_t *wq, struct gl_waitqueue_element *elt)
141 if (elt->link.wql_next != NULL && elt->link.wql_prev != NULL)
143 /* Remove elt from the circular list. */
144 struct gl_waitqueue_link *prev = elt->link.wql_prev;
145 struct gl_waitqueue_link *next = elt->link.wql_next;
146 prev->wql_next = next;
147 next->wql_prev = prev;
148 elt->link.wql_next = NULL;
149 elt->link.wql_prev = NULL;
150 return true;
152 else
153 return false;
156 /* Notifies the first thread from a wait queue and dequeues it. */
157 static void
158 gl_waitqueue_notify_first (gl_waitqueue_t *wq)
160 if (wq->wq_list.wql_next != &wq->wq_list)
162 struct gl_waitqueue_element *elt =
163 (struct gl_waitqueue_element *) wq->wq_list.wql_next;
164 struct gl_waitqueue_link *prev;
165 struct gl_waitqueue_link *next;
167 /* Remove elt from the circular list. */
168 prev = &wq->wq_list; /* = elt->link.wql_prev; */
169 next = elt->link.wql_next;
170 prev->wql_next = next;
171 next->wql_prev = prev;
172 elt->link.wql_next = NULL;
173 elt->link.wql_prev = NULL;
175 SetEvent (elt->event);
176 /* After the SetEvent, this thread cannot access *elt any more, because
177 the woken-up thread will quickly call free (elt). */
181 /* Notifies all threads from a wait queue and dequeues them all. */
182 static void
183 gl_waitqueue_notify_all (gl_waitqueue_t *wq)
185 struct gl_waitqueue_link *l;
187 for (l = wq->wq_list.wql_next; l != &wq->wq_list; )
189 struct gl_waitqueue_element *elt = (struct gl_waitqueue_element *) l;
190 struct gl_waitqueue_link *prev;
191 struct gl_waitqueue_link *next;
193 /* Remove elt from the circular list. */
194 prev = &wq->wq_list; /* = elt->link.wql_prev; */
195 next = elt->link.wql_next;
196 prev->wql_next = next;
197 next->wql_prev = prev;
198 elt->link.wql_next = NULL;
199 elt->link.wql_prev = NULL;
201 SetEvent (elt->event);
202 /* After the SetEvent, this thread cannot access *elt any more, because
203 the woken-up thread will quickly call free (elt). */
205 l = next;
207 if (!(wq->wq_list.wql_next == &wq->wq_list
208 && wq->wq_list.wql_prev == &wq->wq_list))
209 abort ();
213 glthread_cond_init_func (gl_cond_t *cond)
215 InitializeCriticalSection (&cond->lock);
216 gl_waitqueue_init (&cond->waiters);
218 cond->guard.done = 1;
219 return 0;
223 glthread_cond_wait_func (gl_cond_t *cond, gl_lock_t *lock)
225 if (!cond->guard.done)
227 if (InterlockedIncrement (&cond->guard.started) == 0)
228 /* This thread is the first one to need this condition variable.
229 Initialize it. */
230 glthread_cond_init (cond);
231 else
232 /* Yield the CPU while waiting for another thread to finish
233 initializing this condition variable. */
234 while (!cond->guard.done)
235 Sleep (0);
238 EnterCriticalSection (&cond->lock);
240 struct gl_waitqueue_element *elt = gl_waitqueue_add (&cond->waiters);
241 LeaveCriticalSection (&cond->lock);
242 if (elt == NULL)
244 /* Allocation failure. Weird. */
245 return EAGAIN;
247 else
249 HANDLE event = elt->event;
250 int err;
251 DWORD result;
253 /* Now release the lock and let any other thread take it. */
254 err = glthread_lock_unlock (lock);
255 if (err != 0)
257 EnterCriticalSection (&cond->lock);
258 gl_waitqueue_remove (&cond->waiters, elt);
259 LeaveCriticalSection (&cond->lock);
260 CloseHandle (event);
261 free (elt);
262 return err;
264 /* POSIX says:
265 "If another thread is able to acquire the mutex after the
266 about-to-block thread has released it, then a subsequent call to
267 pthread_cond_broadcast() or pthread_cond_signal() in that thread
268 shall behave as if it were issued after the about-to-block thread
269 has blocked."
270 This is fulfilled here, because the thread signalling is done
271 through SetEvent, not PulseEvent. */
272 /* Wait until another thread signals this event. */
273 result = WaitForSingleObject (event, INFINITE);
274 if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
275 abort ();
276 CloseHandle (event);
277 free (elt);
278 /* The thread which signalled the event already did the bookkeeping:
279 removed us from the waiters. */
280 return glthread_lock_lock (lock);
286 glthread_cond_timedwait_func (gl_cond_t *cond, gl_lock_t *lock, struct timespec *abstime)
288 struct timeval currtime;
290 gettimeofday (&currtime, NULL);
291 if (currtime.tv_sec > abstime->tv_sec
292 || (currtime.tv_sec == abstime->tv_sec
293 && currtime.tv_usec * 1000 >= abstime->tv_nsec))
294 return ETIMEDOUT;
296 if (!cond->guard.done)
298 if (InterlockedIncrement (&cond->guard.started) == 0)
299 /* This thread is the first one to need this condition variable.
300 Initialize it. */
301 glthread_cond_init (cond);
302 else
303 /* Yield the CPU while waiting for another thread to finish
304 initializing this condition variable. */
305 while (!cond->guard.done)
306 Sleep (0);
309 EnterCriticalSection (&cond->lock);
311 struct gl_waitqueue_element *elt = gl_waitqueue_add (&cond->waiters);
312 LeaveCriticalSection (&cond->lock);
313 if (elt == NULL)
315 /* Allocation failure. Weird. */
316 return EAGAIN;
318 else
320 HANDLE event = elt->event;
321 int err;
322 DWORD timeout;
323 DWORD result;
325 /* Now release the lock and let any other thread take it. */
326 err = glthread_lock_unlock (lock);
327 if (err != 0)
329 EnterCriticalSection (&cond->lock);
330 gl_waitqueue_remove (&cond->waiters, elt);
331 LeaveCriticalSection (&cond->lock);
332 CloseHandle (event);
333 free (elt);
334 return err;
336 /* POSIX says:
337 "If another thread is able to acquire the mutex after the
338 about-to-block thread has released it, then a subsequent call to
339 pthread_cond_broadcast() or pthread_cond_signal() in that thread
340 shall behave as if it were issued after the about-to-block thread
341 has blocked."
342 This is fulfilled here, because the thread signalling is done
343 through SetEvent, not PulseEvent. */
344 /* Wait until another thread signals this event or until the abstime
345 passes. */
346 gettimeofday (&currtime, NULL);
347 if (currtime.tv_sec > abstime->tv_sec)
348 timeout = 0;
349 else
351 unsigned long seconds = abstime->tv_sec - currtime.tv_sec;
352 timeout = seconds * 1000;
353 if (timeout / 1000 != seconds) /* overflow? */
354 timeout = INFINITE;
355 else
357 long milliseconds =
358 abstime->tv_nsec / 1000000 - currtime.tv_usec / 1000;
359 if (milliseconds >= 0)
361 timeout += milliseconds;
362 if (timeout < milliseconds) /* overflow? */
363 timeout = INFINITE;
365 else
367 if (timeout >= - milliseconds)
368 timeout -= (- milliseconds);
369 else
370 timeout = 0;
374 result = WaitForSingleObject (event, timeout);
375 if (result == WAIT_FAILED)
376 abort ();
377 if (result == WAIT_TIMEOUT)
379 EnterCriticalSection (&cond->lock);
380 if (gl_waitqueue_remove (&cond->waiters, elt))
382 /* The event was not signaled between the WaitForSingleObject
383 call and the EnterCriticalSection call. */
384 if (!(WaitForSingleObject (event, 0) == WAIT_TIMEOUT))
385 abort ();
387 else
389 /* The event was signaled between the WaitForSingleObject
390 call and the EnterCriticalSection call. */
391 if (!(WaitForSingleObject (event, 0) == WAIT_OBJECT_0))
392 abort ();
393 /* Produce the right return value. */
394 result = WAIT_OBJECT_0;
396 LeaveCriticalSection (&cond->lock);
398 else
400 /* The thread which signalled the event already did the
401 bookkeeping: removed us from the waiters. */
403 CloseHandle (event);
404 free (elt);
405 /* Take the lock again. It does not matter whether this is done
406 before or after the bookkeeping for WAIT_TIMEOUT. */
407 err = glthread_lock_lock (lock);
408 return (err ? err :
409 result == WAIT_OBJECT_0 ? 0 :
410 result == WAIT_TIMEOUT ? ETIMEDOUT :
411 /* WAIT_FAILED shouldn't happen */ EAGAIN);
417 glthread_cond_signal_func (gl_cond_t *cond)
419 if (!cond->guard.done)
420 return EINVAL;
422 EnterCriticalSection (&cond->lock);
423 /* POSIX says:
424 "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
425 have no effect if there are no threads currently blocked on cond." */
426 if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
427 gl_waitqueue_notify_first (&cond->waiters);
428 LeaveCriticalSection (&cond->lock);
430 return 0;
434 glthread_cond_broadcast_func (gl_cond_t *cond)
436 if (!cond->guard.done)
437 return EINVAL;
439 EnterCriticalSection (&cond->lock);
440 /* POSIX says:
441 "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
442 have no effect if there are no threads currently blocked on cond."
443 gl_waitqueue_notify_all is a nop in this case. */
444 gl_waitqueue_notify_all (&cond->waiters);
445 LeaveCriticalSection (&cond->lock);
447 return 0;
451 glthread_cond_destroy_func (gl_cond_t *cond)
453 if (!cond->guard.done)
454 return EINVAL;
455 if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
456 return EBUSY;
457 DeleteCriticalSection (&cond->lock);
458 cond->guard.done = 0;
459 return 0;
462 #endif
464 /* ========================================================================= */