]> Savannah Git Hosting - gnulib.git/commitdiff
windows-cond: New module.
authorBruno Haible <bruno@clisp.org>
Thu, 20 Jun 2019 02:13:43 +0000 (04:13 +0200)
committerBruno Haible <bruno@clisp.org>
Thu, 20 Jun 2019 02:15:05 +0000 (04:15 +0200)
* lib/windows-cond.h: New file, based on lib/glthread/cond.h.
* lib/windows-cond.c: New file, based on lib/glthread/cond.c.
* lib/glthread/cond.h: Include windows-cond.h.
(struct gl_waitqueue_link, gl_linked_waitqueue_t): Remove types.
(gl_cond_t): Define using glwthread_cond_t.
(gl_cond_initializer): Define using GLWTHREAD_COND_INIT.
(glthread_cond_init): Define using glwthread_cond_init.
(glthread_cond_wait): Define using glwthread_cond_wait.
(glthread_cond_timedwait): Define using glwthread_cond_timedwait.
(glthread_cond_signal): Define using glwthread_cond_signal.
(glthread_cond_broadcast): Define using glwthread_cond_broadcast.
(glthread_cond_destroy): Define using glwthread_cond_destroy.
(glthread_cond_init_func, glthread_cond_wait_func,
glthread_cond_timedwait_func, glthread_cond_signal_func,
glthread_cond_broadcast_func, glthread_cond_destroy_func): Remove
declarations.
* lib/glthread/cond.c (gl_waitqueue_t, gl_waitqueue_element): Remove
types.
(gl_waitqueue_init, gl_waitqueue_add, gl_waitqueue_remove,
gl_waitqueue_notify_first, gl_waitqueue_notify_all,
glthread_cond_init_func, glthread_cond_wait_func,
glthread_cond_timedwait_func, glthread_cond_signal_func,
glthread_cond_broadcast_func, glthread_cond_destroy_func): Remove
functions.
* modules/windows-cond: New file.
* modules/cond (Depends-on): Add windows-cond. Remove gettimeofday.

ChangeLog
lib/glthread/cond.c
lib/glthread/cond.h
lib/windows-cond.c [new file with mode: 0644]
lib/windows-cond.h [new file with mode: 0644]
modules/cond
modules/windows-cond [new file with mode: 0644]

index 5e416fede8af6f40e13967142771bb4771a72134..6307fcc58c4054713b50c4dc3a4fe84211ceb9af 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2019-06-20  Bruno Haible  <bruno@clisp.org>
+
+       windows-cond: New module.
+       * lib/windows-cond.h: New file, based on lib/glthread/cond.h.
+       * lib/windows-cond.c: New file, based on lib/glthread/cond.c.
+       * lib/glthread/cond.h: Include windows-cond.h.
+       (struct gl_waitqueue_link, gl_linked_waitqueue_t): Remove types.
+       (gl_cond_t): Define using glwthread_cond_t.
+       (gl_cond_initializer): Define using GLWTHREAD_COND_INIT.
+       (glthread_cond_init): Define using glwthread_cond_init.
+       (glthread_cond_wait): Define using glwthread_cond_wait.
+       (glthread_cond_timedwait): Define using glwthread_cond_timedwait.
+       (glthread_cond_signal): Define using glwthread_cond_signal.
+       (glthread_cond_broadcast): Define using glwthread_cond_broadcast.
+       (glthread_cond_destroy): Define using glwthread_cond_destroy.
+       (glthread_cond_init_func, glthread_cond_wait_func,
+       glthread_cond_timedwait_func, glthread_cond_signal_func,
+       glthread_cond_broadcast_func, glthread_cond_destroy_func): Remove
+       declarations.
+       * lib/glthread/cond.c (gl_waitqueue_t, gl_waitqueue_element): Remove
+       types.
+       (gl_waitqueue_init, gl_waitqueue_add, gl_waitqueue_remove,
+       gl_waitqueue_notify_first, gl_waitqueue_notify_all,
+       glthread_cond_init_func, glthread_cond_wait_func,
+       glthread_cond_timedwait_func, glthread_cond_signal_func,
+       glthread_cond_broadcast_func, glthread_cond_destroy_func): Remove
+       functions.
+       * modules/windows-cond: New file.
+       * modules/cond (Depends-on): Add windows-cond. Remove gettimeofday.
+
 2019-06-20  Bruno Haible  <bruno@clisp.org>
 
        windows-timedrecmutex: New module.
