Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,40 @@
set(MODULE_NAME "TestSynch")
set(MODULE_PREFIX "TEST_SYNCH")
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
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(SYSTEM ${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")

View File

@@ -0,0 +1,151 @@
/**
* 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++;
(void)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 = WINPR_C_ARRAY_INIT;
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 = nullptr;
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(nullptr, 0, uncleanThread, &userApcArg, 0, nullptr);
if (!thread)
return 10;
(void)WaitForSingleObject(thread, INFINITE);
(void)CloseHandle(thread);
if (userApcArg.called || userApcArg.error)
return 11;
/* test a remote APC queuing */
thread = CreateThread(nullptr, 0, cleanThread, &userApcArg, 0, nullptr);
if (!thread)
return 20;
if (!QueueUserAPC((PAPCFUNC)userApc, thread, (ULONG_PTR)&userApcArg))
return 21;
(void)WaitForSingleObject(thread, INFINITE);
(void)CloseHandle(thread);
if (!userApcArg.called)
return 22;
return 0;
}

View File

@@ -0,0 +1,270 @@
#include <winpr/crt.h>
#include <winpr/crypto.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 = nullptr;
static LONG gErrorCount = 0;
#define MAX_SLEEP_MS 22
struct test_params
{
LONG threadCount;
LONG trueCount;
LONG falseCount;
DWORD loops;
DWORD flags;
};
static UINT32 prand(UINT32 max)
{
UINT32 tmp = 0;
if (max <= 1)
return 1;
if (winpr_RAND(&tmp, sizeof(tmp)) < 0)
{
// NOLINTNEXTLINE(concurrency-mt-unsafe)
exit(-1);
}
return tmp % (max - 1) + 1;
}
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 + prand(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)
{
BOOL rc = FALSE;
DWORD dwStatus = 0;
struct test_params p = {
.threadCount = 0, .trueCount = 0, .falseCount = 0, .loops = dwLoops, .flags = dwFlags
};
DWORD expectedTrueCount = dwLoops;
DWORD expectedFalseCount = dwLoops * (dwThreads - 1);
printf("%s: >> Testing with flags 0x%08" PRIx32 ". Using %" PRIu32
" threads performing %" PRIu32 " loops\n",
__func__, dwFlags, dwThreads, dwLoops);
HANDLE* threads = (HANDLE*)calloc(dwThreads, sizeof(HANDLE));
if (!threads)
{
printf("%s: error allocatin thread array memory\n", __func__);
return FALSE;
}
if (dwThreads > INT32_MAX)
goto fail;
if (!InitializeSynchronizationBarrier(&gBarrier, (LONG)dwThreads, -1))
{
printf("%s: InitializeSynchronizationBarrier failed. GetLastError() = 0x%08x", __func__,
GetLastError());
goto fail;
}
if (!(gStartEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
printf("%s: CreateEvent failed with error 0x%08x", __func__, GetLastError());
goto fail;
}
DWORD i = 0;
for (; i < dwThreads; i++)
{
threads[i] = CreateThread(nullptr, 0, test_synch_barrier_thread, &p, 0, nullptr);
if (!threads[i])
{
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);
}
}
}
if (!CloseHandle(gStartEvent))
{
printf("%s: CloseHandle(gStartEvent) failed with error = 0x%08x)\n", __func__,
GetLastError());
InterlockedIncrement(&gErrorCount);
}
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);
rc = TRUE;
fail:
free((void*)threads);
DeleteSynchronizationBarrier(&gBarrier);
if (gErrorCount > 0)
{
printf("%s: Error test failed with %" PRId32 " reported errors\n", __func__, gErrorCount);
return FALSE;
}
return rc;
}
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))
{
(void)fprintf(
stderr,
"%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = 0\n",
__func__);
return -1;
}
if (InitializeSynchronizationBarrier(&gBarrier, -1, -1))
{
(void)fprintf(
stderr,
"%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = -1\n",
__func__);
return -1;
}
if (InitializeSynchronizationBarrier(&gBarrier, 1, -2))
{
(void)fprintf(
stderr,
"%s: InitializeSynchronizationBarrier unecpectedly succeeded with lSpinCount = -2\n",
__func__);
return -1;
}
/* Functional tests */
if (!TestSynchBarrierWithFlags(0, dwMaxThreads, dwNumLoops))
{
(void)fprintf(
stderr,
"%s: TestSynchBarrierWithFlags(0) unecpectedly succeeded with lTotalThreads = -1\n",
__func__);
return -1;
}
if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY, dwMinThreads,
dwNumLoops))
{
(void)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))
{
(void)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;
}

