[PATCH] kernel32: implement condition variables

Marcus Meissner marcus at jet.franken.de
Mon Jan 14 02:03:42 CST 2013


From: Marcus Meissner <meissner at suse.de>

Hi,

Next try after Nikolays and Alexandres comments.

Back to using local data via private Ptr in the condition
variable. Almost all implementations do it this way, so
I am assuming that they do not keep process wide state
elsewhere.

This implementation is modeled after glibc nptl/DESIGN-condvar.txt.

Currently uses a semaphore instead of a more lightweight method like
e.g. futexes. Futex implementation can be added however.

I also included a link to a long page of various implementations
and their cave-ats. I could remodel it to fit one of those better.

Ciao, Marcus
---
 dlls/kernel32/kernel32.spec |    4 ++
 dlls/kernel32/sync.c        |  135 +++++++++++++++++++++++++++++++++++++++++++
 dlls/kernel32/tests/sync.c  |    6 +-
 3 files changed, 143 insertions(+), 2 deletions(-)

diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec
index 0bd1adc..69de820 100644
--- a/dlls/kernel32/kernel32.spec
+++ b/dlls/kernel32/kernel32.spec
@@ -745,6 +745,7 @@
 @ stdcall IdnToUnicode(long wstr long ptr long)
 @ stdcall InitAtomTable(long)
 @ stdcall InitializeSRWLock(ptr)
+@ stdcall InitializeConditionVariable(ptr)
 @ stdcall InitializeCriticalSection(ptr)
 @ stdcall InitializeCriticalSectionAndSpinCount(ptr long)
 @ stdcall InitializeCriticalSectionEx(ptr long long)
@@ -1187,6 +1188,7 @@
 @ stdcall SignalObjectAndWait(long long long long)
 @ stdcall SizeofResource(long long)
 @ stdcall Sleep(long)
+@ stdcall SleepConditionVariableCS(ptr ptr long)
 @ stdcall SleepEx(long long)
 @ stdcall SuspendThread(long)
 @ stdcall SwitchToFiber(ptr)
@@ -1256,6 +1258,8 @@
 @ stdcall WaitForSingleObjectEx(long long long)
 @ stdcall WaitNamedPipeA (str long)
 @ stdcall WaitNamedPipeW (wstr long)
+@ stdcall WakeAllConditionVariable (ptr)
+@ stdcall WakeConditionVariable (ptr)
 @ stdcall WerRegisterFile(wstr long long)
 @ stdcall WerRegisterMemoryBlock(ptr long)
 @ stdcall WerRegisterRuntimeExceptionModule(wstr ptr)
diff --git a/dlls/kernel32/sync.c b/dlls/kernel32/sync.c
index f8c951b..d06613b 100644
--- a/dlls/kernel32/sync.c
+++ b/dlls/kernel32/sync.c
@@ -20,6 +20,7 @@
 
 #include "config.h"
 #include "wine/port.h"
+#include "wine/list.h"
 
 #include <string.h>
 #ifdef HAVE_UNISTD_H
@@ -2299,3 +2300,137 @@ __ASM_STDCALL_FUNC(InterlockedDecrement, 4,
                   "ret $4")
 
 #endif  /* __i386__ */
