From a2dc01d60e8b0fc32b98fe885068720492de1406 Mon Sep 17 00:00:00 2001
From: Benoit Germain <b n t DOT g e r m a i n AT g m a i l DOT c o m>
Date: Sat, 20 Jul 2013 09:25:39 +0200
Subject: Ditch PulseEvent usage on WIN32 builds

---
 CHANGES         |   3 ++
 src/threading.c | 155 +++++++++++++++++++++++++++++++-------------------------
 src/threading.h |  12 ++++-
 3 files changed, 100 insertions(+), 70 deletions(-)

diff --git a/CHANGES b/CHANGES
index e3312d5..538acba 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,8 @@
 CHANGES:
 
+CHANGE 64: BGe 20-Jul-13
+   * WIN32 builds against pre-Vista versions no longer use PulseEvent to fix occasional hangs when a wake event is missed
+
 CHANGE 63: BGe 20-May-13
    * version 3.6.2
    * WIN32 builds use condition variables instead of PulseEvent() when available.
diff --git a/src/threading.c b/src/threading.c
index 43e5f46..1e4fc92 100644
--- a/src/threading.c
+++ b/src/threading.c
@@ -358,79 +358,98 @@ bool_t THREAD_WAIT_IMPL( THREAD_T *ref, double secs)
 #endif // !__GNUC__
 	}
 
-#if WINVER <= 0x0400 // Windows NT4: Use PulseEvent, although it is unreliable, but then...
+#if WINVER <= 0x0400 // Windows NT4
 
