Blame winpr/libwinpr/synch/test/TestSynchCritical.c

Packit 1fb8d4
Packit 1fb8d4
#include <stdio.h>
Packit 1fb8d4
#include <winpr/crt.h>
Packit 1fb8d4
#include <winpr/windows.h>
Packit 1fb8d4
#include <winpr/synch.h>
Packit 1fb8d4
#include <winpr/sysinfo.h>
Packit 1fb8d4
#include <winpr/thread.h>
Packit 1fb8d4
#include <winpr/interlocked.h>
Packit 1fb8d4
Packit 1fb8d4
#define TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS 500
Packit 1fb8d4
#define TEST_SYNC_CRITICAL_TEST1_RUNS 4
Packit 1fb8d4
Packit 1fb8d4
static CRITICAL_SECTION critical;
Packit 1fb8d4
static LONG gTestValueVulnerable = 0;
Packit 1fb8d4
static LONG gTestValueSerialized = 0;
Packit 1fb8d4
Packit 1fb8d4
static BOOL TestSynchCritical_TriggerAndCheckRaceCondition(HANDLE OwningThread, LONG RecursionCount)
Packit 1fb8d4
{
Packit 1fb8d4
	/* if called unprotected this will hopefully trigger a race condition ... */
Packit 1fb8d4
	gTestValueVulnerable++;
Packit 1fb8d4
Packit 1fb8d4
	if (critical.OwningThread != OwningThread)
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: OwningThread is invalid\n");
Packit 1fb8d4
		return FALSE;
Packit 1fb8d4
	}
Packit 1fb8d4
	if (critical.RecursionCount != RecursionCount)
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: RecursionCount is invalid\n");
Packit 1fb8d4
		return FALSE;
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	/* ... which we try to detect using the serialized counter */
Packit 1fb8d4
	if (gTestValueVulnerable != InterlockedIncrement(&gTestValueSerialized))
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: Data corruption detected\n");
Packit 1fb8d4
		return FALSE;
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	return TRUE;
Packit 1fb8d4
}
Packit 1fb8d4
Packit Service 5a9772
/* this thread function shall increment the global dwTestValue until the PBOOL passsed in arg is
Packit Service 5a9772
 * FALSE */
Packit 1fb8d4
static DWORD WINAPI TestSynchCritical_Test1(LPVOID arg)
Packit 1fb8d4
{
Packit 1fb8d4
	int i, j, rc;
Packit Service 5a9772
	HANDLE hThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
Packit 1fb8d4
Packit 1fb8d4
	PBOOL pbContinueRunning = (PBOOL)arg;
Packit 1fb8d4
Packit Service 5a9772
	while (*pbContinueRunning)
Packit 1fb8d4
	{
Packit 1fb8d4
		EnterCriticalSection(&critical);
Packit 1fb8d4
Packit 1fb8d4
		rc = 1;
Packit 1fb8d4
Packit 1fb8d4
		if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
Packit 1fb8d4
			return 1;
Packit 1fb8d4
Packit 1fb8d4
		/* add some random recursion level */
Packit Service 5a9772
		j = rand() % 5;
Packit Service 5a9772
		for (i = 0; i < j; i++)
Packit 1fb8d4
		{
Packit 1fb8d4
			if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc++))
Packit 1fb8d4
				return 2;
Packit 1fb8d4
			EnterCriticalSection(&critical);
Packit 1fb8d4
		}
Packit Service 5a9772
		for (i = 0; i < j; i++)
Packit 1fb8d4
		{
Packit 1fb8d4
			if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc--))
Packit 1fb8d4
				return 2;
Packit 1fb8d4
			LeaveCriticalSection(&critical);
Packit 1fb8d4
		}
Packit 1fb8d4
Packit 1fb8d4
		if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
Packit 1fb8d4
			return 3;
Packit 1fb8d4
Packit 1fb8d4
		LeaveCriticalSection(&critical);
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	return 0;
Packit 1fb8d4
}
Packit 1fb8d4
Packit Service 5a9772
/* this thread function tries to call TryEnterCriticalSection while the main thread holds the lock
Packit Service 5a9772
 */
