changeset 10627:04756a28484d

Provide a Win32 implementation of the 'cond' module.
author Yoann Vandoorselaere <yoann@prelude-ids.org>
date Sun, 12 Oct 2008 00:14:22 +0200
parents 4d2d469f9c49
children b40075443afa
files ChangeLog lib/glthread/cond.c lib/glthread/cond.h modules/cond
diffstat 4 files changed, 379 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2008-10-11  Yoann Vandoorselaere  <yoann@prelude-ids.org>
+            Bruno Haible  <bruno@clisp.org>
+
+	Provide a Win32 implementation of the 'cond' module.
+	* lib/glthread/cond.h [USE_WIN32]: New implementation.
+	* lib/glthread/cond.c (glthread_cond_init_func,
+	glthread_cond_wait_func, glthread_cond_timedwait_func,
+	glthread_cond_signal_func, glthread_cond_broadcast_func,
+	glthread_cond_destroy_func) [USE_WIN32]: New functions.
+	* modules/cond (Dependencies): Add gettimeofday.
+
 2008-10-11  Bruno Haible  <bruno@clisp.org>
 
 	Make sleep work on older versions of mingw.
--- a/lib/glthread/cond.c
+++ b/lib/glthread/cond.c
@@ -15,7 +15,8 @@
    along with this program; if not, write to the Free Software Foundation,
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
-/* Written by Yoann Vandoorselaere <yoann@prelude-ids.org>, 2008.  */
+/* Written by Yoann Vandoorselaere <yoann@prelude-ids.org>, 2008,
+   and Bruno Haible <bruno@clisp.org>, 2008.  */
 
 #include <config.h>
 
@@ -71,3 +72,310 @@
 #endif
 
 /* ========================================================================= */
