diff options
Diffstat (limited to '')
-rw-r--r-- | winpr/libwinpr/synch/test/CMakeLists.txt | 41 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchAPC.c | 173 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchBarrier.c | 257 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchCritical.c | 363 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchEvent.c | 94 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchInit.c | 156 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchMultipleThreads.c | 243 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchMutex.c | 258 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchSemaphore.c | 21 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchThread.c | 131 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchTimerQueue.c | 125 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchWaitableTimer.c | 83 | ||||
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c | 92 |
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; +} |