Packit 1fb8d4
static DWORD WINAPI TestSynchCritical_Test2(LPVOID arg)
Packit 1fb8d4
{
Packit Service 5a9772
	if (TryEnterCriticalSection(&critical) == TRUE)
Packit 1fb8d4
	{
Packit 1fb8d4
		LeaveCriticalSection(&critical);
Packit 1fb8d4
		return 1;
Packit 1fb8d4
	}
Packit 1fb8d4
	return 0;
Packit 1fb8d4
}
Packit 1fb8d4
Packit 1fb8d4
static DWORD WINAPI TestSynchCritical_Main(LPVOID arg)
Packit 1fb8d4
{
Packit 1fb8d4
	int i, j;
Packit 1fb8d4
	SYSTEM_INFO sysinfo;
Packit 1fb8d4
	DWORD dwPreviousSpinCount;
Packit 1fb8d4
	DWORD dwSpinCount;
Packit 1fb8d4
	DWORD dwSpinCountExpected;
Packit 1fb8d4
	HANDLE hMainThread;
Packit 1fb8d4
	HANDLE* hThreads;
Packit 1fb8d4
	HANDLE hThread;
Packit 1fb8d4
	DWORD dwThreadCount;
Packit 1fb8d4
	DWORD dwThreadExitCode;
Packit 1fb8d4
	BOOL bTest1Running;
Packit 1fb8d4
Packit 1fb8d4
	PBOOL pbThreadTerminated = (PBOOL)arg;
Packit 1fb8d4
Packit 1fb8d4
	GetNativeSystemInfo(&sysinfo);
Packit 1fb8d4
Packit Service 5a9772
	hMainThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
Packit 1fb8d4
Packit 1fb8d4
	/**
Packit Service 5a9772
	 * Test SpinCount in SetCriticalSectionSpinCount, InitializeCriticalSectionEx and
Packit Service 5a9772
	 * InitializeCriticalSectionAndSpinCount SpinCount must be forced to be zero on on uniprocessor
Packit Service 5a9772
	 * systems and on systems where WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT is defined
Packit 1fb8d4
	 */
Packit 1fb8d4
Packit 1fb8d4
	dwSpinCount = 100;
Packit 1fb8d4
	InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
Packit Service 5a9772
	while (--dwSpinCount)
Packit 1fb8d4
	{
Packit 1fb8d4
		dwPreviousSpinCount = SetCriticalSectionSpinCount(&critical, dwSpinCount);
Packit 1fb8d4
		dwSpinCountExpected = 0;
Packit 1fb8d4
#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
Packit 1fb8d4
		if (sysinfo.dwNumberOfProcessors > 1)
Packit Service 5a9772
			dwSpinCountExpected = dwSpinCount + 1;
Packit 1fb8d4
#endif
Packit 1fb8d4
		if (dwPreviousSpinCount != dwSpinCountExpected)
Packit 1fb8d4
		{
Packit Service 5a9772
			printf("CriticalSection failure: SetCriticalSectionSpinCount returned %" PRIu32
Packit Service 5a9772
			       " (expected: %" PRIu32 ")\n",
Packit Service 5a9772
			       dwPreviousSpinCount, dwSpinCountExpected);
Packit 1fb8d4
			goto fail;
Packit 1fb8d4
		}
Packit 1fb8d4
Packit 1fb8d4
		DeleteCriticalSection(&critical);
Packit 1fb8d4
Packit Service 5a9772
		if (dwSpinCount % 2 == 0)
Packit 1fb8d4
			InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
Packit 1fb8d4
		else
Packit 1fb8d4
			InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
Packit 1fb8d4
	}
Packit 1fb8d4
	DeleteCriticalSection(&critical);
Packit 1fb8d4
Packit 1fb8d4
	/**
Packit Service 5a9772
	 * Test single-threaded recursive
Packit Service 5a9772
	 * TryEnterCriticalSection/EnterCriticalSection/LeaveCriticalSection
Packit 1fb8d4
	 *
Packit 1fb8d4
	 */
Packit 1fb8d4
Packit 1fb8d4
	InitializeCriticalSection(&critical);
Packit 1fb8d4
Packit 1fb8d4
	for (i = 0; i < 1000; i++)
Packit 1fb8d4
	{
Packit 1fb8d4
		if (critical.RecursionCount != i)
Packit 1fb8d4
		{
Packit Service 5a9772
			printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
Packit Service 5a9772
			       critical.RecursionCount, i);
Packit 1fb8d4
			goto fail;
Packit 1fb8d4
		}
Packit Service 5a9772
		if (i % 2 == 0)
Packit 1fb8d4
		{
Packit 1fb8d4
			EnterCriticalSection(&critical);
Packit 1fb8d4
		}
Packit 1fb8d4
		else
Packit 1fb8d4
		{
Packit 1fb8d4
			if (TryEnterCriticalSection(&critical) == FALSE)
Packit 1fb8d4
			{
Packit Service 5a9772
				printf("CriticalSection failure: TryEnterCriticalSection failed where it should "
Packit Service 5a9772
				       "not.\n");
Packit 1fb8d4
				goto fail;
Packit 1fb8d4
			}
Packit 1fb8d4
		}
Packit 1fb8d4
		if (critical.OwningThread != hMainThread)
Packit 1fb8d4
		{
Packit Service 5a9772
			printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
Packit Service 5a9772
			       i);
Packit 1fb8d4
			goto fail;
Packit 1fb8d4
		}