View File

@@ -0,0 +1,382 @@
#include <stdio.h>
#include <winpr/crt.h>
#include <winpr/crypto.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 100
#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;
}
static UINT32 prand(UINT32 max)
{
UINT32 tmp = 0;
if (max <= 1)
return 1;
if (winpr_RAND(&tmp, sizeof(tmp)) < 0)
return 0;
return tmp % (max - 1) + 1;
}
/* this thread function shall increment the global dwTestValue until the PBOOL passed 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 */
UINT32 j = prand(5);
for (UINT32 i = 0; i < j; i++)
{
if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc++))
return 2;
EnterCriticalSection(&critical);
}
for (UINT32 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 = nullptr;
HANDLE* hThreads = nullptr;
HANDLE hThread = nullptr;
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)
{
if (!InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount))
goto fail;
}
else
{
if (!InitializeCriticalSectionEx(&critical, dwSpinCount, 0))
goto fail;
}
}
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 != (i ? hMainThread : nullptr))
{
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;
if (!InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount))
goto fail;
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(nullptr, 0, TestSynchCritical_Test1, &bTest1Running, 0,
nullptr)))
{
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;
}
(void)CloseHandle(hThreads[i]);
}
if (gTestValueVulnerable != gTestValueSerialized)
{
printf("CriticalSection failure: unexpected test value %" PRId32 " (expected %" PRId32
")\n",
gTestValueVulnerable, gTestValueSerialized);
goto fail;
}
DeleteCriticalSection(&critical);
}
free((void*)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(nullptr, 0, TestSynchCritical_Test2, nullptr, 0, nullptr)))
{
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;
}
(void)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 = nullptr;
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(nullptr, 0, TestSynchCritical_Main, &bThreadTerminated, 0, nullptr)))
{
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);
(void)CloseHandle(hThread);
if (dwThreadExitCode != 0)
{
return -1;
}
return 0;
}

View File

@@ -0,0 +1,94 @@
#include <winpr/crt.h>
#include <winpr/synch.h>
int TestSynchEvent(int argc, char* argv[])
{
HANDLE event = nullptr;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
if (ResetEvent(nullptr))
{
printf("ResetEvent(nullptr) unexpectedly succeeded\n");
return -1;
}
if (SetEvent(nullptr))
{
printf("SetEvent(nullptr) unexpectedly succeeded\n");
return -1;
}
event = CreateEvent(nullptr, TRUE, TRUE, nullptr);
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;
}
(void)CloseHandle(event);
return 0;
}

View File

