summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/synch/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--winpr/libwinpr/synch/test/CMakeLists.txt41
-rw-r--r--winpr/libwinpr/synch/test/TestSynchAPC.c173
-rw-r--r--winpr/libwinpr/synch/test/TestSynchBarrier.c257
-rw-r--r--winpr/libwinpr/synch/test/TestSynchCritical.c363
-rw-r--r--winpr/libwinpr/synch/test/TestSynchEvent.c94
-rw-r--r--winpr/libwinpr/synch/test/TestSynchInit.c156
-rw-r--r--winpr/libwinpr/synch/test/TestSynchMultipleThreads.c243
-rw-r--r--winpr/libwinpr/synch/test/TestSynchMutex.c258
-rw-r--r--winpr/libwinpr/synch/test/TestSynchSemaphore.c21
-rw-r--r--winpr/libwinpr/synch/test/TestSynchThread.c131
-rw-r--r--winpr/libwinpr/synch/test/TestSynchTimerQueue.c125
-rw-r--r--winpr/libwinpr/synch/test/TestSynchWaitableTimer.c83
-rw-r--r--winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c92
13 files changed, 2037 insertions, 0 deletions
diff --git a/winpr/libwinpr/synch/test/CMakeLists.txt b/winpr/libwinpr/synch/test/CMakeLists.txt
new file mode 100644
index 0000000..66d15c6
--- /dev/null
+++ b/winpr/libwinpr/synch/test/CMakeLists.txt
@@ -0,0 +1,41 @@
+
+set(MODULE_NAME "TestSynch")
+set(MODULE_PREFIX "TEST_SYNCH")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestSynchInit.c
+ TestSynchEvent.c
+ TestSynchMutex.c
+ TestSynchBarrier.c
+ TestSynchCritical.c
+ TestSynchSemaphore.c
+ TestSynchThread.c
+ # TestSynchMultipleThreads.c
+ TestSynchTimerQueue.c
+ TestSynchWaitableTimer.c
+ TestSynchWaitableTimerAPC.c
+ TestSynchAPC.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+if(FREEBSD)
+ include_directories(${EPOLLSHIM_INCLUDE_DIR})
+endif()
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/synch/test/TestSynchAPC.c b/winpr/libwinpr/synch/test/TestSynchAPC.c
new file mode 100644
index 0000000..d6239d8
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchAPC.c
@@ -0,0 +1,173 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * TestSyncAPC
+ *
+ * Copyright 2021 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <winpr/wtypes.h>
+#include <winpr/thread.h>
+#include <winpr/synch.h>
+
+typedef struct
+{
+ BOOL error;
+ BOOL called;
+} UserApcArg;
+
+static void CALLBACK userApc(ULONG_PTR arg)
+{
+ UserApcArg* userArg = (UserApcArg*)arg;
+ userArg->called = TRUE;
+}
+
+static DWORD WINAPI uncleanThread(LPVOID lpThreadParameter)
+{
+ /* this thread post an APC that will never get executed */
+ UserApcArg* userArg = (UserApcArg*)lpThreadParameter;
+ if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)lpThreadParameter))
+ {
+ userArg->error = TRUE;
+ return 1;
+ }
+
+ return 0;
+}
+
+static DWORD WINAPI cleanThread(LPVOID lpThreadParameter)
+{
+ Sleep(500);
+
+ SleepEx(500, TRUE);
+ return 0;
+}
+
+typedef struct
+{
+ HANDLE timer1;
+ DWORD timer1Calls;
+ HANDLE timer2;
+ DWORD timer2Calls;
+ BOOL endTest;
+} UncleanCloseData;
+
+static VOID CALLBACK Timer1APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ UncleanCloseData* data = (UncleanCloseData*)lpArg;
+ data->timer1Calls++;
+ CloseHandle(data->timer2);
+ data->endTest = TRUE;
+}
+
+static VOID CALLBACK Timer2APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ UncleanCloseData* data = (UncleanCloseData*)lpArg;
+ data->timer2Calls++;
+}
+
+static DWORD /*WINAPI*/ closeHandleTest(LPVOID lpThreadParameter)
+{
+ LARGE_INTEGER dueTime;
+ UncleanCloseData* data = (UncleanCloseData*)lpThreadParameter;
+ data->endTest = FALSE;
+
+ dueTime.QuadPart = -500;
+ if (!SetWaitableTimer(data->timer1, &dueTime, 0, Timer1APCProc, lpThreadParameter, FALSE))
+ return 1;
+
+ dueTime.QuadPart = -900;
+ if (!SetWaitableTimer(data->timer2, &dueTime, 0, Timer2APCProc, lpThreadParameter, FALSE))
+ return 1;
+
+ while (!data->endTest)
+ {
+ SleepEx(100, TRUE);
+ }
+ return 0;
+}
+
+int TestSynchAPC(int argc, char* argv[])
+{
+ HANDLE thread = NULL;
+ UserApcArg userApcArg;
+
+ userApcArg.error = FALSE;
+ userApcArg.called = FALSE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* first post an APC and check it is executed during a SleepEx */
+ if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)&userApcArg))
+ return 1;
+
+ if (SleepEx(100, FALSE) != 0)
+ return 2;
+
+ if (SleepEx(100, TRUE) != WAIT_IO_COMPLETION)
+ return 3;
+
+ if (!userApcArg.called)
+ return 4;
+
+ userApcArg.called = FALSE;
+
+ /* test that the APC is cleaned up even when not called */
+ thread = CreateThread(NULL, 0, uncleanThread, &userApcArg, 0, NULL);
+ if (!thread)
+ return 10;
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (userApcArg.called || userApcArg.error)
+ return 11;
+
+ /* test a remote APC queuing */
+ thread = CreateThread(NULL, 0, cleanThread, &userApcArg, 0, NULL);
+ if (!thread)
+ return 20;
+
+ if (!QueueUserAPC((PAPCFUNC)userApc, thread, (ULONG_PTR)&userApcArg))
+ return 21;
+
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (!userApcArg.called)
+ return 22;
+
+#if 0
+ /* test cleanup of timer completions */
+ memset(&uncleanCloseData, 0, sizeof(uncleanCloseData));
+ uncleanCloseData.timer1 = CreateWaitableTimerA(NULL, FALSE, NULL);
+ if (!uncleanCloseData.timer1)
+ return 31;
+
+ uncleanCloseData.timer2 = CreateWaitableTimerA(NULL, FALSE, NULL);
+ if (!uncleanCloseData.timer2)
+ return 32;
+
+ thread = CreateThread(NULL, 0, closeHandleTest, &uncleanCloseData, 0, NULL);
+ if (!thread)
+ return 33;
+
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (uncleanCloseData.timer1Calls != 1 || uncleanCloseData.timer2Calls != 0)
+ return 34;
+ CloseHandle(uncleanCloseData.timer1);
+#endif
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchBarrier.c b/winpr/libwinpr/synch/test/TestSynchBarrier.c
new file mode 100644
index 0000000..b1c91c9
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchBarrier.c
@@ -0,0 +1,257 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+#include <winpr/sysinfo.h>
+
+#include "../synch.h"
+
+static SYNCHRONIZATION_BARRIER gBarrier;
+static HANDLE gStartEvent = NULL;
+static LONG gErrorCount = 0;
+
+#define MAX_SLEEP_MS 22
+
+struct test_params
+{
+ LONG threadCount;
+ LONG trueCount;
+ LONG falseCount;
+ DWORD loops;
+ DWORD flags;
+};
+
+static DWORD WINAPI test_synch_barrier_thread(LPVOID lpParam)
+{
+ BOOL status = FALSE;
+ struct test_params* p = (struct test_params*)lpParam;
+
+ InterlockedIncrement(&p->threadCount);
+
+ // printf("Thread #%03u entered.\n", tnum);
+
+ /* wait for start event from main */
+ if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ InterlockedIncrement(&gErrorCount);
+ goto out;
+ }
+
+ // printf("Thread #%03u unblocked.\n", tnum);
+
+ for (DWORD i = 0; i < p->loops && gErrorCount == 0; i++)
+ {
+ /* simulate different execution times before the barrier */
+ Sleep(1 + abs((rand() % MAX_SLEEP_MS)));
+ status = EnterSynchronizationBarrier(&gBarrier, p->flags);
+
+ // printf("Thread #%03u status: %s\n", tnum, status ? "TRUE" : "FALSE");
+ if (status)
+ InterlockedIncrement(&p->trueCount);
+ else
+ InterlockedIncrement(&p->falseCount);
+ }
+
+out:
+ // printf("Thread #%03u leaving.\n", tnum);
+ return 0;
+}
+
+static BOOL TestSynchBarrierWithFlags(DWORD dwFlags, DWORD dwThreads, DWORD dwLoops)
+{
+ HANDLE* threads = NULL;
+ struct test_params p;
+ DWORD dwStatus = 0;
+ DWORD expectedTrueCount = 0;
+ DWORD expectedFalseCount = 0;
+ p.threadCount = 0;
+ p.trueCount = 0;
+ p.falseCount = 0;
+ p.loops = dwLoops;
+ p.flags = dwFlags;
+ expectedTrueCount = dwLoops;
+ expectedFalseCount = dwLoops * (dwThreads - 1);
+ printf("%s: >> Testing with flags 0x%08" PRIx32 ". Using %" PRIu32
+ " threads performing %" PRIu32 " loops\n",
+ __func__, dwFlags, dwThreads, dwLoops);
+
+ if (!(threads = calloc(dwThreads, sizeof(HANDLE))))
+ {
+ printf("%s: error allocatin thread array memory\n", __func__);
+ return FALSE;
+ }
+
+ if (!InitializeSynchronizationBarrier(&gBarrier, dwThreads, -1))
+ {
+ printf("%s: InitializeSynchronizationBarrier failed. GetLastError() = 0x%08x", __func__,
+ GetLastError());
+ free(threads);
+ DeleteSynchronizationBarrier(&gBarrier);
+ return FALSE;
+ }
+
+ if (!(gStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("%s: CreateEvent failed with error 0x%08x", __func__, GetLastError());
+ free(threads);
+ DeleteSynchronizationBarrier(&gBarrier);
+ return FALSE;
+ }
+
+ DWORD i = 0;
+ for (; i < dwThreads; i++)
+ {
+ if (!(threads[i] = CreateThread(NULL, 0, test_synch_barrier_thread, &p, 0, NULL)))
+ {
+ printf("%s: CreateThread failed for thread #%" PRIu32 " with error 0x%08x\n", __func__,
+ i, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ break;
+ }
+ }
+
+ if (i > 0)
+ {
+ if (!SetEvent(gStartEvent))
+ {
+ printf("%s: SetEvent(gStartEvent) failed with error = 0x%08x)\n", __func__,
+ GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ while (i--)
+ {
+ if (WAIT_OBJECT_0 != (dwStatus = WaitForSingleObject(threads[i], INFINITE)))
+ {
+ printf("%s: WaitForSingleObject(thread[%" PRIu32 "] unexpectedly returned %" PRIu32
+ " (error = 0x%08x)\n",
+ __func__, i, dwStatus, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ if (!CloseHandle(threads[i]))
+ {
+ printf("%s: CloseHandle(thread[%" PRIu32 "]) failed with error = 0x%08x)\n",
+ __func__, i, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+ }
+ }
+
+ free(threads);
+
+ if (!CloseHandle(gStartEvent))
+ {
+ printf("%s: CloseHandle(gStartEvent) failed with error = 0x%08x)\n", __func__,
+ GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ DeleteSynchronizationBarrier(&gBarrier);
+
+ if (p.threadCount != (INT64)dwThreads)
+ InterlockedIncrement(&gErrorCount);
+
+ if (p.trueCount != (INT64)expectedTrueCount)
+ InterlockedIncrement(&gErrorCount);
+
+ if (p.falseCount != (INT64)expectedFalseCount)
+ InterlockedIncrement(&gErrorCount);
+
+ printf("%s: error count: %" PRId32 "\n", __func__, gErrorCount);
+ printf("%s: thread count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.threadCount,
+ dwThreads);
+ printf("%s: true count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.trueCount,
+ expectedTrueCount);
+ printf("%s: false count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.falseCount,
+ expectedFalseCount);
+
+ if (gErrorCount > 0)
+ {
+ printf("%s: Error test failed with %" PRId32 " reported errors\n", __func__, gErrorCount);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSynchBarrier(int argc, char* argv[])
+{
+ SYSTEM_INFO sysinfo;
+ DWORD dwMaxThreads = 0;
+ DWORD dwMinThreads = 0;
+ DWORD dwNumLoops = 10;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetNativeSystemInfo(&sysinfo);
+ printf("%s: Number of processors: %" PRIu32 "\n", __func__, sysinfo.dwNumberOfProcessors);
+ dwMinThreads = sysinfo.dwNumberOfProcessors;
+ dwMaxThreads = sysinfo.dwNumberOfProcessors * 4;
+
+ if (dwMaxThreads > 32)
+ dwMaxThreads = 32;
+
+ /* Test invalid parameters */
+ if (InitializeSynchronizationBarrier(&gBarrier, 0, -1))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = 0\n",
+ __func__);
+ return -1;
+ }
+
+ if (InitializeSynchronizationBarrier(&gBarrier, -1, -1))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (InitializeSynchronizationBarrier(&gBarrier, 1, -2))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lSpinCount = -2\n",
+ __func__);
+ return -1;
+ }
+
+ /* Functional tests */
+
+ if (!TestSynchBarrierWithFlags(0, dwMaxThreads, dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(0) unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY, dwMinThreads,
+ dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY) "
+ "unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY, dwMaxThreads,
+ dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) "
+ "unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ printf("%s: Test successfully completed\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchCritical.c b/winpr/libwinpr/synch/test/TestSynchCritical.c
new file mode 100644
index 0000000..9d56356
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchCritical.c
@@ -0,0 +1,363 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#define TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS 50
+#define TEST_SYNC_CRITICAL_TEST1_RUNS 4
+
+static CRITICAL_SECTION critical;
+static LONG gTestValueVulnerable = 0;
+static LONG gTestValueSerialized = 0;
+
+static BOOL TestSynchCritical_TriggerAndCheckRaceCondition(HANDLE OwningThread, LONG RecursionCount)
+{
+ /* if called unprotected this will hopefully trigger a race condition ... */
+ gTestValueVulnerable++;
+
+ if (critical.OwningThread != OwningThread)
+ {
+ printf("CriticalSection failure: OwningThread is invalid\n");
+ return FALSE;
+ }
+ if (critical.RecursionCount != RecursionCount)
+ {
+ printf("CriticalSection failure: RecursionCount is invalid\n");
+ return FALSE;
+ }
+
+ /* ... which we try to detect using the serialized counter */
+ if (gTestValueVulnerable != InterlockedIncrement(&gTestValueSerialized))
+ {
+ printf("CriticalSection failure: Data corruption detected\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* this thread function shall increment the global dwTestValue until the PBOOL passsed in arg is
+ * FALSE */
+static DWORD WINAPI TestSynchCritical_Test1(LPVOID arg)
+{
+ int rc = 0;
+ HANDLE hThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ PBOOL pbContinueRunning = (PBOOL)arg;
+
+ while (*pbContinueRunning)
+ {
+ EnterCriticalSection(&critical);
+
+ rc = 1;
+
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
+ return 1;
+
+ /* add some random recursion level */
+ int j = rand() % 5;
+ for (int i = 0; i < j; i++)
+ {
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc++))
+ return 2;
+ EnterCriticalSection(&critical);
+ }
+ for (int i = 0; i < j; i++)
+ {
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc--))
+ return 2;
+ LeaveCriticalSection(&critical);
+ }
+
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
+ return 3;
+
+ LeaveCriticalSection(&critical);
+ }
+
+ return 0;
+}
+
+/* this thread function tries to call TryEnterCriticalSection while the main thread holds the lock
+ */
+static DWORD WINAPI TestSynchCritical_Test2(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+ if (TryEnterCriticalSection(&critical) == TRUE)
+ {
+ LeaveCriticalSection(&critical);
+ return 1;
+ }
+ return 0;
+}
+
+static DWORD WINAPI TestSynchCritical_Main(LPVOID arg)
+{
+ SYSTEM_INFO sysinfo;
+ DWORD dwPreviousSpinCount = 0;
+ DWORD dwSpinCount = 0;
+ DWORD dwSpinCountExpected = 0;
+ HANDLE hMainThread = NULL;
+ HANDLE* hThreads = NULL;
+ HANDLE hThread = NULL;
+ DWORD dwThreadCount = 0;
+ DWORD dwThreadExitCode = 0;
+ BOOL bTest1Running = 0;
+
+ PBOOL pbThreadTerminated = (PBOOL)arg;
+
+ GetNativeSystemInfo(&sysinfo);
+
+ hMainThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ /**
+ * Test SpinCount in SetCriticalSectionSpinCount, InitializeCriticalSectionEx and
+ * InitializeCriticalSectionAndSpinCount SpinCount must be forced to be zero on on uniprocessor
+ * systems and on systems where WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT is defined
+ */
+
+ dwSpinCount = 100;
+ InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
+ while (--dwSpinCount)
+ {
+ dwPreviousSpinCount = SetCriticalSectionSpinCount(&critical, dwSpinCount);
+ dwSpinCountExpected = 0;
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ if (sysinfo.dwNumberOfProcessors > 1)
+ dwSpinCountExpected = dwSpinCount + 1;
+#endif
+ if (dwPreviousSpinCount != dwSpinCountExpected)
+ {
+ printf("CriticalSection failure: SetCriticalSectionSpinCount returned %" PRIu32
+ " (expected: %" PRIu32 ")\n",
+ dwPreviousSpinCount, dwSpinCountExpected);
+ goto fail;
+ }
+
+ DeleteCriticalSection(&critical);
+
+ if (dwSpinCount % 2 == 0)
+ InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
+ else
+ InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
+ }
+ DeleteCriticalSection(&critical);
+
+ /**
+ * Test single-threaded recursive
+ * TryEnterCriticalSection/EnterCriticalSection/LeaveCriticalSection
+ *
+ */
+
+ InitializeCriticalSection(&critical);
+
+ int i = 0;
+ for (; i < 10; i++)
+ {
+ if (critical.RecursionCount != i)
+ {
+ printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
+ critical.RecursionCount, i);
+ goto fail;
+ }
+ if (i % 2 == 0)
+ {
+ EnterCriticalSection(&critical);
+ }
+ else
+ {
+ if (TryEnterCriticalSection(&critical) == FALSE)
+ {
+ printf("CriticalSection failure: TryEnterCriticalSection failed where it should "
+ "not.\n");
+ goto fail;
+ }
+ }
+ if (critical.OwningThread != hMainThread)
+ {
+ printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
+ i);
+ goto fail;
+ }
+ }
+ while (--i >= 0)
+ {
+ LeaveCriticalSection(&critical);
+ if (critical.RecursionCount != i)
+ {
+ printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
+ critical.RecursionCount, i);
+ goto fail;
+ }
+ if (critical.OwningThread != (HANDLE)(i ? hMainThread : NULL))
+ {
+ printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
+ i);
+ goto fail;
+ }
+ }
+ DeleteCriticalSection(&critical);
+
+ /**
+ * Test using multiple threads modifying the same value
+ */
+
+ dwThreadCount = sysinfo.dwNumberOfProcessors > 1 ? sysinfo.dwNumberOfProcessors : 2;
+
+ hThreads = (HANDLE*)calloc(dwThreadCount, sizeof(HANDLE));
+ if (!hThreads)
+ {
+ printf("Problem allocating memory\n");
+ goto fail;
+ }
+
+ for (int j = 0; j < TEST_SYNC_CRITICAL_TEST1_RUNS; j++)
+ {
+ dwSpinCount = j * 100;
+ InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
+
+ gTestValueVulnerable = 0;
+ gTestValueSerialized = 0;
+
+ /* the TestSynchCritical_Test1 threads shall run until bTest1Running is FALSE */
+ bTest1Running = TRUE;
+ for (int i = 0; i < (int)dwThreadCount; i++)
+ {
+ if (!(hThreads[i] =
+ CreateThread(NULL, 0, TestSynchCritical_Test1, &bTest1Running, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create test_1 thread #%d\n", i);
+ goto fail;
+ }
+ }
+
+ /* let it run for TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS ... */
+ Sleep(TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS);
+ bTest1Running = FALSE;
+
+ for (int i = 0; i < (int)dwThreadCount; i++)
+ {
+ if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("CriticalSection failure: Failed to wait for thread #%d\n", i);
+ goto fail;
+ }
+ GetExitCodeThread(hThreads[i], &dwThreadExitCode);
+ if (dwThreadExitCode != 0)
+ {
+ printf("CriticalSection failure: Thread #%d returned error code %" PRIu32 "\n", i,
+ dwThreadExitCode);
+ goto fail;
+ }
+ CloseHandle(hThreads[i]);
+ }
+
+ if (gTestValueVulnerable != gTestValueSerialized)
+ {
+ printf("CriticalSection failure: unexpected test value %" PRId32 " (expected %" PRId32
+ ")\n",
+ gTestValueVulnerable, gTestValueSerialized);
+ goto fail;
+ }
+
+ DeleteCriticalSection(&critical);
+ }
+
+ free(hThreads);
+
+ /**
+ * TryEnterCriticalSection in thread must fail if we hold the lock in the main thread
+ */
+
+ InitializeCriticalSection(&critical);
+
+ if (TryEnterCriticalSection(&critical) == FALSE)
+ {
+ printf("CriticalSection failure: TryEnterCriticalSection unexpectedly failed.\n");
+ goto fail;
+ }
+ /* This thread tries to call TryEnterCriticalSection which must fail */
+ if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Test2, NULL, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create test_2 thread\n");
+ goto fail;
+ }
+ if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("CriticalSection failure: Failed to wait for thread\n");
+ goto fail;
+ }
+ GetExitCodeThread(hThread, &dwThreadExitCode);
+ if (dwThreadExitCode != 0)
+ {
+ printf("CriticalSection failure: Thread returned error code %" PRIu32 "\n",
+ dwThreadExitCode);
+ goto fail;
+ }
+ CloseHandle(hThread);
+
+ *pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
+ return 0;
+
+fail:
+ *pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
+ return 1;
+}
+
+int TestSynchCritical(int argc, char* argv[])
+{
+ BOOL bThreadTerminated = FALSE;
+ HANDLE hThread = NULL;
+ DWORD dwThreadExitCode = 0;
+ DWORD dwDeadLockDetectionTimeMs = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ dwDeadLockDetectionTimeMs =
+ 2 * TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS * TEST_SYNC_CRITICAL_TEST1_RUNS;
+
+ printf("Deadlock will be assumed after %" PRIu32 " ms.\n", dwDeadLockDetectionTimeMs);
+
+ if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Main, &bThreadTerminated, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create main thread\n");
+ return -1;
+ }
+
+ /**
+ * We have to be able to detect dead locks in this test.
+ * At the time of writing winpr's WaitForSingleObject has not implemented timeout for thread
+ * wait
+ *
+ * Workaround checking the value of bThreadTerminated which is passed in the thread arg
+ */
+
+ for (DWORD i = 0; i < dwDeadLockDetectionTimeMs; i += 10)
+ {
+ if (bThreadTerminated)
+ break;
+
+ Sleep(10);
+ }
+
+ if (!bThreadTerminated)
+ {
+ printf("CriticalSection failure: Possible dead lock detected\n");
+ return -1;
+ }
+
+ GetExitCodeThread(hThread, &dwThreadExitCode);
+ CloseHandle(hThread);
+
+ if (dwThreadExitCode != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchEvent.c b/winpr/libwinpr/synch/test/TestSynchEvent.c
new file mode 100644
index 0000000..083282c
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchEvent.c
@@ -0,0 +1,94 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchEvent(int argc, char* argv[])
+{
+ HANDLE event = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ if (ResetEvent(NULL))
+ {
+ printf("ResetEvent(NULL) unexpectedly succeeded\n");
+ return -1;
+ }
+
+ if (SetEvent(NULL))
+ {
+ printf("SetEvent(NULL) unexpectedly succeeded\n");
+ return -1;
+ }
+
+ event = CreateEvent(NULL, TRUE, TRUE, NULL);
+
+ if (!event)
+ {
+ printf("CreateEvent failure\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject failure 1\n");
+ return -1;
+ }
+
+ if (!ResetEvent(event))
+ {
+ printf("ResetEvent failure with signaled event object\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, 0) != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject failure 2\n");
+ return -1;
+ }
+
+ if (!ResetEvent(event))
+ {
+ /* Note: ResetEvent must also succeed if event is currently nonsignaled */
+ printf("ResetEvent failure with nonsignaled event object\n");
+ return -1;
+ }
+
+ if (!SetEvent(event))
+ {
+ printf("SetEvent failure with nonsignaled event object\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, 0) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject failure 3\n");
+ return -1;
+ }
+
+ for (int i = 0; i < 10000; i++)
+ {
+ if (!SetEvent(event))
+ {
+ printf("SetEvent failure with signaled event object (i = %d)\n", i);
+ return -1;
+ }
+ }
+
+ if (!ResetEvent(event))
+ {
+ printf("ResetEvent failure after multiple SetEvent calls\n");
+ return -1;
+ }
+
+ /* Independent of the amount of the previous SetEvent calls, a single
+ ResetEvent must be sufficient to get into nonsignaled state */
+
+ if (WaitForSingleObject(event, 0) != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject failure 4\n");
+ return -1;
+ }
+
+ CloseHandle(event);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchInit.c b/winpr/libwinpr/synch/test/TestSynchInit.c
new file mode 100644
index 0000000..20da415
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchInit.c
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#define TEST_NUM_THREADS 100
+#define TEST_NUM_FAILURES 10
+
+static INIT_ONCE initOnceTest = INIT_ONCE_STATIC_INIT;
+
+static HANDLE hStartEvent = NULL;
+static LONG* pErrors = NULL;
+static LONG* pTestThreadFunctionCalls = NULL;
+static LONG* pTestOnceFunctionCalls = NULL;
+static LONG* pInitOnceExecuteOnceCalls = NULL;
+
+static BOOL CALLBACK TestOnceFunction(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ LONG calls = InterlockedIncrement(pTestOnceFunctionCalls) - 1;
+
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+
+ /* simulate execution time */
+ Sleep(30 + rand() % 40);
+
+ if (calls < TEST_NUM_FAILURES)
+ {
+ /* simulated error */
+ return FALSE;
+ }
+ if (calls == TEST_NUM_FAILURES)
+ {
+ return TRUE;
+ }
+ fprintf(stderr, "%s: error: called again after success\n", __func__);
+ InterlockedIncrement(pErrors);
+ return FALSE;
+}
+
+static DWORD WINAPI TestThreadFunction(LPVOID lpParam)
+{
+ LONG calls = 0;
+ BOOL ok = 0;
+
+ WINPR_UNUSED(lpParam);
+
+ InterlockedIncrement(pTestThreadFunctionCalls);
+ if (WaitForSingleObject(hStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: error: failed to wait for start event\n", __func__);
+ InterlockedIncrement(pErrors);
+ return 0;
+ }
+
+ ok = InitOnceExecuteOnce(&initOnceTest, TestOnceFunction, NULL, NULL);
+ calls = InterlockedIncrement(pInitOnceExecuteOnceCalls);
+ if (!ok && calls > TEST_NUM_FAILURES)
+ {
+ fprintf(stderr, "%s: InitOnceExecuteOnce failed unexpectedly\n", __func__);
+ InterlockedIncrement(pErrors);
+ }
+ return 0;
+}
+
+int TestSynchInit(int argc, char* argv[])
+{
+ HANDLE hThreads[TEST_NUM_THREADS];
+ DWORD dwCreatedThreads = 0;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ pErrors = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pTestThreadFunctionCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pTestOnceFunctionCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pInitOnceExecuteOnceCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+
+ if (!pErrors || !pTestThreadFunctionCalls || !pTestOnceFunctionCalls ||
+ !pInitOnceExecuteOnceCalls)
+ {
+ fprintf(stderr, "error: _aligned_malloc failed\n");
+ goto out;
+ }
+
+ *pErrors = 0;
+ *pTestThreadFunctionCalls = 0;
+ *pTestOnceFunctionCalls = 0;
+ *pInitOnceExecuteOnceCalls = 0;
+
+ if (!(hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ fprintf(stderr, "error creating start event\n");
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+
+ for (DWORD i = 0; i < TEST_NUM_THREADS; i++)
+ {
+ if (!(hThreads[i] = CreateThread(NULL, 0, TestThreadFunction, NULL, 0, NULL)))
+ {
+ fprintf(stderr, "error creating thread #%" PRIu32 "\n", i);
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+ dwCreatedThreads++;
+ }
+
+ Sleep(100);
+ SetEvent(hStartEvent);
+
+ for (DWORD i = 0; i < dwCreatedThreads; i++)
+ {
+ if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "error: error waiting for thread #%" PRIu32 "\n", i);
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+ }
+
+ if (*pErrors == 0 && *pTestThreadFunctionCalls == TEST_NUM_THREADS &&
+ *pInitOnceExecuteOnceCalls == TEST_NUM_THREADS &&
+ *pTestOnceFunctionCalls == TEST_NUM_FAILURES + 1)
+ {
+ result = TRUE;
+ }
+
+out:
+ fprintf(stderr, "Test result: %s\n", result ? "OK" : "ERROR");
+ fprintf(stderr, "Error count: %" PRId32 "\n", pErrors ? *pErrors : -1);
+ fprintf(stderr, "Threads created: %" PRIu32 "\n", dwCreatedThreads);
+ fprintf(stderr, "TestThreadFunctionCalls: %" PRId32 "\n",
+ pTestThreadFunctionCalls ? *pTestThreadFunctionCalls : -1);
+ fprintf(stderr, "InitOnceExecuteOnceCalls: %" PRId32 "\n",
+ pInitOnceExecuteOnceCalls ? *pInitOnceExecuteOnceCalls : -1);
+ fprintf(stderr, "TestOnceFunctionCalls: %" PRId32 "\n",
+ pTestOnceFunctionCalls ? *pTestOnceFunctionCalls : -1);
+
+ winpr_aligned_free(pErrors);
+ winpr_aligned_free(pTestThreadFunctionCalls);
+ winpr_aligned_free(pTestOnceFunctionCalls);
+ winpr_aligned_free(pInitOnceExecuteOnceCalls);
+
+ CloseHandle(hStartEvent);
+
+ for (DWORD i = 0; i < dwCreatedThreads; i++)
+ {
+ CloseHandle(hThreads[i]);
+ }
+
+ return (result ? 0 : 1);
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c b/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c
new file mode 100644
index 0000000..7218b64
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c
@@ -0,0 +1,243 @@
+
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#define THREADS 8
+
+static DWORD WINAPI test_thread(LPVOID arg)
+{
+ long timeout = 50 + (rand() % 100);
+ WINPR_UNUSED(arg);
+ Sleep(timeout);
+ ExitThread(0);
+ return 0;
+}
+
+static int start_threads(size_t count, HANDLE* threads)
+{
+ for (size_t i = 0; i < count; i++)
+ {
+ threads[i] = CreateThread(NULL, 0, test_thread, NULL, CREATE_SUSPENDED, NULL);
+
+ if (!threads[i])
+ {
+ fprintf(stderr, "%s: CreateThread [%" PRIuz "] failure\n", __func__, i);
+ return -1;
+ }
+ }
+
+ for (size_t i = 0; i < count; i++)
+ ResumeThread(threads[i]);
+ return 0;
+}
+
+static int close_threads(DWORD count, HANDLE* threads)
+{
+ int rc = 0;
+
+ for (DWORD i = 0; i < count; i++)
+ {
+ if (!threads[i])
+ continue;
+
+ if (!CloseHandle(threads[i]))
+ {
+ fprintf(stderr, "%s: CloseHandle [%" PRIu32 "] failure\n", __func__, i);
+ rc = -1;
+ }
+ threads[i] = NULL;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitForAll(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, 10);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, timeout 10 failed, ret=%d\n",
+ __func__, ret);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOne(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, INFINITE);
+ if (ret > (WAIT_OBJECT_0 + ARRAYSIZE(threads)))
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOneTimeout(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 1);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects timeout 50 failed, ret=%d\n", __func__, ret);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOneTimeoutMultijoin(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(threads); i++)
+ {
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 0);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects timeout 0 failed, ret=%d\n", __func__, ret);
+ goto fail;
+ }
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestDetach(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+int TestSynchMultipleThreads(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!TestWaitForAll())
+ return -1;
+
+ if (!TestWaitOne())
+ return -2;
+
+ if (!TestWaitOneTimeout())
+ return -3;
+
+ if (!TestWaitOneTimeoutMultijoin())
+ return -4;
+
+ if (!TestDetach())
+ return -5;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchMutex.c b/winpr/libwinpr/synch/test/TestSynchMutex.c
new file mode 100644
index 0000000..296c371
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchMutex.c
@@ -0,0 +1,258 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static BOOL test_mutex_basic(void)
+{
+ HANDLE mutex = NULL;
+ DWORD rc = 0;
+
+ if (!(mutex = CreateMutex(NULL, FALSE, NULL)))
+ {
+ printf("%s: CreateMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ rc = WaitForSingleObject(mutex, INFINITE);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ printf("%s: WaitForSingleObject on mutex failed with %" PRIu32 "\n", __func__, rc);
+ return FALSE;
+ }
+
+ if (!ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ if (ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on released mutex\n", __func__);
+ return FALSE;
+ }
+
+ if (!CloseHandle(mutex))
+ {
+ printf("%s: CloseHandle on mutex failed\n", __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_mutex_recursive(void)
+{
+ HANDLE mutex = NULL;
+ DWORD rc = 0;
+ DWORD cnt = 50;
+
+ if (!(mutex = CreateMutex(NULL, TRUE, NULL)))
+ {
+ printf("%s: CreateMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < cnt; i++)
+ {
+ rc = WaitForSingleObject(mutex, INFINITE);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ printf("%s: WaitForSingleObject #%" PRIu32 " on mutex failed with %" PRIu32 "\n",
+ __func__, i, rc);
+ return FALSE;
+ }
+ }
+
+ for (UINT32 i = 0; i < cnt; i++)
+ {
+ if (!ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex #%" PRIu32 " failed\n", __func__, i);
+ return FALSE;
+ }
+ }
+
+ if (!ReleaseMutex(mutex))
+ {
+ /* Note: The mutex was initially owned ! */
+ printf("%s: Final ReleaseMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ if (ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on released mutex\n", __func__);
+ return FALSE;
+ }
+
+ if (!CloseHandle(mutex))
+ {
+ printf("%s: CloseHandle on mutex failed\n", __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static HANDLE thread1_mutex1 = NULL;
+static HANDLE thread1_mutex2 = NULL;
+static BOOL thread1_failed = TRUE;
+
+static DWORD WINAPI test_mutex_thread1(LPVOID lpParam)
+{
+ HANDLE hStartEvent = (HANDLE)lpParam;
+ DWORD rc = 0;
+
+ if (WaitForSingleObject(hStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: failed to wait for start event\n", __func__);
+ return 0;
+ }
+
+ /**
+ * at this point:
+ * thread1_mutex1 is expected to be locked
+ * thread1_mutex2 is expected to be unlocked
+ * defined task:
+ * try to lock thread1_mutex1 (expected to fail)
+ * lock and unlock thread1_mutex2 (expected to work)
+ */
+ rc = WaitForSingleObject(thread1_mutex1, 10);
+
+ if (rc != WAIT_TIMEOUT)
+ {
+ fprintf(stderr,
+ "%s: WaitForSingleObject on thread1_mutex1 unexpectedly returned %" PRIu32
+ " instead of WAIT_TIMEOUT (%u)\n",
+ __func__, rc, WAIT_TIMEOUT);
+ return 0;
+ }
+
+ rc = WaitForSingleObject(thread1_mutex2, 10);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ fprintf(stderr,
+ "%s: WaitForSingleObject on thread1_mutex2 unexpectedly returned %" PRIu32
+ " instead of WAIT_OBJECT_0\n",
+ __func__, rc);
+ return 0;
+ }
+
+ if (!ReleaseMutex(thread1_mutex2))
+ {
+ fprintf(stderr, "%s: ReleaseMutex failed on thread1_mutex2\n", __func__);
+ return 0;
+ }
+
+ thread1_failed = FALSE;
+ return 0;
+}
+
+static BOOL test_mutex_threading(void)
+{
+ HANDLE hThread = NULL;
+ HANDLE hStartEvent = NULL;
+
+ if (!(thread1_mutex1 = CreateMutex(NULL, TRUE, NULL)))
+ {
+ printf("%s: CreateMutex thread1_mutex1 failed\n", __func__);
+ goto fail;
+ }
+
+ if (!(thread1_mutex2 = CreateMutex(NULL, FALSE, NULL)))
+ {
+ printf("%s: CreateMutex thread1_mutex2 failed\n", __func__);
+ goto fail;
+ }
+
+ if (!(hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ fprintf(stderr, "%s: error creating start event\n", __func__);
+ goto fail;
+ }
+
+ thread1_failed = TRUE;
+
+ if (!(hThread = CreateThread(NULL, 0, test_mutex_thread1, (LPVOID)hStartEvent, 0, NULL)))
+ {
+ fprintf(stderr, "%s: error creating test_mutex_thread_1\n", __func__);
+ goto fail;
+ }
+
+ Sleep(100);
+
+ if (!thread1_failed)
+ {
+ fprintf(stderr, "%s: thread1 premature success\n", __func__);
+ goto fail;
+ }
+
+ SetEvent(hStartEvent);
+
+ if (WaitForSingleObject(hThread, 2000) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: thread1 premature success\n", __func__);
+ goto fail;
+ }
+
+ if (thread1_failed)
+ {
+ fprintf(stderr, "%s: thread1 has not reported success\n", __func__);
+ goto fail;
+ }
+
+ /**
+ * - thread1 must not have succeeded to lock thread1_mutex1
+ * - thread1 must have locked and unlocked thread1_mutex2
+ */
+
+ if (!ReleaseMutex(thread1_mutex1))
+ {
+ printf("%s: ReleaseMutex unexpectedly failed on thread1_mutex1\n", __func__);
+ goto fail;
+ }
+
+ if (ReleaseMutex(thread1_mutex2))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on thread1_mutex2\n", __func__);
+ goto fail;
+ }
+
+ CloseHandle(hThread);
+ CloseHandle(hStartEvent);
+ CloseHandle(thread1_mutex1);
+ CloseHandle(thread1_mutex2);
+ return TRUE;
+fail:
+ ReleaseMutex(thread1_mutex1);
+ ReleaseMutex(thread1_mutex2);
+ CloseHandle(thread1_mutex1);
+ CloseHandle(thread1_mutex2);
+ CloseHandle(hStartEvent);
+ CloseHandle(hThread);
+ return FALSE;
+}
+
+int TestSynchMutex(int argc, char* argv[])
+{
+ int rc = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_mutex_basic())
+ rc += 1;
+
+ if (!test_mutex_recursive())
+ rc += 2;
+
+ if (!test_mutex_threading())
+ rc += 4;
+
+ printf("TestSynchMutex result %d\n", rc);
+ return rc;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchSemaphore.c b/winpr/libwinpr/synch/test/TestSynchSemaphore.c
new file mode 100644
index 0000000..d44446a
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchSemaphore.c
@@ -0,0 +1,21 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchSemaphore(int argc, char* argv[])
+{
+ HANDLE semaphore = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ semaphore = CreateSemaphore(NULL, 0, 1, NULL);
+
+ if (!semaphore)
+ {
+ printf("CreateSemaphore failure\n");
+ return -1;
+ }
+
+ CloseHandle(semaphore);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchThread.c b/winpr/libwinpr/synch/test/TestSynchThread.c
new file mode 100644
index 0000000..58f7cb0
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchThread.c
@@ -0,0 +1,131 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static DWORD WINAPI test_thread(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+ Sleep(100);
+ ExitThread(0);
+ return 0;
+}
+
+int TestSynchThread(int argc, char* argv[])
+{
+ DWORD rc = 0;
+ HANDLE thread = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ /* TryJoin should now fail. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_TIMEOUT != rc)
+ {
+ printf("Timed WaitForSingleObject on running thread failed with %" PRIu32 "\n", rc);
+ return -3;
+ }
+
+ /* Join the thread */
+ rc = WaitForSingleObject(thread, INFINITE);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("WaitForSingleObject on thread failed with %" PRIu32 "\n", rc);
+ return -2;
+ }
+
+ /* TimedJoin should now succeed. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -5;
+ }
+
+ /* check that WaitForSingleObject works multiple times on a terminated thread */
+ for (int i = 0; i < 4; i++)
+ {
+ rc = WaitForSingleObject(thread, 0);
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -6;
+ }
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ /* TryJoin should now fail. */
+ rc = WaitForSingleObject(thread, 10);
+
+ if (WAIT_TIMEOUT != rc)
+ {
+ printf("Timed WaitForSingleObject on running thread failed with %" PRIu32 "\n", rc);
+ return -3;
+ }
+
+ /* Join the thread */
+ rc = WaitForSingleObject(thread, INFINITE);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("WaitForSingleObject on thread failed with %" PRIu32 "\n", rc);
+ return -2;
+ }
+
+ /* TimedJoin should now succeed. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -5;
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ /* Thread detach test */
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchTimerQueue.c b/winpr/libwinpr/synch/test/TestSynchTimerQueue.c
new file mode 100644
index 0000000..08cc957
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchTimerQueue.c
@@ -0,0 +1,125 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+
+#define FIRE_COUNT 5
+#define TIMER_COUNT 5
+
+struct apc_data
+{
+ DWORD TimerId;
+ DWORD FireCount;
+ DWORD DueTime;
+ DWORD Period;
+ UINT32 StartTime;
+ DWORD MaxFireCount;
+ HANDLE CompletionEvent;
+};
+typedef struct apc_data APC_DATA;
+
+static VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired)
+{
+ UINT32 TimerTime = 0;
+ APC_DATA* apcData = NULL;
+ UINT32 expectedTime = 0;
+ UINT32 CurrentTime = GetTickCount();
+
+ WINPR_UNUSED(TimerOrWaitFired);
+
+ if (!lpParam)
+ return;
+
+ apcData = (APC_DATA*)lpParam;
+
+ TimerTime = CurrentTime - apcData->StartTime;
+ expectedTime = apcData->DueTime + (apcData->Period * apcData->FireCount);
+
+ apcData->FireCount++;
+
+ printf("TimerRoutine: TimerId: %" PRIu32 " FireCount: %" PRIu32 " ActualTime: %" PRIu32
+ " ExpectedTime: %" PRIu32 " Discrepancy: %" PRIu32 "\n",
+ apcData->TimerId, apcData->FireCount, TimerTime, expectedTime, TimerTime - expectedTime);
+
+ Sleep(11);
+
+ if (apcData->FireCount == apcData->MaxFireCount)
+ {
+ SetEvent(apcData->CompletionEvent);
+ }
+}
+
+int TestSynchTimerQueue(int argc, char* argv[])
+{
+ HANDLE hTimerQueue = NULL;
+ HANDLE hTimers[TIMER_COUNT];
+ APC_DATA apcData[TIMER_COUNT];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ hTimerQueue = CreateTimerQueue();
+
+ if (!hTimerQueue)
+ {
+ printf("CreateTimerQueue failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ apcData[index].TimerId = index;
+ apcData[index].StartTime = GetTickCount();
+ apcData[index].DueTime = (index * 10) + 50;
+ apcData[index].Period = 100;
+ apcData[index].FireCount = 0;
+ apcData[index].MaxFireCount = FIRE_COUNT;
+
+ if (!(apcData[index].CompletionEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("Failed to create apcData[%" PRIu32 "] event (%" PRIu32 ")\n", index,
+ GetLastError());
+ return -1;
+ }
+
+ if (!CreateTimerQueueTimer(&hTimers[index], hTimerQueue, TimerRoutine, &apcData[index],
+ apcData[index].DueTime, apcData[index].Period, 0))
+ {
+ printf("CreateTimerQueueTimer failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ if (WaitForSingleObject(apcData[index].CompletionEvent, 2000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for timer queue timer #%" PRIu32 " (%" PRIu32 ")\n", index,
+ GetLastError());
+ return -1;
+ }
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ /**
+ * Note: If the CompletionEvent parameter is INVALID_HANDLE_VALUE, the function waits
+ * for any running timer callback functions to complete before returning.
+ */
+ if (!DeleteTimerQueueTimer(hTimerQueue, hTimers[index], INVALID_HANDLE_VALUE))
+ {
+ printf("DeleteTimerQueueTimer failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ CloseHandle(apcData[index].CompletionEvent);
+ }
+
+ if (!DeleteTimerQueue(hTimerQueue))
+ {
+ printf("DeleteTimerQueue failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c b/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c
new file mode 100644
index 0000000..f61bbc1
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c
@@ -0,0 +1,83 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchWaitableTimer(int argc, char* argv[])
+{
+ DWORD status = 0;
+ HANDLE timer = NULL;
+ LONG period = 0;
+ LARGE_INTEGER due;
+ int result = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ timer = CreateWaitableTimer(NULL, FALSE, NULL);
+
+ if (!timer)
+ {
+ printf("CreateWaitableTimer failure\n");
+ goto out;
+ }
+
+ due.QuadPart = -1500000LL; /* 0.15 seconds */
+
+ if (!SetWaitableTimer(timer, &due, 0, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ status = WaitForSingleObject(timer, INFINITE);
+
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+ status = WaitForSingleObject(timer, 200);
+
+ if (status != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject(timer, 200) failure: Actual: 0x%08" PRIX32
+ ", Expected: 0x%08X\n",
+ status, WAIT_TIMEOUT);
+ goto out;
+ }
+
+ due.QuadPart = 0;
+ period = 120; /* 0.12 seconds */
+
+ if (!SetWaitableTimer(timer, &due, period, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ if (WaitForSingleObject(timer, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+
+ if (!SetWaitableTimer(timer, &due, period, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ if (WaitForMultipleObjects(1, &timer, FALSE, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForMultipleObjects(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+ result = 0;
+out:
+ CloseHandle(timer);
+ return result;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c b/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c
new file mode 100644
index 0000000..cf1f677
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c
@@ -0,0 +1,92 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+
+static int g_Count = 0;
+static HANDLE g_Event = NULL;
+
+struct apc_data
+{
+ UINT32 StartTime;
+};
+typedef struct apc_data APC_DATA;
+
+static VOID CALLBACK TimerAPCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ APC_DATA* apcData = NULL;
+ UINT32 CurrentTime = GetTickCount();
+ WINPR_UNUSED(dwTimerLowValue);
+ WINPR_UNUSED(dwTimerHighValue);
+
+ if (!lpArg)
+ return;
+
+ apcData = (APC_DATA*)lpArg;
+ printf("TimerAPCProc: time: %" PRIu32 "\n", CurrentTime - apcData->StartTime);
+ g_Count++;
+
+ if (g_Count >= 5)
+ {
+ SetEvent(g_Event);
+ }
+}
+
+int TestSynchWaitableTimerAPC(int argc, char* argv[])
+{
+ int status = -1;
+ DWORD rc = 0;
+ HANDLE hTimer = NULL;
+ BOOL bSuccess = 0;
+ LARGE_INTEGER due;
+ APC_DATA apcData = { 0 };
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ g_Event = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!g_Event)
+ {
+ printf("Failed to create event\n");
+ goto cleanup;
+ }
+
+ hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
+ if (!hTimer)
+ goto cleanup;
+
+ due.QuadPart = -1000 * 100LL; /* 0.1 seconds */
+ apcData.StartTime = GetTickCount();
+ bSuccess = SetWaitableTimer(hTimer, &due, 10, TimerAPCProc, &apcData, FALSE);
+
+ if (!bSuccess)
+ goto cleanup;
+
+ /* nothing shall happen after 0.12 second, because thread is not in alertable state */
+ rc = WaitForSingleObject(g_Event, 120);
+ if (rc != WAIT_TIMEOUT)
+ goto cleanup;
+
+ for (;;)
+ {
+ rc = WaitForSingleObjectEx(g_Event, INFINITE, TRUE);
+ if (rc == WAIT_OBJECT_0)
+ break;
+
+ if (rc == WAIT_IO_COMPLETION)
+ continue;
+
+ printf("Failed to wait for completion event (%" PRIu32 ")\n", GetLastError());
+ goto cleanup;
+ }
+
+ status = 0;
+cleanup:
+
+ if (hTimer)
+ CloseHandle(hTimer);
+
+ if (g_Event)
+ CloseHandle(g_Event);
+
+ return status;
+}