Packit 1fb8d4
	}
Packit 1fb8d4
	while (--i >= 0)
Packit 1fb8d4
	{
Packit 1fb8d4
		LeaveCriticalSection(&critical);
Packit 1fb8d4
		if (critical.RecursionCount != i)
Packit 1fb8d4
		{
Packit Service 5a9772
			printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
Packit Service 5a9772
			       critical.RecursionCount, i);
Packit 1fb8d4
			goto fail;
Packit 1fb8d4
		}
Packit 1fb8d4
		if (critical.OwningThread != (HANDLE)(i ? hMainThread : NULL))
Packit 1fb8d4
		{
Packit Service 5a9772
			printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
Packit Service 5a9772
			       i);
Packit 1fb8d4
			goto fail;
Packit 1fb8d4
		}
Packit 1fb8d4
	}
Packit 1fb8d4
	DeleteCriticalSection(&critical);
Packit 1fb8d4
Packit 1fb8d4
	/**
Packit 1fb8d4
	 * Test using multiple threads modifying the same value
Packit 1fb8d4
	 */
Packit 1fb8d4
Packit 1fb8d4
	dwThreadCount = sysinfo.dwNumberOfProcessors > 1 ? sysinfo.dwNumberOfProcessors : 2;
Packit 1fb8d4
Packit Service 5a9772
	hThreads = (HANDLE*)calloc(dwThreadCount, sizeof(HANDLE));
Packit 1fb8d4
	if (!hThreads)
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("Problem allocating memory\n");
Packit 1fb8d4
		goto fail;
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	for (j = 0; j < TEST_SYNC_CRITICAL_TEST1_RUNS; j++)
Packit 1fb8d4
	{
Packit 1fb8d4
		dwSpinCount = j * 1000;
Packit 1fb8d4
		InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
Packit 1fb8d4
Packit 1fb8d4
		gTestValueVulnerable = 0;
Packit 1fb8d4
		gTestValueSerialized = 0;
Packit 1fb8d4
Packit 1fb8d4
		/* the TestSynchCritical_Test1 threads shall run until bTest1Running is FALSE */
Packit 1fb8d4
		bTest1Running = TRUE;
Packit Service 5a9772
		for (i = 0; i < (int)dwThreadCount; i++)
Packit 1fb8d4
		{
Packit Service 5a9772
			if (!(hThreads[i] =
Packit Service 5a9772
			          CreateThread(NULL, 0, TestSynchCritical_Test1, &bTest1Running, 0, NULL)))
Packit 1fb8d4
			{
Packit 1fb8d4
				printf("CriticalSection failure: Failed to create test_1 thread #%d\n", i);
Packit 1fb8d4
				goto fail;
Packit 1fb8d4
			}
Packit 1fb8d4
		}
Packit 1fb8d4
Packit 1fb8d4
		/* let it run for TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS ... */
Packit 1fb8d4
		Sleep(TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS);
Packit 1fb8d4
		bTest1Running = FALSE;
Packit 1fb8d4
Packit Service 5a9772
		for (i = 0; i < (int)dwThreadCount; i++)
Packit 1fb8d4
		{
Packit 1fb8d4
			if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
Packit 1fb8d4
			{
Packit 1fb8d4
				printf("CriticalSection failure: Failed to wait for thread #%d\n", i);
Packit 1fb8d4
				goto fail;
Packit 1fb8d4
			}
Packit 1fb8d4
			GetExitCodeThread(hThreads[i], &dwThreadExitCode);
Packit Service 5a9772
			if (dwThreadExitCode != 0)
Packit 1fb8d4
			{
Packit Service 5a9772
				printf("CriticalSection failure: Thread #%d returned error code %" PRIu32 "\n", i,
Packit Service 5a9772
				       dwThreadExitCode);
Packit 1fb8d4
				goto fail;
Packit 1fb8d4
			}
Packit 1fb8d4
			CloseHandle(hThreads[i]);
Packit 1fb8d4
		}
Packit 1fb8d4
Packit 1fb8d4
		if (gTestValueVulnerable != gTestValueSerialized)
Packit 1fb8d4
		{
Packit Service 5a9772
			printf("CriticalSection failure: unexpected test value %" PRId32 " (expected %" PRId32
Packit Service 5a9772
			       ")\n",
Packit Service 5a9772
			       gTestValueVulnerable, gTestValueSerialized);
Packit 1fb8d4
			goto fail;
Packit 1fb8d4
		}
Packit 1fb8d4
Packit 1fb8d4
		DeleteCriticalSection(&critical);
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	free(hThreads);
Packit 1fb8d4
Packit 1fb8d4
	/**
Packit 1fb8d4
	 * TryEnterCriticalSection in thread must fail if we hold the lock in the main thread
Packit 1fb8d4
	 */
Packit 1fb8d4
Packit 1fb8d4
	InitializeCriticalSection(&critical);
Packit 1fb8d4
Packit 1fb8d4
	if (TryEnterCriticalSection(&critical) == FALSE)
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: TryEnterCriticalSection unexpectedly failed.\n");
Packit 1fb8d4
		goto fail;
Packit 1fb8d4
	}