index 6df6780ac8fdf5f170ce879d9039dc6fa2572d1e..2285c165208284af2961df8011b827a3b094816e 100644 (file)
@@ -75,400 +75,6 @@ glthread_cond_timedwait_multithreaded (gl_cond_t *cond,
 
 #if USE_WINDOWS_THREADS
 
-#include <sys/time.h>
-
-/* -------------------------- gl_cond_t datatype -------------------------- */
-
-/* In this file, the waitqueues are implemented as linked lists.  */
-#define gl_waitqueue_t gl_linked_waitqueue_t
-
-/* All links of a circular list, except the anchor, are of this type, carrying
-   a payload.  */
-struct gl_waitqueue_element
-{
-  struct gl_waitqueue_link link; /* must be the first field! */
-  HANDLE event; /* Waiting thread, represented by an event.
-                   This field is immutable once initialized. */
-};
-
-static void
-gl_waitqueue_init (gl_waitqueue_t *wq)
-{
-  wq->wq_list.wql_next = &wq->wq_list;
-  wq->wq_list.wql_prev = &wq->wq_list;
-}
-
-/* Enqueues the current thread, represented by an event, in a wait queue.
-   Returns NULL if an allocation failure occurs.  */
-static struct gl_waitqueue_element *
-gl_waitqueue_add (gl_waitqueue_t *wq)
-{
-  struct gl_waitqueue_element *elt;
-  HANDLE event;
-
-  /* Allocate the memory for the waitqueue element on the heap, not on the
-     thread's stack.  If the thread exits unexpectedly, we prefer to leak
-     some memory rather than to access unavailable memory and crash.  */
-  elt =
-    (struct gl_waitqueue_element *)
-    malloc (sizeof (struct gl_waitqueue_element));
-  if (elt == NULL)
-    /* No more memory.  */
-    return NULL;
-
-  /* Whether the created event is a manual-reset one or an auto-reset one,
-     does not matter, since we will wait on it only once.  */
-  event = CreateEvent (NULL, TRUE, FALSE, NULL);
-  if (event == INVALID_HANDLE_VALUE)
-    {
-      /* No way to allocate an event.  */
-      free (elt);
-      return NULL;
-    }
-  elt->event = event;
-  /* Insert elt at the end of the circular list.  */
-  (elt->link.wql_prev = wq->wq_list.wql_prev)->wql_next = &elt->link;
-  (elt->link.wql_next = &wq->wq_list)->wql_prev = &elt->link;
-  return elt;
-}
-
-/* Removes the current thread, represented by a 'struct gl_waitqueue_element *',
-   from a wait queue.
-   Returns true if is was found and removed, false if it was not present.  */
-static bool
-gl_waitqueue_remove (gl_waitqueue_t *wq, struct gl_waitqueue_element *elt)
-{
-  if (elt->link.wql_next != NULL && elt->link.wql_prev != NULL)
-    {
-      /* Remove elt from the circular list.  */
-      struct gl_waitqueue_link *prev = elt->link.wql_prev;
-      struct gl_waitqueue_link *next = elt->link.wql_next;
-      prev->wql_next = next;
-      next->wql_prev = prev;
-      elt->link.wql_next = NULL;
-      elt->link.wql_prev = NULL;
-      return true;
-    }
-  else
-    return false;
-}
-
-/* Notifies the first thread from a wait queue and dequeues it.  */
-static void
-gl_waitqueue_notify_first (gl_waitqueue_t *wq)
-{
-  if (wq->wq_list.wql_next != &wq->wq_list)
-    {
-      struct gl_waitqueue_element *elt =
-        (struct gl_waitqueue_element *) wq->wq_list.wql_next;
-      struct gl_waitqueue_link *prev;
-      struct gl_waitqueue_link *next;
-
-      /* Remove elt from the circular list.  */
-      prev = &wq->wq_list; /* = elt->link.wql_prev; */
-      next = elt->link.wql_next;
-      prev->wql_next = next;
-      next->wql_prev = prev;
-      elt->link.wql_next = NULL;
-      elt->link.wql_prev = NULL;
-
-      SetEvent (elt->event);
-      /* After the SetEvent, this thread cannot access *elt any more, because
-         the woken-up thread will quickly call  free (elt).  */
-    }
-}
-
-/* Notifies all threads from a wait queue and dequeues them all.  */
-static void
-gl_waitqueue_notify_all (gl_waitqueue_t *wq)
-{
-  struct gl_waitqueue_link *l;
-
-  for (l = wq->wq_list.wql_next; l != &wq->wq_list; )
-    {
-      struct gl_waitqueue_element *elt = (struct gl_waitqueue_element *) l;
-      struct gl_waitqueue_link *prev;
-      struct gl_waitqueue_link *next;
-
-      /* Remove elt from the circular list.  */
-      prev = &wq->wq_list; /* = elt->link.wql_prev; */
-      next = elt->link.wql_next;
-      prev->wql_next = next;
-      next->wql_prev = prev;
-      elt->link.wql_next = NULL;
-      elt->link.wql_prev = NULL;
-
-      SetEvent (elt->event);
-      /* After the SetEvent, this thread cannot access *elt any more, because
-         the woken-up thread will quickly call  free (elt).  */
-
-      l = next;
-    }
-  if (!(wq->wq_list.wql_next == &wq->wq_list
-        && wq->wq_list.wql_prev == &wq->wq_list))
-    abort ();
-}
-
-int
-glthread_cond_init_func (gl_cond_t *cond)
-{
-  InitializeCriticalSection (&cond->lock);
-  gl_waitqueue_init (&cond->waiters);
-
-  cond->guard.done = 1;
-  return 0;
-}
-
-int
-glthread_cond_wait_func (gl_cond_t *cond, gl_lock_t *lock)
-{
-  if (!cond->guard.done)
-    {
-      if (InterlockedIncrement (&cond->guard.started) == 0)
-        /* This thread is the first one to need this condition variable.
-           Initialize it.  */
-        glthread_cond_init (cond);
-      else
-        {
-          /* Don't let cond->guard.started grow and wrap around.  */
-          InterlockedDecrement (&cond->guard.started);
-          /* Yield the CPU while waiting for another thread to finish
-             initializing this condition variable.  */
-          while (!cond->guard.done)
-            Sleep (0);
-        }
-    }
-
-  EnterCriticalSection (&cond->lock);
-  {
-    struct gl_waitqueue_element *elt = gl_waitqueue_add (&cond->waiters);
-    LeaveCriticalSection (&cond->lock);
-    if (elt == NULL)
-      {
-        /* Allocation failure.  Weird.  */
-        return EAGAIN;
-      }
-    else
-      {
-        HANDLE event = elt->event;
-        int err;
-        DWORD result;
-
-        /* Now release the lock and let any other thread take it.  */
-        err = glthread_lock_unlock (lock);
-        if (err != 0)
-          {
-            EnterCriticalSection (&cond->lock);
-            gl_waitqueue_remove (&cond->waiters, elt);
-            LeaveCriticalSection (&cond->lock);
-            CloseHandle (event);
-            free (elt);
-            return err;
-          }
-        /* POSIX says:
-            "If another thread is able to acquire the mutex after the
-             about-to-block thread has released it, then a subsequent call to
-             pthread_cond_broadcast() or pthread_cond_signal() in that thread
-             shall behave as if it were issued after the about-to-block thread
-             has blocked."
-           This is fulfilled here, because the thread signalling is done
-           through SetEvent, not PulseEvent.  */
-        /* Wait until another thread signals this event.  */
-        result = WaitForSingleObject (event, INFINITE);
-        if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
-          abort ();
-        CloseHandle (event);
-        free (elt);
-        /* The thread which signalled the event already did the bookkeeping:
-           removed us from the waiters.  */
-        return glthread_lock_lock (lock);
-      }
-  }
-}
-
-int
-glthread_cond_timedwait_func (gl_cond_t *cond, gl_lock_t *lock, struct timespec *abstime)
-{
-  if (!cond->guard.done)
-    {
-      if (InterlockedIncrement (&cond->guard.started) == 0)
-        /* This thread is the first one to need this condition variable.
-           Initialize it.  */
-        glthread_cond_init (cond);
-      else
-        {
-          /* Don't let cond->guard.started grow and wrap around.  */
-          InterlockedDecrement (&cond->guard.started);
-          /* Yield the CPU while waiting for another thread to finish
-             initializing this condition variable.  */
-          while (!cond->guard.done)
-            Sleep (0);
-        }
-    }
-
-  {
-    struct timeval currtime;
-
-    gettimeofday (&currtime, NULL);
-    if (currtime.tv_sec > abstime->tv_sec
-        || (currtime.tv_sec == abstime->tv_sec
-            && currtime.tv_usec * 1000 >= abstime->tv_nsec))
-      return ETIMEDOUT;
-
-    EnterCriticalSection (&cond->lock);
-    {
-      struct gl_waitqueue_element *elt = gl_waitqueue_add (&cond->waiters);
-      LeaveCriticalSection (&cond->lock);
-      if (elt == NULL)
-        {
-          /* Allocation failure.  Weird.  */
-          return EAGAIN;
-        }
-      else
-        {
-          HANDLE event = elt->event;
-          int err;
-          DWORD timeout;
-          DWORD result;
-
-          /* Now release the lock and let any other thread take it.  */
-          err = glthread_lock_unlock (lock);
-          if (err != 0)
-            {
-              EnterCriticalSection (&cond->lock);
-              gl_waitqueue_remove (&cond->waiters, elt);
-              LeaveCriticalSection (&cond->lock);
-              CloseHandle (event);
-              free (elt);
-              return err;
-            }
-          /* POSIX says:
-              "If another thread is able to acquire the mutex after the
-               about-to-block thread has released it, then a subsequent call to
-               pthread_cond_broadcast() or pthread_cond_signal() in that thread
-               shall behave as if it were issued after the about-to-block thread
-               has blocked."
-             This is fulfilled here, because the thread signalling is done
-             through SetEvent, not PulseEvent.  */
-          /* Wait until another thread signals this event or until the abstime
-             passes.  */
-          gettimeofday (&currtime, NULL);
-          if (currtime.tv_sec > abstime->tv_sec)
-            timeout = 0;
-          else
-            {
-              unsigned long seconds = abstime->tv_sec - currtime.tv_sec;
-              timeout = seconds * 1000;
-              if (timeout / 1000 != seconds) /* overflow? */
-                timeout = INFINITE;
-              else
-                {
-                  long milliseconds =
-                    abstime->tv_nsec / 1000000 - currtime.tv_usec / 1000;
-                  if (milliseconds >= 0)
-                    {
-                      timeout += milliseconds;
-                      if (timeout < milliseconds) /* overflow? */
-                        timeout = INFINITE;
-                    }
-                  else
-                    {
-                      if (timeout >= - milliseconds)
-                        timeout -= (- milliseconds);
-                      else
-                        timeout = 0;
-                    }
-                }
-            }
-          result = WaitForSingleObject (event, timeout);
-          if (result == WAIT_FAILED)
-            abort ();
-          if (result == WAIT_TIMEOUT)
-            {
-              EnterCriticalSection (&cond->lock);
-              if (gl_waitqueue_remove (&cond->waiters, elt))
-                {
-                  /* The event was not signaled between the WaitForSingleObject
-                     call and the EnterCriticalSection call.  */
-                  if (!(WaitForSingleObject (event, 0) == WAIT_TIMEOUT))
-                    abort ();
-                }
-              else
-                {
-                  /* The event was signaled between the WaitForSingleObject
-                     call and the EnterCriticalSection call.  */
-                  if (!(WaitForSingleObject (event, 0) == WAIT_OBJECT_0))
-                    abort ();
-                  /* Produce the right return value.  */
-                  result = WAIT_OBJECT_0;
-                }
-              LeaveCriticalSection (&cond->lock);
-            }
-          else
-            {
-              /* The thread which signalled the event already did the
-                 bookkeeping: removed us from the waiters.  */
-            }
-          CloseHandle (event);
-          free (elt);
-          /* Take the lock again.  It does not matter whether this is done
-             before or after the bookkeeping for WAIT_TIMEOUT.  */
-          err = glthread_lock_lock (lock);
-          return (err ? err :
-                  result == WAIT_OBJECT_0 ? 0 :
-                  result == WAIT_TIMEOUT ? ETIMEDOUT :
-                  /* WAIT_FAILED shouldn't happen */ EAGAIN);
-        }
-    }
-  }
-}
-
-int
-glthread_cond_signal_func (gl_cond_t *cond)
-{
-  if (!cond->guard.done)
-    return EINVAL;
-
-  EnterCriticalSection (&cond->lock);
-  /* POSIX says:
-      "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
-       have no effect if there are no threads currently blocked on cond."  */
-  if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
-    gl_waitqueue_notify_first (&cond->waiters);
-  LeaveCriticalSection (&cond->lock);
-
-  return 0;
-}
-
-int
-glthread_cond_broadcast_func (gl_cond_t *cond)
-{
-  if (!cond->guard.done)
-    return EINVAL;
-
-  EnterCriticalSection (&cond->lock);
-  /* POSIX says:
-      "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
-       have no effect if there are no threads currently blocked on cond."
-     gl_waitqueue_notify_all is a nop in this case.  */
-  gl_waitqueue_notify_all (&cond->waiters);
-  LeaveCriticalSection (&cond->lock);
-
-  return 0;
-}
-
-int
-glthread_cond_destroy_func (gl_cond_t *cond)
-{
-  if (!cond->guard.done)
-    return EINVAL;
-  if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
-    return EBUSY;
-  DeleteCriticalSection (&cond->lock);
-  cond->guard.done = 0;
-  return 0;
-}
-
 #endif
 
 /* ========================================================================= */
index f252c329cc7d1d346f1339a1b12895421d4437e1..8880b078e89b3aae69a9d32de7627f642444dcd8 100644 (file)
@@ -293,53 +293,38 @@ extern int glthread_cond_timedwait_multithreaded (gl_cond_t *cond, gl_lock_t *lo
 # define WIN32_LEAN_AND_MEAN  /* avoid including junk */
 # include <windows.h>
 
+# include "windows-cond.h"
+
 # ifdef __cplusplus
 extern "C" {
 # endif
 
 /* -------------------------- gl_cond_t datatype -------------------------- */
 
-struct gl_waitqueue_link
-{
-  struct gl_waitqueue_link *wql_next;
-  struct gl_waitqueue_link *wql_prev;
-};
-typedef struct
-        {
-          struct gl_waitqueue_link wq_list; /* circular list of waiting threads */
-        }
-        gl_linked_waitqueue_t;
-typedef struct
-        {
-          glwthread_spinlock_t guard; /* protects the initialization */
-          CRITICAL_SECTION lock; /* protects the remaining fields */
-          gl_linked_waitqueue_t waiters; /* waiting threads */
-        }
-        gl_cond_t;
+typedef glwthread_cond_t gl_cond_t;
 # define gl_cond_define(STORAGECLASS, NAME) \
     STORAGECLASS gl_cond_t NAME;
 # define gl_cond_define_initialized(STORAGECLASS, NAME) \
     STORAGECLASS gl_cond_t NAME = gl_cond_initializer;
 # define gl_cond_initializer \
-    { { 0, -1 } }
+    GLWTHREAD_COND_INIT
 # define glthread_cond_init(COND) \
-    glthread_cond_init_func (COND)
+    glwthread_cond_init (COND)
 # define glthread_cond_wait(COND, LOCK) \
-    glthread_cond_wait_func (COND, LOCK)
+    glwthread_cond_wait (COND, LOCK, \
+                         (int (*) (void *)) glwthread_mutex_lock, \
+                         (int (*) (void *)) glwthread_mutex_unlock)
 # define glthread_cond_timedwait(COND, LOCK, ABSTIME) \
-    glthread_cond_timedwait_func (COND, LOCK, ABSTIME)
+    glwthread_cond_timedwait (COND, LOCK, \
+                              (int (*) (void *)) glwthread_mutex_lock, \
+                              (int (*) (void *)) glwthread_mutex_unlock, \
+                              ABSTIME)
 # define glthread_cond_signal(COND) \
-    glthread_cond_signal_func (COND)
+    glwthread_cond_signal (COND)
 # define glthread_cond_broadcast(COND) \
-    glthread_cond_broadcast_func (COND)
+    glwthread_cond_broadcast (COND)
 # define glthread_cond_destroy(COND) \
-    glthread_cond_destroy_func (COND)
-extern int glthread_cond_init_func (gl_cond_t *cond);
-extern int glthread_cond_wait_func (gl_cond_t *cond, gl_lock_t *lock);
-extern int glthread_cond_timedwait_func (gl_cond_t *cond, gl_lock_t *lock, struct timespec *abstime);
-extern int glthread_cond_signal_func (gl_cond_t *cond);
-extern int glthread_cond_broadcast_func (gl_cond_t *cond);
-extern int glthread_cond_destroy_func (gl_cond_t *cond);
+    glwthread_cond_destroy (COND)
 
 # ifdef __cplusplus
 }
diff --git a/lib/windows-cond.c b/lib/windows-cond.c
new file mode 100644 (file)
index 0000000..1c1c68a
--- /dev/null
@@ -0,0 +1,425 @@
+/* Condition variables (native Windows implementation).
+   Copyright (C) 2008-2019 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Yoann Vandoorselaere <yoann@prelude-ids.org>, 2008,
+   and Bruno Haible <bruno@clisp.org>, 2008.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "windows-cond.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+/* In this file, the waitqueues are implemented as linked lists.  */
+#define glwthread_waitqueue_t glwthread_linked_waitqueue_t
+
+/* All links of a circular list, except the anchor, are of this type, carrying
+   a payload.  */
+struct glwthread_waitqueue_element
+{
+  struct glwthread_waitqueue_link link; /* must be the first field! */
+  HANDLE event; /* Waiting thread, represented by an event.
+                   This field is immutable once initialized. */
+};
+
+static void
+glwthread_waitqueue_init (glwthread_waitqueue_t *wq)
+{
+  wq->wq_list.wql_next = &wq->wq_list;
+  wq->wq_list.wql_prev = &wq->wq_list;
+}
+
+/* Enqueues the current thread, represented by an event, in a wait queue.
+   Returns NULL if an allocation failure occurs.  */
+static struct glwthread_waitqueue_element *
+glwthread_waitqueue_add (glwthread_waitqueue_t *wq)
+{
+  struct glwthread_waitqueue_element *elt;
+  HANDLE event;
+
+  /* Allocate the memory for the waitqueue element on the heap, not on the
+     thread's stack.  If the thread exits unexpectedly, we prefer to leak
+     some memory rather than to access unavailable memory and crash.  */
+  elt =
+    (struct glwthread_waitqueue_element *)
+    malloc (sizeof (struct glwthread_waitqueue_element));
+  if (elt == NULL)
+    /* No more memory.  */
+    return NULL;
+
+  /* Whether the created event is a manual-reset one or an auto-reset one,
+     does not matter, since we will wait on it only once.  */
+  event = CreateEvent (NULL, TRUE, FALSE, NULL);
+  if (event == INVALID_HANDLE_VALUE)
+    {
+      /* No way to allocate an event.  */
+      free (elt);
+      return NULL;
+    }
+  elt->event = event;
+  /* Insert elt at the end of the circular list.  */
+  (elt->link.wql_prev = wq->wq_list.wql_prev)->wql_next = &elt->link;
+  (elt->link.wql_next = &wq->wq_list)->wql_prev = &elt->link;
+  return elt;
+}
+
+/* Removes the current thread, represented by a
+   'struct glwthread_waitqueue_element *', from a wait queue.
+   Returns true if is was found and removed, false if it was not present.  */
+static bool
+glwthread_waitqueue_remove (glwthread_waitqueue_t *wq,
+                            struct glwthread_waitqueue_element *elt)
+{
+  if (elt->link.wql_next != NULL && elt->link.wql_prev != NULL)
+    {
+      /* Remove elt from the circular list.  */
+      struct glwthread_waitqueue_link *prev = elt->link.wql_prev;
+      struct glwthread_waitqueue_link *next = elt->link.wql_next;
+      prev->wql_next = next;
+      next->wql_prev = prev;
+      elt->link.wql_next = NULL;
+      elt->link.wql_prev = NULL;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* Notifies the first thread from a wait queue and dequeues it.  */
+static void
+glwthread_waitqueue_notify_first (glwthread_waitqueue_t *wq)
+{
+  if (wq->wq_list.wql_next != &wq->wq_list)
+    {
+      struct glwthread_waitqueue_element *elt =
+        (struct glwthread_waitqueue_element *) wq->wq_list.wql_next;
+      struct glwthread_waitqueue_link *prev;
+      struct glwthread_waitqueue_link *next;
+
+      /* Remove elt from the circular list.  */
+      prev = &wq->wq_list; /* = elt->link.wql_prev; */
+      next = elt->link.wql_next;
+      prev->wql_next = next;
+      next->wql_prev = prev;
+      elt->link.wql_next = NULL;
+      elt->link.wql_prev = NULL;
+
+      SetEvent (elt->event);
+      /* After the SetEvent, this thread cannot access *elt any more, because
+         the woken-up thread will quickly call  free (elt).  */
+    }
+}
+
+/* Notifies all threads from a wait queue and dequeues them all.  */
+static void
+glwthread_waitqueue_notify_all (glwthread_waitqueue_t *wq)
+{
+  struct glwthread_waitqueue_link *l;
+
+  for (l = wq->wq_list.wql_next; l != &wq->wq_list; )
+    {
+      struct glwthread_waitqueue_element *elt =
+        (struct glwthread_waitqueue_element *) l;
+      struct glwthread_waitqueue_link *prev;
+      struct glwthread_waitqueue_link *next;
+
+      /* Remove elt from the circular list.  */
+      prev = &wq->wq_list; /* = elt->link.wql_prev; */
+      next = elt->link.wql_next;
+      prev->wql_next = next;
+      next->wql_prev = prev;
+      elt->link.wql_next = NULL;
+      elt->link.wql_prev = NULL;
+
+      SetEvent (elt->event);
+      /* After the SetEvent, this thread cannot access *elt any more, because
+         the woken-up thread will quickly call  free (elt).  */
+
+      l = next;
+    }
+  if (!(wq->wq_list.wql_next == &wq->wq_list
+        && wq->wq_list.wql_prev == &wq->wq_list))
+    abort ();
+}
+
+int
+glwthread_cond_init (glwthread_cond_t *cond)
+{
+  InitializeCriticalSection (&cond->lock);
+  glwthread_waitqueue_init (&cond->waiters);
+
+  cond->guard.done = 1;
+  return 0;
+}
+
+int
+glwthread_cond_wait (glwthread_cond_t *cond,
+                     void *mutex, int (*mutex_lock) (void *), int (*mutex_unlock) (void *))
+{
+  if (!cond->guard.done)
+    {
+      if (InterlockedIncrement (&cond->guard.started) == 0)
+        /* This thread is the first one to need this condition variable.
+           Initialize it.  */
+        glwthread_cond_init (cond);
+      else
+        {
+          /* Don't let cond->guard.started grow and wrap around.  */
+          InterlockedDecrement (&cond->guard.started);
+          /* Yield the CPU while waiting for another thread to finish
+             initializing this condition variable.  */
+          while (!cond->guard.done)
+            Sleep (0);
+        }
+    }
+
+  EnterCriticalSection (&cond->lock);
+  {
+    struct glwthread_waitqueue_element *elt =
+      glwthread_waitqueue_add (&cond->waiters);
+    LeaveCriticalSection (&cond->lock);
+    if (elt == NULL)
+      {
+        /* Allocation failure.  Weird.  */
+        return EAGAIN;
+      }
+    else
+      {
+        HANDLE event = elt->event;
+        int err;
+        DWORD result;
+
+        /* Now release the mutex and let any other thread take it.  */
+        err = mutex_unlock (mutex);
+        if (err != 0)
+          {
+            EnterCriticalSection (&cond->lock);
+            glwthread_waitqueue_remove (&cond->waiters, elt);
+            LeaveCriticalSection (&cond->lock);
+            CloseHandle (event);
+            free (elt);
+            return err;
+          }
+        /* POSIX says:
+            "If another thread is able to acquire the mutex after the
+             about-to-block thread has released it, then a subsequent call to
+             pthread_cond_broadcast() or pthread_cond_signal() in that thread
+             shall behave as if it were issued after the about-to-block thread
+             has blocked."
+           This is fulfilled here, because the thread signalling is done
+           through SetEvent, not PulseEvent.  */
+        /* Wait until another thread signals this event.  */
+        result = WaitForSingleObject (event, INFINITE);
+        if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
+          abort ();
+        CloseHandle (event);
+        free (elt);
+        /* The thread which signalled the event already did the bookkeeping:
+           removed us from the waiters.  */
+        return mutex_lock (mutex);
+      }
+  }
+}
+
+int
+glwthread_cond_timedwait (glwthread_cond_t *cond,
+                          void *mutex, int (*mutex_lock) (void *), int (*mutex_unlock) (void *),
+                          const struct timespec *abstime)
+{
+  if (!cond->guard.done)
+    {
+      if (InterlockedIncrement (&cond->guard.started) == 0)
+        /* This thread is the first one to need this condition variable.
+           Initialize it.  */
+        glwthread_cond_init (cond);
+      else
+        {
+          /* Don't let cond->guard.started grow and wrap around.  */
+          InterlockedDecrement (&cond->guard.started);
+          /* Yield the CPU while waiting for another thread to finish
+             initializing this condition variable.  */
+          while (!cond->guard.done)
+            Sleep (0);
+        }
+    }
+
+  {
+    struct timeval currtime;
+
+    gettimeofday (&currtime, NULL);
+    if (currtime.tv_sec > abstime->tv_sec
+        || (currtime.tv_sec == abstime->tv_sec
+            && currtime.tv_usec * 1000 >= abstime->tv_nsec))
+      return ETIMEDOUT;
+
+    EnterCriticalSection (&cond->lock);
+    {
+      struct glwthread_waitqueue_element *elt =
+        glwthread_waitqueue_add (&cond->waiters);
+      LeaveCriticalSection (&cond->lock);
+      if (elt == NULL)
+        {
+          /* Allocation failure.  Weird.  */
+          return EAGAIN;
+        }
+      else
+        {
+          HANDLE event = elt->event;
+          int err;
+          DWORD timeout;
+          DWORD result;
+
+          /* Now release the mutex and let any other thread take it.  */
+          err = mutex_unlock (mutex);
+          if (err != 0)
+            {
+              EnterCriticalSection (&cond->lock);
+              glwthread_waitqueue_remove (&cond->waiters, elt);
+              LeaveCriticalSection (&cond->lock);
+              CloseHandle (event);
+              free (elt);
+              return err;
+            }
+          /* POSIX says:
+              "If another thread is able to acquire the mutex after the
+               about-to-block thread has released it, then a subsequent call to
+               pthread_cond_broadcast() or pthread_cond_signal() in that thread
+               shall behave as if it were issued after the about-to-block thread
+               has blocked."
+             This is fulfilled here, because the thread signalling is done
+             through SetEvent, not PulseEvent.  */
+          /* Wait until another thread signals this event or until the abstime
+             passes.  */
+          gettimeofday (&currtime, NULL);
+          if (currtime.tv_sec > abstime->tv_sec)
+            timeout = 0;
+          else
+            {
+              unsigned long seconds = abstime->tv_sec - currtime.tv_sec;
+              timeout = seconds * 1000;
+              if (timeout / 1000 != seconds) /* overflow? */
+                timeout = INFINITE;
+              else
+                {
+                  long milliseconds =
+                    abstime->tv_nsec / 1000000 - currtime.tv_usec / 1000;
+                  if (milliseconds >= 0)
+                    {
+                      timeout += milliseconds;
+                      if (timeout < milliseconds) /* overflow? */
+                        timeout = INFINITE;
+                    }
+                  else
+                    {
+                      if (timeout >= - milliseconds)
+                        timeout -= (- milliseconds);
+                      else
+                        timeout = 0;
+                    }
+                }
+            }
+          result = WaitForSingleObject (event, timeout);
+          if (result == WAIT_FAILED)
+            abort ();
+          if (result == WAIT_TIMEOUT)
+            {
+              EnterCriticalSection (&cond->lock);
+              if (glwthread_waitqueue_remove (&cond->waiters, elt))
+                {
+                  /* The event was not signaled between the WaitForSingleObject
+                     call and the EnterCriticalSection call.  */
+                  if (!(WaitForSingleObject (event, 0) == WAIT_TIMEOUT))
+                    abort ();
+                }
+              else
+                {
+                  /* The event was signaled between the WaitForSingleObject
+                     call and the EnterCriticalSection call.  */
+                  if (!(WaitForSingleObject (event, 0) == WAIT_OBJECT_0))
+                    abort ();
+                  /* Produce the right return value.  */
+                  result = WAIT_OBJECT_0;
+                }
+              LeaveCriticalSection (&cond->lock);
+            }
+          else
+            {
+              /* The thread which signalled the event already did the
+                 bookkeeping: removed us from the waiters.  */
+            }
+          CloseHandle (event);
+          free (elt);
+          /* Take the mutex again.  It does not matter whether this is done
+             before or after the bookkeeping for WAIT_TIMEOUT.  */
+          err = mutex_lock (mutex);
+          return (err ? err :
+                  result == WAIT_OBJECT_0 ? 0 :
+                  result == WAIT_TIMEOUT ? ETIMEDOUT :
+                  /* WAIT_FAILED shouldn't happen */ EAGAIN);
+        }
+    }
+  }
+}
+
+int
+glwthread_cond_signal (glwthread_cond_t *cond)
+{
+  if (!cond->guard.done)
+    return EINVAL;
+
+  EnterCriticalSection (&cond->lock);
+  /* POSIX says:
+      "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
+       have no effect if there are no threads currently blocked on cond."  */
+  if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
+    glwthread_waitqueue_notify_first (&cond->waiters);
+  LeaveCriticalSection (&cond->lock);
+
+  return 0;
+}
+
+int
+glwthread_cond_broadcast (glwthread_cond_t *cond)
+{
+  if (!cond->guard.done)
+    return EINVAL;
+
+  EnterCriticalSection (&cond->lock);
+  /* POSIX says:
+      "The pthread_cond_broadcast() and pthread_cond_signal() functions shall
+       have no effect if there are no threads currently blocked on cond."
+     glwthread_waitqueue_notify_all is a nop in this case.  */
+  glwthread_waitqueue_notify_all (&cond->waiters);
+  LeaveCriticalSection (&cond->lock);
+
+  return 0;
+}
+
+int
+glwthread_cond_destroy (glwthread_cond_t *cond)
+{
+  if (!cond->guard.done)
+    return EINVAL;
+  if (cond->waiters.wq_list.wql_next != &cond->waiters.wq_list)
+    return EBUSY;
+  DeleteCriticalSection (&cond->lock);
+  cond->guard.done = 0;
+  return 0;
+}
diff --git a/lib/windows-cond.h b/lib/windows-cond.h
new file mode 100644 (file)
index 0000000..a21a9d0
--- /dev/null
@@ -0,0 +1,74 @@
+/* Condition variables (native Windows implementation).
+   Copyright (C) 2008-2019 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Yoann Vandoorselaere <yoann@prelude-ids.org>, 2008.
+   Based on Bruno Haible <bruno@clisp.org> lock.h */
+
+#ifndef _WINDOWS_COND_H
+#define _WINDOWS_COND_H
+
+#define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#include <windows.h>
+
+#include <time.h>
+
+#include "windows-spinlock.h"
+
+struct glwthread_waitqueue_link
+{
+  struct glwthread_waitqueue_link *wql_next;
+  struct glwthread_waitqueue_link *wql_prev;
+};
+typedef struct
+        {
+          struct glwthread_waitqueue_link wq_list; /* circular list of waiting threads */
+        }
+        glwthread_linked_waitqueue_t;
+typedef struct
+        {
+          glwthread_spinlock_t guard; /* protects the initialization */
+          CRITICAL_SECTION lock; /* protects the remaining fields */
+          glwthread_linked_waitqueue_t waiters; /* waiting threads */
+        }
+        glwthread_cond_t;
+
+#define GLWTHREAD_COND_INIT { GLWTHREAD_SPINLOCK_INIT }
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int glwthread_cond_init (glwthread_cond_t *cond);
+/* Here, to cope with the various types of mutexes, the mutex is a 'void *', and
+   the caller needs to pass the corresponding *_lock and *_unlock functions.  */
+extern int glwthread_cond_wait (glwthread_cond_t *cond,
+                                void *mutex,
+                                int (*mutex_lock) (void *),
+                                int (*mutex_unlock) (void *));
+extern int glwthread_cond_timedwait (glwthread_cond_t *cond,
+                                     void *mutex,
+                                     int (*mutex_lock) (void *),
+                                     int (*mutex_unlock) (void *),
+                                     const struct timespec *abstime);
+extern int glwthread_cond_signal (glwthread_cond_t *cond);
+extern int glwthread_cond_broadcast (glwthread_cond_t *cond);
+extern int glwthread_cond_destroy (glwthread_cond_t *cond);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WINDOWS_COND_H */
index c3a2dab2549ea8e9cb866c1dca5a81906444e388..82b87e3b3c576c3b7ec1826694fe4fb9fdc908f4 100644 (file)
@@ -13,7 +13,7 @@ errno
 extern-inline
 stdbool
 time
-gettimeofday
+windows-cond    [test $gl_threads_api = windows]
 
 configure.ac:
 gl_COND
diff --git a/modules/windows-cond b/modules/windows-cond
new file mode 100644 (file)
index 0000000..afb70b0
--- /dev/null
@@ -0,0 +1,32 @@
+Description:
+Condition variables (native Windows implementation).
+
+Files:
+lib/windows-cond.h
+lib/windows-cond.c
+lib/windows-spinlock.h
+
+Depends-on:
+stdbool
+errno
+time
+gettimeofday
+
+configure.ac:
+AC_REQUIRE([AC_CANONICAL_HOST])
+case "$host_os" in
+  mingw*)
+    AC_LIBOBJ([windows-cond])
+    ;;
+esac
+
+Makefile.am:
+
+Include:
+"windows-cond.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all