-  //
-  void SIGNAL_INIT( SIGNAL_T *ref ) {
-    // 'manual reset' event type selected, to be able to wake up all the
-    // waiting threads.
-    //
-    HANDLE h= CreateEvent( NULL,    // security attributes
-                           TRUE,    // TRUE: manual event
-                           FALSE,   // Initial state
-                           NULL );  // name
+	void SIGNAL_INIT( SIGNAL_T* ref)
+	{
+		InitializeCriticalSection( &ref->signalCS);
+		InitializeCriticalSection( &ref->countCS);
+		if( 0 == (ref->waitEvent = CreateEvent( 0, TRUE, FALSE, 0)))     // manual-reset
+			FAIL( "CreateEvent", GetLastError());
+		if( 0 == (ref->waitDoneEvent = CreateEvent( 0, FALSE, FALSE, 0)))    // auto-reset
+			FAIL( "CreateEvent", GetLastError());
+		ref->waitersCount = 0;
+	}
 
-    if (h == NULL) FAIL( "CreateEvent", GetLastError() );
-    *ref= h;
-  }
-  void SIGNAL_FREE( SIGNAL_T *ref ) {
-    if (!CloseHandle(*ref)) FAIL( "CloseHandle (event)", GetLastError() );
-    *ref= NULL;
-  }
-  //
-  bool_t SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu_ref, time_d abs_secs ) {
-    DWORD rc;
-    long ms;
-    
-    if (abs_secs<0.0)
-        ms= INFINITE;
-    else if (abs_secs==0.0)
-        ms= 0;
-    else {
-        ms= (long) ((abs_secs - now_secs())*1000.0 + 0.5);
-        
-        // If the time already passed, still try once (ms==0). A short timeout
-        // may have turned negative or 0 because of the two time samples done.
-        //
-        if (ms<0) ms= 0;
-    }
+	void SIGNAL_FREE( SIGNAL_T* ref)
+	{
+		CloseHandle( ref->waitDoneEvent);
+		CloseHandle( ref->waitEvent);
+		DeleteCriticalSection( &ref->countCS);
+		DeleteCriticalSection( &ref->signalCS);
+	}
 
-    // Unlock and start a wait, atomically (like condition variables do)
-    //
-    rc= SignalObjectAndWait( *mu_ref,   // "object to signal" (unlock)
-                             *ref,      // "object to wait on"
-                             ms,
-                             FALSE );   // not alertable
+	bool_t SIGNAL_WAIT( SIGNAL_T* ref, MUTEX_T* mu_ref, time_d abs_secs)
+	{
+		DWORD errc;
+		DWORD ms;
 
-    // All waiting locks are woken here; each competes for the lock in turn.
-    //
-    // Note: We must get the lock even if we've timed out; it makes upper
-    //       level code equivalent to how PThread does it.
-    //
-    MUTEX_LOCK(mu_ref);
+		if( abs_secs < 0.0)
+			ms = INFINITE;
+		else if( abs_secs == 0.0)
+			ms = 0;
+		else 
+		{
+			time_d msd = (abs_secs - now_secs()) * 1000.0 + 0.5;
+			// If the time already passed, still try once (ms==0). A short timeout
+			// may have turned negative or 0 because of the two time samples done.
+			ms = msd <= 0.0 ? 0 : (DWORD)msd;
+		}
 
-    if (rc==WAIT_TIMEOUT) return FALSE;
-    if (rc!=0) FAIL( "SignalObjectAndWait", rc );
-    return TRUE;
-  }
-  void SIGNAL_ALL( SIGNAL_T *ref ) {
-/* 
- * MSDN tries to scare that 'PulseEvent' is bad, unreliable and should not be
- * used. Use condition variables instead (wow, they have that!?!); which will
- * ONLY WORK on Vista and 2008 Server, it seems... so MS, isn't it.
- * 
- * I refuse to believe that; using 'PulseEvent' is probably just as good as
- * using Windows (XP) in the first place. Just don't use APC's (asynchronous
- * process calls) in your C side coding.
- */
-    // PulseEvent on manual event:
-    //
-    // Release ALL threads waiting for it (and go instantly back to unsignalled
-    // status = future threads to start a wait will wait)
-    //
-    if (!PulseEvent( *ref ))
-        FAIL( "PulseEvent", GetLastError() );
-  }
+		EnterCriticalSection( &ref->signalCS);
+		EnterCriticalSection( &ref->countCS);
+		++ ref->waitersCount;
+		LeaveCriticalSection( &ref->countCS);
+		LeaveCriticalSection( &ref->signalCS);
+
+		errc = SignalObjectAndWait( *mu_ref, ref->waitEvent, ms, FALSE);
+
+		EnterCriticalSection( &ref->countCS);
+		if( 0 == -- ref->waitersCount)
+		{
+			// we're the last one leaving...
+			ResetEvent( ref->waitEvent);
+			SetEvent( ref->waitDoneEvent);
+		}
+		LeaveCriticalSection( &ref->countCS);
+		MUTEX_LOCK( mu_ref);
+
+		switch( errc)
+		{
+			case WAIT_TIMEOUT:
+			return FALSE;
+			case WAIT_OBJECT_0:
+			return TRUE;
+		}
+
+		FAIL( "SignalObjectAndWait", GetLastError());
+		return FALSE;
+	}
+
+	void SIGNAL_ALL( SIGNAL_T* ref)
+	{
+		DWORD errc = WAIT_OBJECT_0;
+
+		EnterCriticalSection( &ref->signalCS);
+		EnterCriticalSection( &ref->countCS);
+
+		if( ref->waitersCount > 0)
+		{
+			ResetEvent( ref->waitDoneEvent);
+			SetEvent( ref->waitEvent);
+			LeaveCriticalSection( &ref->countCS);
+			errc = WaitForSingleObject( ref->waitDoneEvent, INFINITE);
+		}
+		else
+		{
+			LeaveCriticalSection( &ref->countCS);
+		}
+
+		LeaveCriticalSection( &ref->signalCS);
+
+		if( WAIT_OBJECT_0 != errc)
+			FAIL( "WaitForSingleObject", GetLastError());
+	}
 
 #else // Windows Vista and above: condition variables exist, use them
 
diff --git a/src/threading.h b/src/threading.h
index e559910..7d94f26 100644
--- a/src/threading.h
+++ b/src/threading.h
@@ -64,7 +64,7 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED };
   #else // !PLATFORM_XBOX
     #define WIN32_LEAN_AND_MEAN
     // 'SignalObjectAndWait' needs this (targets Windows 2000 and above)
-    //#define _WIN32_WINNT 0x0500 Let the compiler decide depending on the host OS
+    #define _WIN32_WINNT 0x0400
     #include <windows.h>
   #endif // !PLATFORM_XBOX
   #include <process.h>
@@ -76,8 +76,16 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED };
   //
 
 	#if WINVER <= 0x0400 // Windows NT4: use a signal
+	typedef struct
+	{
+		CRITICAL_SECTION    signalCS;
+		CRITICAL_SECTION    countCS;
+		HANDLE      waitEvent;
+		HANDLE      waitDoneEvent;
+		LONG        waitersCount;
+	} SIGNAL_T;
+
 
-	#define SIGNAL_T HANDLE
 	#define MUTEX_T HANDLE
 	void MUTEX_INIT( MUTEX_T* ref);
 	void MUTEX_FREE( MUTEX_T* ref);
-- 
cgit v1.2.3-55-g6feb