diff options
Diffstat (limited to '')
-rw-r--r-- | winpr/libwinpr/synch/test/TestSynchCritical.c | 363 |
1 files changed, 363 insertions, 0 deletions
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; +} |