Packit 1fb8d4
	/* This thread tries to call TryEnterCriticalSection which must fail */
Packit Service 5a9772
	if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Test2, NULL, 0, NULL)))
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: Failed to create test_2 thread\n");
Packit 1fb8d4
		goto fail;
Packit 1fb8d4
	}
Packit 1fb8d4
	if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: Failed to wait for thread\n");
Packit 1fb8d4
		goto fail;
Packit 1fb8d4
	}
Packit 1fb8d4
	GetExitCodeThread(hThread, &dwThreadExitCode);
Packit Service 5a9772
	if (dwThreadExitCode != 0)
Packit 1fb8d4
	{
Packit Service 5a9772
		printf("CriticalSection failure: Thread returned error code %" PRIu32 "\n",
Packit Service 5a9772
		       dwThreadExitCode);
Packit 1fb8d4
		goto fail;
Packit 1fb8d4
	}
Packit 1fb8d4
	CloseHandle(hThread);
Packit 1fb8d4
Packit 1fb8d4
	*pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
Packit 1fb8d4
	return 0;
Packit 1fb8d4
Packit 1fb8d4
fail:
Packit 1fb8d4
	*pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
Packit 1fb8d4
	return 1;
Packit 1fb8d4
}
Packit 1fb8d4
Packit 1fb8d4
int TestSynchCritical(int argc, char* argv[])
Packit 1fb8d4
{
Packit 1fb8d4
	BOOL bThreadTerminated = FALSE;
Packit 1fb8d4
	HANDLE hThread;
Packit 1fb8d4
	DWORD dwThreadExitCode;
Packit 1fb8d4
	DWORD dwDeadLockDetectionTimeMs;
Packit 1fb8d4
	DWORD i;
Packit 1fb8d4
Packit Service 5a9772
	dwDeadLockDetectionTimeMs =
Packit Service 5a9772
	    2 * TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS * TEST_SYNC_CRITICAL_TEST1_RUNS;
Packit 1fb8d4
Packit Service 5a9772
	printf("Deadlock will be assumed after %" PRIu32 " ms.\n", dwDeadLockDetectionTimeMs);
Packit 1fb8d4
Packit 1fb8d4
	if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Main, &bThreadTerminated, 0, NULL)))
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: Failed to create main thread\n");
Packit 1fb8d4
		return -1;
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	/**
Packit 1fb8d4
	 * We have to be able to detect dead locks in this test.
Packit Service 5a9772
	 * At the time of writing winpr's WaitForSingleObject has not implemented timeout for thread
Packit Service 5a9772
	 * wait
Packit 1fb8d4
	 *
Packit 1fb8d4
	 * Workaround checking the value of bThreadTerminated which is passed in the thread arg
Packit 1fb8d4
	 */
Packit 1fb8d4
Packit 1fb8d4
	for (i = 0; i < dwDeadLockDetectionTimeMs; i += 100)
Packit 1fb8d4
	{
Packit 1fb8d4
		if (bThreadTerminated)
Packit 1fb8d4
			break;
Packit 1fb8d4
Packit 1fb8d4
		Sleep(100);
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	if (!bThreadTerminated)
Packit 1fb8d4
	{
Packit 1fb8d4
		printf("CriticalSection failure: Possible dead lock detected\n");
Packit 1fb8d4
		return -1;
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	GetExitCodeThread(hThread, &dwThreadExitCode);
Packit 1fb8d4
	CloseHandle(hThread);
Packit 1fb8d4
Packit 1fb8d4
	if (dwThreadExitCode != 0)
Packit 1fb8d4
	{
Packit 1fb8d4
		return -1;
Packit 1fb8d4
	}
Packit 1fb8d4
Packit 1fb8d4
	return 0;
Packit 1fb8d4
}