@@ -0,0 +1,167 @@
#include <stdio.h>
#include <winpr/crt.h>
#include <winpr/crypto.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 = nullptr;
static LONG* pErrors = nullptr;
static LONG* pTestThreadFunctionCalls = nullptr;
static LONG* pTestOnceFunctionCalls = nullptr;
static LONG* pInitOnceExecuteOnceCalls = nullptr;
static UINT32 prand(UINT32 max)
{
UINT32 tmp = 0;
if (max <= 1)
return 1;
if (winpr_RAND(&tmp, sizeof(tmp)) < 0)
return 0;
return tmp % (max - 1) + 1;
}
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 + prand(40));
if (calls < TEST_NUM_FAILURES)
{
/* simulated error */
return FALSE;
}
if (calls == TEST_NUM_FAILURES)
{
return TRUE;
}
(void)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)
{
(void)fprintf(stderr, "%s: error: failed to wait for start event\n", __func__);
InterlockedIncrement(pErrors);
return 0;
}
ok = InitOnceExecuteOnce(&initOnceTest, TestOnceFunction, nullptr, nullptr);
calls = InterlockedIncrement(pInitOnceExecuteOnceCalls);
if (!ok && calls > TEST_NUM_FAILURES)
{
(void)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)
{
(void)fprintf(stderr, "error: _aligned_malloc failed\n");
goto out;
}
*pErrors = 0;
*pTestThreadFunctionCalls = 0;
*pTestOnceFunctionCalls = 0;
*pInitOnceExecuteOnceCalls = 0;
if (!(hStartEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
(void)fprintf(stderr, "error creating start event\n");
InterlockedIncrement(pErrors);
goto out;
}
for (DWORD i = 0; i < TEST_NUM_THREADS; i++)
{
if (!(hThreads[i] = CreateThread(nullptr, 0, TestThreadFunction, nullptr, 0, nullptr)))
{
(void)fprintf(stderr, "error creating thread #%" PRIu32 "\n", i);
InterlockedIncrement(pErrors);
goto out;
}
dwCreatedThreads++;
}
Sleep(100);
(void)SetEvent(hStartEvent);
for (DWORD i = 0; i < dwCreatedThreads; i++)
{
if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
{
(void)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:
(void)fprintf(stderr, "Test result: %s\n", result ? "OK" : "ERROR");
(void)fprintf(stderr, "Error count: %" PRId32 "\n", pErrors ? *pErrors : -1);
(void)fprintf(stderr, "Threads created: %" PRIu32 "\n", dwCreatedThreads);
(void)fprintf(stderr, "TestThreadFunctionCalls: %" PRId32 "\n",
pTestThreadFunctionCalls ? *pTestThreadFunctionCalls : -1);
(void)fprintf(stderr, "InitOnceExecuteOnceCalls: %" PRId32 "\n",
pInitOnceExecuteOnceCalls ? *pInitOnceExecuteOnceCalls : -1);
(void)fprintf(stderr, "TestOnceFunctionCalls: %" PRId32 "\n",
pTestOnceFunctionCalls ? *pTestOnceFunctionCalls : -1);
winpr_aligned_free(pErrors);
winpr_aligned_free(pTestThreadFunctionCalls);
winpr_aligned_free(pTestOnceFunctionCalls);
winpr_aligned_free(pInitOnceExecuteOnceCalls);
(void)CloseHandle(hStartEvent);
for (DWORD i = 0; i < dwCreatedThreads; i++)
{
(void)CloseHandle(hThreads[i]);
}
return (result ? 0 : 1);
}

View File

@@ -0,0 +1,255 @@
#include <stdlib.h>
#include <winpr/crt.h>
#include <winpr/crypto.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#define THREADS 8
static UINT32 prand(UINT32 max)
{
UINT32 tmp = 0;
if (max <= 1)
return 1;
winpr_RAND(&tmp, sizeof(tmp));
return tmp % (max - 1) + 1;
}
static DWORD WINAPI test_thread(LPVOID arg)
{
UINT32 timeout = 50 + prand(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(nullptr, 0, test_thread, nullptr, CREATE_SUSPENDED, nullptr);
if (!threads[i])
{
(void)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]))
{
(void)fprintf(stderr, "%s: CloseHandle [%" PRIu32 "] failure\n", __func__, i);
rc = -1;
}
threads[i] = nullptr;
}
return rc;
}
static BOOL TestWaitForAll(void)
{
BOOL rc = FALSE;
HANDLE threads[THREADS] = WINPR_C_ARRAY_INIT;
/* WaitForAll, timeout */
if (start_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: start_threads failed\n", __func__);
goto fail;
}
const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, 10);
if (ret != WAIT_TIMEOUT)
{
(void)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)
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
goto fail;
}
rc = TRUE;
fail:
if (close_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: close_threads failed\n", __func__);
return FALSE;
}
return rc;
}
static BOOL TestWaitOne(void)
{
BOOL rc = FALSE;
HANDLE threads[THREADS] = WINPR_C_ARRAY_INIT;
/* WaitForAll, timeout */
if (start_threads(ARRAYSIZE(threads), threads))
{
(void)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)))
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects INFINITE failed\n", __func__);
goto fail;
}
if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
goto fail;
}
rc = TRUE;
fail:
if (close_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: close_threads failed\n", __func__);
return FALSE;
}
return rc;
}
static BOOL TestWaitOneTimeout(void)
{
BOOL rc = FALSE;
HANDLE threads[THREADS] = WINPR_C_ARRAY_INIT;
/* WaitForAll, timeout */
if (start_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: start_threads failed\n", __func__);
goto fail;
}
const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 1);
if (ret != WAIT_TIMEOUT)
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects timeout 50 failed, ret=%d\n", __func__,
ret);
goto fail;
}
if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
goto fail;
}
rc = TRUE;
fail:
if (close_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: close_threads failed\n", __func__);
return FALSE;
}
return rc;
}
static BOOL TestWaitOneTimeoutMultijoin(void)
{
BOOL rc = FALSE;
HANDLE threads[THREADS] = WINPR_C_ARRAY_INIT;
/* WaitForAll, timeout */
if (start_threads(ARRAYSIZE(threads), threads))
{
(void)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)
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects timeout 0 failed, ret=%d\n", __func__,
ret);
goto fail;
}
}
if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
{
(void)fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
goto fail;
}
rc = TRUE;
fail:
if (close_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: close_threads failed\n", __func__);
return FALSE;
}
return rc;
}
static BOOL TestDetach(void)
{
BOOL rc = FALSE;
HANDLE threads[THREADS] = WINPR_C_ARRAY_INIT;
/* WaitForAll, timeout */
if (start_threads(ARRAYSIZE(threads), threads))
{
(void)fprintf(stderr, "%s: start_threads failed\n", __func__);
goto fail;
}
rc = TRUE;
fail:
if (close_threads(ARRAYSIZE(threads), threads))
{
(void)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;
}

View File

@@ -0,0 +1,258 @@
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
static BOOL test_mutex_basic(void)
{
HANDLE mutex = nullptr;
DWORD rc = 0;
if (!(mutex = CreateMutex(nullptr, FALSE, nullptr)))
{
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 = nullptr;
DWORD rc = 0;
DWORD cnt = 50;
if (!(mutex = CreateMutex(nullptr, TRUE, nullptr)))
{
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 = nullptr;
static HANDLE thread1_mutex2 = nullptr;
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)
{
(void)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)
{
(void)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)
{
(void)fprintf(stderr,
"%s: WaitForSingleObject on thread1_mutex2 unexpectedly returned %" PRIu32
" instead of WAIT_OBJECT_0\n",
__func__, rc);
return 0;
}
if (!ReleaseMutex(thread1_mutex2))
{
(void)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 = nullptr;
HANDLE hStartEvent = nullptr;
if (!(thread1_mutex1 = CreateMutex(nullptr, TRUE, nullptr)))
{
printf("%s: CreateMutex thread1_mutex1 failed\n", __func__);
goto fail;
}
if (!(thread1_mutex2 = CreateMutex(nullptr, FALSE, nullptr)))
{
printf("%s: CreateMutex thread1_mutex2 failed\n", __func__);
goto fail;
}
if (!(hStartEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
{
(void)fprintf(stderr, "%s: error creating start event\n", __func__);
goto fail;
}
thread1_failed = TRUE;
if (!(hThread = CreateThread(nullptr, 0, test_mutex_thread1, (LPVOID)hStartEvent, 0, nullptr)))
{
(void)fprintf(stderr, "%s: error creating test_mutex_thread_1\n", __func__);
goto fail;
}
Sleep(100);
if (!thread1_failed)
{
(void)fprintf(stderr, "%s: thread1 premature success\n", __func__);
goto fail;
}
(void)SetEvent(hStartEvent);
if (WaitForSingleObject(hThread, 2000) != WAIT_OBJECT_0)
{
(void)fprintf(stderr, "%s: thread1 premature success\n", __func__);
goto fail;
}
if (thread1_failed)
{
(void)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;
}
(void)CloseHandle(hThread);
(void)CloseHandle(hStartEvent);
(void)CloseHandle(thread1_mutex1);
(void)CloseHandle(thread1_mutex2);
return TRUE;
fail:
(void)ReleaseMutex(thread1_mutex1);
(void)ReleaseMutex(thread1_mutex2);
(void)CloseHandle(thread1_mutex1);
(void)CloseHandle(thread1_mutex2);
(void)CloseHandle(hStartEvent);
(void)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;
}

View File

@@ -0,0 +1,21 @@
#include <winpr/crt.h>
#include <winpr/synch.h>
int TestSynchSemaphore(int argc, char* argv[])
{
HANDLE semaphore = nullptr;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
semaphore = CreateSemaphore(nullptr, 0, 1, nullptr);
if (!semaphore)
{
printf("CreateSemaphore failure\n");
return -1;
}
(void)CloseHandle(semaphore);
return 0;
}

View File

@@ -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 = nullptr;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
thread = CreateThread(nullptr, 0, test_thread, nullptr, 0, nullptr);
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(nullptr, 0, test_thread, nullptr, 0, nullptr);
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(nullptr, 0, test_thread, nullptr, 0, nullptr);
if (!thread)
{
printf("CreateThread failure\n");
return -1;
}
if (!CloseHandle(thread))
{
printf("CloseHandle failed!");
return -1;
}
return 0;
}

View File

@@ -0,0 +1,120 @@
#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)
{
WINPR_UNUSED(TimerOrWaitFired);
if (!lpParam)
return;
APC_DATA* apcData = (APC_DATA*)lpParam;
const UINT32 CurrentTime = GetTickCount();
const INT64 TimerTime = 1ll * CurrentTime - apcData->StartTime;
const INT64 expectedTime =
1ll * apcData->DueTime + (1ll * apcData->Period * apcData->FireCount);
apcData->FireCount++;
printf("TimerRoutine: TimerId: %" PRIu32 " FireCount: %" PRIu32 " ActualTime: %" PRId64
" ExpectedTime: %" PRId64 " Discrepancy: %" PRId64 "\n",
apcData->TimerId, apcData->FireCount, TimerTime, expectedTime, TimerTime - expectedTime);
Sleep(11);
if (apcData->FireCount == apcData->MaxFireCount)
{
(void)SetEvent(apcData->CompletionEvent);
}
}
int TestSynchTimerQueue(int argc, char* argv[])
{
HANDLE hTimers[TIMER_COUNT] = WINPR_C_ARRAY_INIT;
APC_DATA apcData[TIMER_COUNT] = WINPR_C_ARRAY_INIT;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
HANDLE 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(nullptr, TRUE, FALSE, nullptr)))
{
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;
}
(void)CloseHandle(apcData[index].CompletionEvent);
}
if (!DeleteTimerQueue(hTimerQueue))
{
printf("DeleteTimerQueue failed (%" PRIu32 ")\n", GetLastError());
return -1;
}
return 0;
}

View File

@@ -0,0 +1,83 @@
#include <winpr/crt.h>
#include <winpr/synch.h>
int TestSynchWaitableTimer(int argc, char* argv[])
{
DWORD status = 0;
HANDLE timer = nullptr;
LONG period = 0;
LARGE_INTEGER due = WINPR_C_ARRAY_INIT;
int result = -1;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
timer = CreateWaitableTimer(nullptr, FALSE, nullptr);
if (!timer)
{
printf("CreateWaitableTimer failure\n");
goto out;
}
due.QuadPart = -1500000LL; /* 0.15 seconds */
if (!SetWaitableTimer(timer, &due, 0, nullptr, nullptr, 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, nullptr, nullptr, 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, nullptr, nullptr, 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:
(void)CloseHandle(timer);
return result;
}

View File

@@ -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 = nullptr;
struct apc_data
{
UINT32 StartTime;
};
typedef struct apc_data APC_DATA;
static VOID CALLBACK TimerAPCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
APC_DATA* apcData = nullptr;
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)
{
(void)SetEvent(g_Event);
}
}
int TestSynchWaitableTimerAPC(int argc, char* argv[])
{
int status = -1;
DWORD rc = 0;
HANDLE hTimer = nullptr;
BOOL bSuccess = 0;
LARGE_INTEGER due = WINPR_C_ARRAY_INIT;
APC_DATA apcData = WINPR_C_ARRAY_INIT;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
g_Event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (!g_Event)
{
printf("Failed to create event\n");
goto cleanup;
}
hTimer = CreateWaitableTimer(nullptr, FALSE, nullptr);
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)
(void)CloseHandle(hTimer);
if (g_Event)
(void)CloseHandle(g_Event);
return status;
}