+
+/* See http://locklessinc.com/articles/mutex_cv_futex/ for a futex implementation
+ * See http://www.cs.wustl.edu/~schmidt/win32-cv-1.html for various implementations
+ * of pthread_cond_* with Win32 primitives.
+ * See glibc/nptl/DESIGN-condvar.txt for the draft of this implementation */ 
+
+struct _condvar_intern {
+        RTL_CRITICAL_SECTION    crit;
+        HANDLE                  sem;
+        DWORD           waiters;
+        ULONGLONG       total_seq;
+        ULONGLONG       wakeup_seq;
+        ULONGLONG       woken_seq;
+        DWORD           broadcast_seq;
+};
+
+/**********************************************************************
+ *           InitializeConditionVariable     (KERNEL32.@)
+ */
+VOID WINAPI InitializeConditionVariable(PCONDITION_VARIABLE variable)
+{
+    variable->Ptr = NULL;
+}
+
+static struct _condvar_intern *
+_alloc_condvar(PCONDITION_VARIABLE variable) {
+    struct _condvar_intern *condvar;
+
+    condvar = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(struct _condvar_intern));
+    if (!condvar) return NULL;
+    InitializeCriticalSection (&condvar->crit);
+    condvar->crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ConditionVariable.cs");
+    return condvar;
+}
+
+/**********************************************************************
+ *           WakeConditionVariable     (KERNEL32.@)
+ */
+VOID WINAPI WakeConditionVariable(PCONDITION_VARIABLE variable)
+{
+    struct _condvar_intern *var = variable->Ptr;
+
+    if (!var) return; /* if no one is waiting - nothing to do */
+    RtlEnterCriticalSection(&var->crit);
+    if (var->total_seq > var->wakeup_seq) {
+        /* we have waiting folks */
+        ++var->wakeup_seq;
+        ReleaseSemaphore (var->sem, 1, 0);
+    }
+    RtlLeaveCriticalSection(&var->crit);
+}
+
+/**********************************************************************
+ *           WakeAllConditionVariable     (KERNEL32.@)
+ */
+VOID WINAPI WakeAllConditionVariable(PCONDITION_VARIABLE variable)
+{
+    struct _condvar_intern *var = variable->Ptr;
+
+    if (!var) return; /* if no one is waiting - nothing to do */
+    RtlEnterCriticalSection(&var->crit);
+    if (var->total_seq > var->wakeup_seq) {
+        /* we have waiters ... wake them all */
+        var->wakeup_seq = var->total_seq;
+        var->woken_seq  = var->total_seq;
+        ++var->broadcast_seq;
+        ReleaseSemaphore (var->sem, var->waiters, 0);
+    }
+    RtlLeaveCriticalSection(&var->crit);
+}
+
+/**********************************************************************
+ *           SleepConditionVariableCS     (KERNEL32.@)
+ */
+BOOL WINAPI SleepConditionVariableCS(PCONDITION_VARIABLE variable,
+    LPCRITICAL_SECTION crit, DWORD timeout
+)
+{
+    struct _condvar_intern *var = variable->Ptr , *newvar;
+    LONG val, seq, bseq;
+    BOOL ret = TRUE;
+
+    /* FIXME("(%p, %p, %d): semi-correct stub.\n", variable, crit, timeout); */
+
+    if (!var)
+        var = variable->Ptr = _alloc_condvar(variable);
+
+    RtlEnterCriticalSection(&var->crit);
+    RtlLeaveCriticalSection(crit);
+
+    ++var->total_seq;
+    val = seq = var->wakeup_seq;
+    bseq = var->broadcast_seq;
+
+    if (!var->sem)
+        NtCreateSemaphore( &var->sem, SEMAPHORE_ALL_ACCESS, NULL, 0, 1 );
+
+    while (1) {
+        DWORD res;
+
+        ++var->waiters;
+        RtlLeaveCriticalSection(&var->crit);
+
+        res = WaitForSingleObject(var->sem, timeout);
+
+        RtlEnterCriticalSection(&var->crit);
+        --var->waiters;
+        if (bseq != var->broadcast_seq) {
+            /* A broadcast happened, so we are done waiting. */
+            goto bc_out; /* to avoid incrementing woken_seq */
+        }
+        val = var->wakeup_seq;
+        if ((val != seq) && (var->woken_seq != val)) {
+            break;
+        }
+        if (res == WAIT_TIMEOUT) {
+            var->wakeup_seq++;
+            ret = FALSE;
+            /* expected from caller, but not set by WaitForSingleObject */
+            SetLastError(ERROR_TIMEOUT);
+            break;
+        }
+	/* We get here if we were woken but not at the right place in
+	 * the queue. We need to release the semaphore again to have to
+	 * release/lock numbers correct.
+         * FIXME: This might wake us immediately again */
+        ReleaseSemaphore(var->sem, 1, NULL);
+    }
+    var->woken_seq++;
+bc_out:
+    RtlLeaveCriticalSection(&var->crit);
+    RtlEnterCriticalSection(crit);
+    return ret;
+}
diff --git a/dlls/kernel32/tests/sync.c b/dlls/kernel32/tests/sync.c
index 0ab82f0..3bfd640 100644
--- a/dlls/kernel32/tests/sync.c
+++ b/dlls/kernel32/tests/sync.c
@@ -1317,7 +1317,7 @@ static void test_condvars(void)
     if (!pInitializeConditionVariable) {
         /* function is not yet in XP, only in newer Windows */
         /* and not yet implemented in Wine for some days/weeks */
-        todo_wine win_skip("no condition variable support.\n");
+        win_skip("no condition variable support.\n");
         return;
     }
 
@@ -1349,7 +1349,9 @@ static void test_condvars(void)
     pWakeAllConditionVariable (&buffernotfull);
     pWakeAllConditionVariable (&buffernotempty);
 
-    ok(buffernotfull.Ptr == NULL, "buffernotfull.Ptr is %p\n", buffernotfull.Ptr);
+    /* (mostly an implementation detail)
+     * ok(buffernotfull.Ptr == NULL, "buffernotfull.Ptr is %p\n", buffernotfull.Ptr);
+     */
 
     WaitForSingleObject(hp1, 1000);
     WaitForSingleObject(hp2, 1000);
-- 
1.7.10.4




More information about the wine-patches mailing list