+
+#if USE_WIN32_THREADS
+
+#include <sys/time.h>
+
+/* -------------------------- gl_cond_t datatype -------------------------- */
+
+/* This implementation is based on the article
+   Douglas C. Schmidt, Irfan Pyarali
+   "Strategies for Implementing POSIX Condition Variables on Win32"
+   <http://www.cs.wustl.edu/~schmidt/win32-cv-1.html>  */
+
+int
+glthread_cond_init_func (gl_cond_t *cond)
+{
+  InitializeCriticalSection (&cond->lock);
+  /* Create a manual-reset event.  */
+  cond->event = CreateEvent (NULL, TRUE, FALSE, NULL);
+  cond->waiters_count = 0;
+  cond->release_count = 0;
+  cond->wait_generation_count = 0;
+
+  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
+	/* Yield the CPU while waiting for another thread to finish
+	   initializing this condition variable.  */
+	while (!cond->guard.done)
+	  Sleep (0);
+    }
+
+  {
+    unsigned old_generation_count;
+    HANDLE event;
+
+    EnterCriticalSection (&cond->lock);
+    /* Increment waiters_count,
+       and get a copy of the current wait_generation_count.  */
+    cond->waiters_count++;
+    old_generation_count = cond->wait_generation_count;
+    event = cond->event;
+    LeaveCriticalSection (&cond->lock);
+
+    {
+      /* Now release the lock and let any other thread take it.  */
+      int err = glthread_lock_unlock (lock);
+      if (err != 0)
+	{
+	  EnterCriticalSection (&cond->lock);
+	  cond->waiters_count--;
+	  LeaveCriticalSection (&cond->lock);
+	  return err;
+	}
+
+      {
+	/* Wait until another thread signals this event.  */
+	DWORD result;
+
+	for (;;)
+	  {
+	    bool wait_done;
+
+	    result = WaitForSingleObject (event, INFINITE);
+	    if (result != WAIT_OBJECT_0)
+	      break;
+	    /* Distingish intended from spurious wakeups.  */
+	    EnterCriticalSection (&cond->lock);
+	    wait_done =
+	      (cond->release_count > 0
+	       && cond->wait_generation_count != old_generation_count);
+	    LeaveCriticalSection (&cond->lock);
+	    if (wait_done)
+	      break;
+	  }
+
+	/* Take the lock again.  */
+	err = glthread_lock_lock (lock);
+
+	/* Do the bookkeeping.  */
+	EnterCriticalSection (&cond->lock);
+	cond->waiters_count--;
+	if (result == WAIT_OBJECT_0)
+	  {
+	    /* The wait terminated because the event was signaled.
+	       Acknowledge the receipt.  */
+	    if (--cond->release_count == 0)
+	      {
+		/* The last waiting thread to be notified has to reset
+		   the event.  */
+		ResetEvent (cond->event);
+	      }
+	  }
+	LeaveCriticalSection (&cond->lock);
+
+	return (err ? err :
+		ret == WAIT_OBJECT_0 ? 0 :
+		/* WAIT_FAILED shouldn't happen */ EAGAIN);
+      }
+    }
+  }
+}
+
+int
+glthread_cond_timedwait_func (gl_cond_t *cond, gl_lock_t *lock, struct timespec *abstime)
+{
+  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;
+
+  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
+	/* Yield the CPU while waiting for another thread to finish
+	   initializing this condition variable.  */
+	while (!cond->guard.done)
+	  Sleep (0);
+    }
+
+  {
+    unsigned old_generation_count;
+    HANDLE event;
+
+    EnterCriticalSection (&cond->lock);
+    /* Increment waiters_count,
+       and get a copy of the current wait_generation_count.  */
+    cond->waiters_count++;
+    old_generation_count = cond->wait_generation_count;
+    event = cond->event;
+    LeaveCriticalSection (&cond->lock);
+
+    {
+      /* Now release the lock and let any other thread take it.  */
+      int err = glthread_lock_unlock (lock);
+      if (err != 0)
+	{
+	  EnterCriticalSection (&cond->lock);
+	  cond->waiters_count--;
+	  LeaveCriticalSection (&cond->lock);
+	  return err;
+	}
+
+      {
+	/* Wait until another thread signals this event or until the abstime
+	   passes.  */
+	DWORD result;
+
+	for (;;)
+	  {
+	    DWORD timeout;
+	    bool wait_done;
+
+	    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_OBJECT_0)
+	      break;
+	    /* Distingish intended from spurious wakeups.  */
+	    EnterCriticalSection (&cond->lock);
+	    wait_done =
+	      (cond->release_count > 0
+	       && cond->wait_generation_count != old_generation_count);
+	    LeaveCriticalSection (&cond->lock);
+	    if (wait_done)
+	      break;
+	  }
+
+	/* Take the lock again.  */
+	err = glthread_lock_lock (lock);
+
+	/* Do the bookkeeping.  */
+	EnterCriticalSection (&cond->lock);
+	cond->waiters_count--;
+	if (result == WAIT_OBJECT_0)
+	  {
+	    /* The wait terminated because the event was signaled.
+	       Acknowledge the receipt.  */
+	    if (--cond->release_count == 0)
+	      {
+		/* The last waiting thread to be notified has to reset
+		   the event.  */
+		ResetEvent (cond->event);
+	      }
+	  }
+	LeaveCriticalSection (&cond->lock);
+
+	return (err ? err :
+		ret == WAIT_OBJECT_0 ? 0 :
+		ret == 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."
+     Also, if  waiters_count == release_count > 0, all blocked threads will
+     be unblocked soon anyway; do nothing in this case as well.  */
+  if (cond->waiters_count > cond->release_count)
+    {
+      /* Signal the manual-reset event.  */
+      SetEvent (cond->event);
+      /* ... and reset it is soon as one of the currently waiting threads has
+	 acknowledged the receipt of the signal.  */
+      cond->release_count++;
+      cond->wait_generation_count++;
+    }
+  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."  */
+  if (cond->waiters_count > 0)
+    {
+      /* Signal the manual-reset event.  */
+      SetEvent (cond->event);
+      /* ... and reset it only after all currently waiting threads have
+	 acknowledged the receipt of the signal.  */
+      cond->release_count = cond->waiters_count;
+      cond->wait_generation_count++;
+    }
+  LeaveCriticalSection (&cond->lock);
+
+  return 0;
+}
+
+int
+glthread_cond_destroy_func (gl_cond_t *cond)
+{
+  if (!cond->guard.done)
+    return EINVAL;
+  if (cond->waiters_count > 0)
+    return EBUSY;
+  CloseHandle (cond->event);
+  DeleteCriticalSection (&cond->lock);
+  cond->guard.done = 0;
+  return 0;
+}
+
+#endif
+
+/* ========================================================================= */
--- a/lib/glthread/cond.h
+++ b/lib/glthread/cond.h
@@ -269,7 +269,64 @@
 
 /* ========================================================================= */
 
-#if !(USE_POSIX_THREADS || USE_PTH_THREADS || USE_SOLARIS_THREADS)
+#if USE_WIN32_THREADS
+
+# include <windows.h>
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/* -------------------------- gl_cond_t datatype -------------------------- */
+
+typedef struct
+        {
+          gl_spinlock_t guard; /* protects the initialization */
+          CRITICAL_SECTION lock; /* protects the remaining fields */
+          HANDLE event; /* an event configured for manual reset */
+          unsigned int waiters_count; /* number of threads currently waiting */
+          unsigned int release_count; /* number of threads that are currently
+                                         being notified but have not yet
+                                         acknowledged. Always
+                                         release_count <= waiters_count */
+          unsigned int wait_generation_count; /* incremented each time a signal
+                                                 or broadcast is performed */
+        }
+        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 } }
+# define glthread_cond_init(COND) \
+    glthread_cond_init_func (COND)
+# define glthread_cond_wait(COND, LOCK) \
+    glthread_cond_wait_func (COND, LOCK)
+# define glthread_cond_timedwait(COND, LOCK, ABSTIME) \
+    glthread_cond_timedwait_func (COND, LOCK, ABSTIME)
+# define glthread_cond_signal(COND) \
+    glthread_cond_signal_func (COND)
+# define glthread_cond_broadcast(COND) \
+    glthread_cond_broadcast_func (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);
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif
+
+/* ========================================================================= */
+
+#if !(USE_POSIX_THREADS || USE_PTH_THREADS || USE_SOLARIS_THREADS || USE_WIN32_THREADS)
 
 /* Provide dummy implementation if threads are not supported.  */
 
--- a/modules/cond
+++ b/modules/cond
@@ -12,6 +12,7 @@
 errno
 stdbool
 time
+gettimeofday
 
 configure.ac:
 gl_COND