Vanara/UnitTests/PInvoke/Kernel32/SynchApiTests.cs

1001 lines
28 KiB
C#

using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Vanara.Extensions;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.PInvoke.Tests
{
[TestFixture]
public class SynchApiTests
{
[Test]
public void ConditionVariableTest()
{
const int BUFFER_SIZE = 10;
const int PRODUCER_SLEEP_TIME_MS = 50;
const int CONSUMER_SLEEP_TIME_MS = 200;
var Buffer = new int[BUFFER_SIZE];
int LastItemProduced = 0;
uint QueueSize = 0, QueueStartOffset = 0, TotalItemsProduced = 0, TotalItemsConsumed = 0;
var StopRequested = false;
var rand = new Random();
InitializeConditionVariable(out var BufferNotEmpty);
InitializeConditionVariable(out var BufferNotFull);
InitializeCriticalSection(out var BufferLock);
using (SafeHTHREAD hProducer1 = CreateThread(null, 0, ProducerThreadProc, (IntPtr)1L, 0, out _),
hConsumer1 = CreateThread(null, 0, ConsumerThreadProc, (IntPtr)1L, 0, out _),
hConsumer2 = CreateThread(null, 0, ConsumerThreadProc, (IntPtr)2L, 0, out _))
{
SleepEx(5000, false);
EnterCriticalSection(ref BufferLock);
StopRequested = true;
LeaveCriticalSection(ref BufferLock);
WakeAllConditionVariable(ref BufferNotFull);
WakeAllConditionVariable(ref BufferNotEmpty);
WaitForMultipleObjects(new[] { hProducer1, hConsumer1, hConsumer2 }, true, INFINITE);
TestContext.WriteLine("TotalItemsProduced: {0}, TotalItemsConsumed: {1}", TotalItemsProduced, TotalItemsConsumed);
}
uint ProducerThreadProc(IntPtr p)
{
var ProducerId = p.ToInt64();
while (true)
{
// Produce a new item.
Sleep((uint)rand.Next(PRODUCER_SLEEP_TIME_MS));
var Item = InterlockedIncrement(ref LastItemProduced);
EnterCriticalSection(ref BufferLock);
while (QueueSize == BUFFER_SIZE && !StopRequested)
{
// Buffer is full - sleep so consumers can get items.
SleepConditionVariableCS(ref BufferNotFull, ref BufferLock, INFINITE);
}
if (StopRequested)
{
LeaveCriticalSection(ref BufferLock);
break;
}
// Insert the item at the end of the queue and increment size.
Buffer[(QueueStartOffset + QueueSize) % BUFFER_SIZE] = Item;
QueueSize++;
TotalItemsProduced++;
System.Diagnostics.Debug.Write($"Producer {ProducerId}: item {Item}, queue size {QueueSize}\r\n");
LeaveCriticalSection(ref BufferLock);
// If a consumer is waiting, wake it.
WakeConditionVariable(ref BufferNotEmpty);
}
System.Diagnostics.Debug.Write($"Producer {ProducerId} exiting\r\n");
return 0;
}
uint ConsumerThreadProc(IntPtr p)
{
var ConsumerId = p.ToInt64();
while (true)
{
EnterCriticalSection(ref BufferLock);
while (QueueSize == 0 && !StopRequested)
{
// Buffer is empty - sleep so producers can create items.
SleepConditionVariableCS(ref BufferNotEmpty, ref BufferLock, INFINITE);
}
if (StopRequested && QueueSize == 0)
{
LeaveCriticalSection(ref BufferLock);
break;
}
// Consume the first available item.
var Item = Buffer[QueueStartOffset];
QueueSize--;
QueueStartOffset++;
TotalItemsConsumed++;
if (QueueStartOffset == BUFFER_SIZE)
{
QueueStartOffset = 0;
}
System.Diagnostics.Debug.Write($"Consumer {ConsumerId}: item {Item}, queue size {QueueSize}\r\n");
LeaveCriticalSection(ref BufferLock);
// If a producer is waiting, wake it.
WakeConditionVariable(ref BufferNotFull);
// Simulate processing of the item.
Sleep((uint)rand.Next(CONSUMER_SLEEP_TIME_MS));
}
System.Diagnostics.Debug.Write($"Consumer {ConsumerId} exiting\r\n");
return 0;
}
}
[Test]
public void ConditionVariableTest2()
{
InitializeSRWLock(out var cond_rwlock);
InitializeConditionVariable(out var start_condition);
var wake_all = false;
var cnt = 0;
try
{
var ghThreads = new SafeHTHREAD[5];
for (int i = 0; i < ghThreads.Length; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
AcquireSRWLockExclusive(ref cond_rwlock);
// set the flag to true, then wake all threads
wake_all = true;
WakeAllConditionVariable(ref start_condition);
ReleaseSRWLockExclusive(ref cond_rwlock);
WaitForMultipleObjects(ghThreads, true, INFINITE);
foreach (var t in ghThreads) t.Dispose();
Assert.That(cnt, Is.EqualTo(ghThreads.Length));
}
finally
{
}
uint ThreadProc(IntPtr _)
{
AcquireSRWLockShared(ref cond_rwlock);
// main thread sets wake_all to true and calls WakeAllConditionVariable()
// so this thread should start doing the work (?)
while (!wake_all)
SleepConditionVariableSRW(ref start_condition, ref cond_rwlock, INFINITE, CONDITION_VARIABLE_FLAGS.CONDITION_VARIABLE_LOCKMODE_SHARED);
InterlockedIncrement(ref cnt);
return 0;
}
}
[Test]
public void CriticalSectionAndSpinCountTest()
{
Assert.That(InitializeCriticalSectionAndSpinCount(out var critSect, 400), ResultIs.Successful);
try
{
using (var hThread = CreateThread(null, 0, ThreadProc, default, 0, out _))
{
WaitForSingleObject(hThread, INFINITE);
Assert.That(GetExitCodeThread(hThread, out var c), ResultIs.Successful);
Assert.That(c, Is.Zero);
}
}
finally
{
DeleteCriticalSection(ref critSect);
}
uint ThreadProc(IntPtr _)
{
if (TryEnterCriticalSection(ref critSect))
{
Sleep(5);
LeaveCriticalSection(ref critSect);
return 0;
}
return 1;
}
}
[Test]
public void CriticalSectionExTest()
{
Assert.That(InitializeCriticalSectionEx(out var critSect, 400, CRITICAL_SECTION_FLAGS.CRITICAL_SECTION_FLAG_NO_DEBUG_INFO), ResultIs.Successful);
try
{
using (var hThread = CreateThread(null, 0, ThreadProc, default, 0, out _))
{
WaitForSingleObject(hThread, INFINITE);
Assert.That(GetExitCodeThread(hThread, out var c), ResultIs.Successful);
Assert.That(c, Is.Zero);
}
}
finally
{
DeleteCriticalSection(ref critSect);
}
uint ThreadProc(IntPtr _)
{
EnterCriticalSection(ref critSect);
Sleep(5);
LeaveCriticalSection(ref critSect);
return 0;
}
}
[Test]
public void CriticalSectionTest()
{
InitializeCriticalSection(out var critSect);
SetCriticalSectionSpinCount(ref critSect, 400);
try
{
using (var hThread = CreateThread(null, 0, ThreadProc, default, 0, out _))
{
WaitForSingleObject(hThread, INFINITE);
Assert.That(GetExitCodeThread(hThread, out var c), ResultIs.Successful);
Assert.That(c, Is.Zero);
}
}
finally
{
DeleteCriticalSection(ref critSect);
}
uint ThreadProc(IntPtr _)
{
EnterCriticalSection(ref critSect);
Sleep(5);
LeaveCriticalSection(ref critSect);
return 0;
}
}
[Test]
public void EventExTest()
{
const string name = "myEvent";
const int THREADCOUNT = 4;
// Create an unnamed waitable timer.
using (var ghWriteEvent = CreateEventEx(null, name, CREATE_EVENT_FLAGS.CREATE_EVENT_MANUAL_RESET, (uint)SynchronizationObjectAccess.EVENT_ALL_ACCESS))
{
Assert.That(ghWriteEvent.IsNull, Is.False);
// Create multiple threads to read from the buffer.
var ghThreads = new SafeHTHREAD[THREADCOUNT];
for (int i = 0; i < THREADCOUNT; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
TestContext.Write("Main thread writing to the shared buffer...\n");
// Set ghWriteEvent to signaled
if (!SetEvent(ghWriteEvent))
{
TestContext.Write("SetEvent failed ({0})\n", GetLastError());
return;
}
TestContext.Write("Main thread waiting for threads to exit...\n");
// The handle for each thread is signaled when the thread is terminated.
switch (WaitForMultipleObjects(ghThreads, true, INFINITE))
{
// All thread objects were signaled
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("All threads ended, cleaning up for application exit...\n");
break;
// An error occurred
default:
TestContext.Write("WaitForMultipleObjects failed ({0})\n", GetLastError());
return;
}
// Close thread handles
foreach (var t in ghThreads)
t.Dispose();
}
uint ThreadProc(IntPtr _)
{
var id = GetCurrentThreadId();
TestContext.Write("Thread {0} waiting for write event...\n", id);
using (var hEvent = OpenEvent((uint)SynchronizationObjectAccess.EVENT_ALL_ACCESS, false, name))
{
switch (WaitForSingleObject(hEvent, INFINITE))
{
// Event object was signaled
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("Thread {0} reading from buffer\n", id);
break;
// An error occurred
default:
TestContext.Write("Wait error ({0})\n", GetLastError());
return 0;
}
}
TestContext.Write("Thread {0} exiting\n", id);
return 1;
}
}
[Test]
public void EventTest()
{
const string name = "myEvent";
const int THREADCOUNT = 4;
// Create an unnamed waitable timer.
SafeEventHandle ghWriteEvent;
using (ghWriteEvent = CreateEvent(null, true, false, name))
{
Assert.That(ghWriteEvent.IsNull, Is.False);
// Create multiple threads to read from the buffer.
var ghThreads = new SafeHTHREAD[THREADCOUNT];
for (int i = 0; i < THREADCOUNT; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
TestContext.Write("Main thread writing to the shared buffer...\n");
// Set ghWriteEvent to signaled
Assert.That(SetEvent(ghWriteEvent), ResultIs.Successful);
TestContext.Write("Main thread waiting for threads to exit...\n");
// The handle for each thread is signaled when the thread is terminated.
switch (WaitForMultipleObjects(ghThreads, true, INFINITE))
{
// All thread objects were signaled
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("All threads ended, cleaning up for application exit...\n");
break;
// An error occurred
default:
TestContext.Write("WaitForMultipleObjects failed ({0})\n", GetLastError());
return;
}
// Close thread handles
foreach (var t in ghThreads)
t.Dispose();
Assert.That(ResetEvent(ghWriteEvent), ResultIs.Successful);
Assert.That(PulseEvent(ghWriteEvent), ResultIs.Successful);
}
uint ThreadProc(IntPtr _)
{
var id = GetCurrentThreadId();
TestContext.Write("Thread {0} waiting for write event...\n", id);
switch (WaitForSingleObject(ghWriteEvent, INFINITE))
{
// Event object was signaled
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("Thread {0} reading from buffer\n", id);
break;
// An error occurred
default:
TestContext.Write("Wait error ({0})\n", GetLastError());
return 0;
}
TestContext.Write("Thread {0} exiting\n", id);
return 1;
}
}
[Test]
public void MutexExTest()
{
const string name = "myMutex";
const int THREADCOUNT = 4;
// Create a mutex with no initial owner
using (var ghMutex = CreateMutexEx(null, name, 0, (uint)SynchronizationObjectAccess.MUTEX_ALL_ACCESS))
{
Assert.That(ghMutex.IsNull, Is.False);
// Create worker threads
var ghThreads = new SafeHTHREAD[THREADCOUNT];
for (int i = 0; i < THREADCOUNT; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
// Wait for all threads to terminate using plain handles
var hThreads = Array.ConvertAll(ghThreads, h => (HTHREAD)h);
WaitForMultipleObjectsEx(hThreads, true, INFINITE, false);
// Close thread and mutex handles
foreach (var t in ghThreads)
{
TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}");
t.Dispose();
}
}
uint ThreadProc(IntPtr _)
{
var id = GetCurrentThreadId();
// Request ownership of mutex.
using (var hMut = OpenMutex((uint)SynchronizationObjectAccess.MUTEX_ALL_ACCESS, false, name))
{
if (hMut.IsNull) return (uint)Win32Error.GetLastError();
for (int i = 0; i < 20; i++)
{
switch (WaitForSingleObject(hMut, INFINITE))
{
// The thread got ownership of the mutex
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("Thread {0} writing to database...\n", id);
if (!ReleaseMutex(hMut)) return (uint)Win32Error.GetLastError();
break;
// The thread got ownership of an abandoned mutex The database is in an indeterminate state
case WAIT_STATUS.WAIT_ABANDONED:
return 0;
}
}
}
return 1;
}
}
[Test]
public void MutexTest()
{
const string name = "myMutex";
const int THREADCOUNT = 4;
// Create a mutex with no initial owner
using (var ghMutex = CreateMutex(null, false, name))
{
Assert.That(ghMutex.IsNull, Is.False);
// Create worker threads
var ghThreads = new SafeHTHREAD[THREADCOUNT];
for (int i = 0; i < THREADCOUNT; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
// Wait for all threads to terminate
WaitForMultipleObjects(ghThreads, true, INFINITE);
// Close thread and mutex handles
foreach (var t in ghThreads)
{
TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}");
t.Dispose();
}
}
uint ThreadProc(IntPtr _)
{
var id = GetCurrentThreadId();
// Request ownership of mutex.
using (var hMut = OpenMutex((uint)SynchronizationObjectAccess.MUTEX_ALL_ACCESS, false, name))
{
if (hMut.IsNull) return (uint)Win32Error.GetLastError();
for (int i = 0; i < 20; i++)
{
switch (WaitForSingleObject(hMut, INFINITE))
{
// The thread got ownership of the mutex
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("Thread {0} writing to database...\n", id);
if (!ReleaseMutex(hMut)) return (uint)Win32Error.GetLastError();
break;
// The thread got ownership of an abandoned mutex The database is in an indeterminate state
case WAIT_STATUS.WAIT_ABANDONED:
return 0;
}
}
}
return 1;
}
}
[Test]
public void SemaphoreExTest()
{
const string name = "mySema4";
const int THREADCOUNT = 12;
// Create a mutex with no initial owner
using (var ghSemaphore = CreateSemaphoreEx(null, 10, 10, name, 0, (uint)SynchronizationObjectAccess.SEMAPHORE_ALL_ACCESS))
{
Assert.That(ghSemaphore.IsNull, Is.False);
// Create worker threads
var ghThreads = new SafeHTHREAD[THREADCOUNT];
for (int i = 0; i < THREADCOUNT; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
// Wait for all threads to terminate
WaitForMultipleObjects(ghThreads, true, INFINITE);
// Close thread and mutex handles
foreach (var t in ghThreads)
{
TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}");
t.Dispose();
}
}
uint ThreadProc(IntPtr _)
{
var id = GetCurrentThreadId();
using (var hSem = OpenSemaphore((uint)SynchronizationObjectAccess.SEMAPHORE_ALL_ACCESS, false, name))
{
if (hSem.IsNull) return (uint)Win32Error.GetLastError();
var loop = true;
while (loop)
{
// Try to enter the semaphore gate.
switch (WaitForSingleObject(hSem, 0))
{
// The semaphore object was signaled.
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("Thread {0} wait succeeded.\n", id);
loop = false;
// Simulate thread spending time on task
Sleep(5);
// Release the semaphore when task is finished
if (!ReleaseSemaphore(hSem, 1, out var _)) return (uint)Win32Error.GetLastError();
break;
// The semaphore was nonsignaled, so a time-out occurred.
case WAIT_STATUS.WAIT_TIMEOUT:
break;
}
}
}
return 1;
}
}
[Test]
public void SemaphoreTest()
{
const string name = "mySema4";
const int THREADCOUNT = 12;
// Create a mutex with no initial owner
using (var ghSemaphore = CreateSemaphore(null, 10, 10, name))
{
Assert.That(ghSemaphore.IsNull, Is.False);
// Create worker threads
var ghThreads = new SafeHTHREAD[THREADCOUNT];
for (int i = 0; i < THREADCOUNT; i++)
{
ghThreads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(ghThreads[i].IsNull, Is.False);
}
// Wait for all threads to terminate
WaitForMultipleObjects(ghThreads, true, INFINITE);
// Close thread and mutex handles
foreach (var t in ghThreads)
{
TestContext.WriteLine($"Thread {GetThreadId(t)} completed with code {(GetExitCodeThread(t, out var c) ? c : 0U)}");
t.Dispose();
}
}
uint ThreadProc(IntPtr _)
{
var id = GetCurrentThreadId();
using (var hSem = OpenSemaphore((uint)SynchronizationObjectAccess.SEMAPHORE_ALL_ACCESS, false, name))
{
if (hSem.IsNull) return (uint)Win32Error.GetLastError();
var loop = true;
while (loop)
{
// Try to enter the semaphore gate.
switch (WaitForSingleObject(hSem, 0))
{
// The semaphore object was signaled.
case WAIT_STATUS.WAIT_OBJECT_0:
TestContext.Write("Thread {0} wait succeeded.\n", id);
loop = false;
// Simulate thread spending time on task
Sleep(5);
// Release the semaphore when task is finished
if (!ReleaseSemaphore(hSem, 1, out var _)) return (uint)Win32Error.GetLastError();
break;
// The semaphore was nonsignaled, so a time-out occurred.
case WAIT_STATUS.WAIT_TIMEOUT:
break;
}
}
}
return 1;
}
}
[Test]
public void SRWLockTest()
{
InitializeSRWLock(out var srwlock);
Assert.That(TryAcquireSRWLockExclusive(ref srwlock), Is.True);
ReleaseSRWLockExclusive(ref srwlock);
AcquireSRWLockExclusive(ref srwlock);
ReleaseSRWLockExclusive(ref srwlock);
Assert.That(TryAcquireSRWLockShared(ref srwlock), Is.True);
ReleaseSRWLockShared(ref srwlock);
AcquireSRWLockShared(ref srwlock);
ReleaseSRWLockShared(ref srwlock);
}
[Test]
public void SyncBarrierTest()
{
const int MAX_SLEEP_MS = 32;
var dwMinThreads = Environment.ProcessorCount;
var dwMaxThreads = dwMinThreads * 4;
var dwNumLoops = 50U;
SYNCHRONIZATION_BARRIER gBarrier;
var gErrorCount = 0;
SafeEventHandle gStartEvent;
(int threadCount, int trueCount, int falseCount, uint loops, SYNC_BARRIER_FLAGS flags) p;
var rand = new Random();
/* Test invalid parameters */
Assert.That(InitializeSynchronizationBarrier(out _, 0, -1), ResultIs.Failure);
Assert.That(InitializeSynchronizationBarrier(out _, -1, -1), ResultIs.Failure);
Assert.That(InitializeSynchronizationBarrier(out _, 1, -2), ResultIs.Failure);
/* Functional tests */
TestSynchBarrierWithFlags(0, dwMaxThreads, dwNumLoops);
TestSynchBarrierWithFlags(SYNC_BARRIER_FLAGS.SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY, dwMinThreads, dwNumLoops);
TestSynchBarrierWithFlags(SYNC_BARRIER_FLAGS.SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY, dwMaxThreads, dwNumLoops);
void TestSynchBarrierWithFlags(SYNC_BARRIER_FLAGS dwFlags, int dwThreads, uint dwLoops)
{
p = (0, 0, 0, dwLoops, dwFlags);
Assert.That(InitializeSynchronizationBarrier(out gBarrier, dwThreads, -1), ResultIs.Successful);
try
{
using (gStartEvent = CreateEvent(null, true, false, null))
{
Assert.That(gStartEvent.IsNull, Is.False);
var threads = new SafeHTHREAD[dwThreads];
int i;
for (i = 0; i < dwThreads; i++)
{
threads[i] = CreateThread(null, 0, test_synch_barrier_thread, default, 0, out _);
Assert.That(threads[i].IsNull, Is.False);
}
if (!SetEvent(gStartEvent))
{
TestContext.WriteLine($"SetEvent(gStartEvent) failed with error = {Win32Error.GetLastError()}");
InterlockedIncrement(ref gErrorCount);
}
while (i-- > 0)
{
WAIT_STATUS dwStatus;
if (WAIT_STATUS.WAIT_OBJECT_0 != (dwStatus = WaitForSingleObject(threads[i], INFINITE)))
{
TestContext.WriteLine($"WaitForSingleObject(thread[{i}] unexpectedly returned {dwStatus} (error = {Win32Error.GetLastError()})");
InterlockedIncrement(ref gErrorCount);
}
threads[i].Dispose();
}
}
}
finally
{
DeleteSynchronizationBarrier(ref gBarrier);
}
Assert.That(p.threadCount, Is.EqualTo(dwThreads));
Assert.That(p.trueCount, Is.EqualTo(dwLoops));
Assert.That(p.falseCount, Is.EqualTo(dwLoops * (dwThreads - 1)));
}
uint test_synch_barrier_thread(IntPtr _)
{
InterlockedIncrement(ref p.threadCount);
/* wait for start event from main */
if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_STATUS.WAIT_OBJECT_0)
{
InterlockedIncrement(ref gErrorCount);
return 1;
}
for (var i = 0; i < p.loops && gErrorCount == 0; i++)
{
/* simulate different execution times before the barrier */
Sleep((uint)rand.Next(MAX_SLEEP_MS));
if (EnterSynchronizationBarrier(ref gBarrier, p.flags))
InterlockedIncrement(ref p.trueCount);
else
InterlockedIncrement(ref p.falseCount);
}
return 0;
}
}
[Test]
public void TimerQueueTest()
{
SafeEventHandle gDoneEvent;
bool? timerOrWaitFired = null;
// Use an event object to track the TimerRoutine execution
using (gDoneEvent = CreateEvent(null, true, false, null))
{
Assert.That(gDoneEvent.IsNull, Is.False);
// Create the timer queue.
using (var hTimerQueue = CreateTimerQueue())
{
Assert.That(hTimerQueue.IsNull, Is.False);
// Set a timer to call the timer routine in 4 seconds.
Assert.That(CreateTimerQueueTimer(out var hTimer, hTimerQueue, TimerRoutine, default, 4000, 0, 0), ResultIs.Successful);
// TODO: Do other useful work here
// Wait for the timer-queue thread to complete using an event object. The thread will signal the event at that time.
Assert.That(WaitForSingleObject(gDoneEvent, INFINITE), Is.EqualTo(WAIT_STATUS.WAIT_OBJECT_0));
Assert.That(timerOrWaitFired.HasValue, Is.True);
Assert.That(timerOrWaitFired.Value, Is.True);
}
}
void TimerRoutine(IntPtr lpParameter, bool TimerOrWaitFired)
{
timerOrWaitFired = TimerOrWaitFired;
SetEvent(gDoneEvent);
}
}
[Test]
public void WaitableTimerExTest()
{
// Create an unnamed waitable timer.
using (var hTimer = CreateWaitableTimerEx(null, null, CREATE_WAITABLE_TIMER_FLAG.CREATE_WAITABLE_TIMER_MANUAL_RESET, (uint)SynchronizationObjectAccess.TIMER_ALL_ACCESS))
{
Assert.That(hTimer.IsNull, Is.False);
// Set a timer to wait for 2 seconds.
var liDueTime = TimeSpan.FromSeconds(2).ToFileTimeStruct();
Assert.That(SetWaitableTimerEx(hTimer, liDueTime, 0, null, default, new REASON_CONTEXT("Because"), 50), ResultIs.Successful);
// Wait for the timer.
Assert.That(WaitForSingleObject(hTimer, INFINITE), Is.EqualTo(WAIT_STATUS.WAIT_OBJECT_0));
}
}
[Test]
public void WaitableTimerTest()
{
const string tName = "myTimer";
// Create an unnamed waitable timer.
using (var hTimer = CreateWaitableTimer(null, true, tName))
{
Assert.That(hTimer.IsNull, Is.False);
new System.Threading.Thread(ThreadProc).Start();
// Set a timer to wait for 2 seconds.
var liDueTime = TimeSpan.FromSeconds(2).ToFileTimeStruct();
Assert.That(SetWaitableTimer(hTimer, liDueTime, 0, null, default, false), ResultIs.Successful);
// Wait for the timer.
Assert.That(WaitForSingleObject(hTimer, INFINITE), Is.EqualTo(WAIT_STATUS.WAIT_OBJECT_0));
// Set a timer to wait for 2 seconds.
Assert.That(SetWaitableTimer(hTimer, liDueTime), ResultIs.Successful);
Assert.That(CancelWaitableTimer(hTimer), ResultIs.Successful);
}
void ThreadProc()
{
using (var hThrTimer = OpenWaitableTimer((uint)SynchronizationObjectAccess.TIMER_ALL_ACCESS, false, tName))
{
Assert.That(hThrTimer.IsNull, Is.False);
}
}
}
[Test]
public void InitOnceSyncTest()
{
const int ctxVal = 1 << INIT_ONCE_CTX_RESERVED_BITS;
SafeEventHandle gStartEvent;
INIT_ONCE gInitOnce = default;
var initCount = 0;
using (gStartEvent = CreateEvent(null, true, false, null))
{
Assert.That(gStartEvent.IsNull, Is.False);
var threads = new SafeHTHREAD[10];
for (var i = 0; i < threads.Length; i++)
{
threads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(threads[i].IsNull, Is.False);
}
Assert.That(SetEvent(gStartEvent), ResultIs.Successful);
WaitForMultipleObjects(threads, true, INFINITE);
Assert.That(threads.Select(t => GetExitCodeThread(t, out var c) ? c : 0), Has.All.EqualTo(ctxVal));
Assert.That(initCount, Is.EqualTo(1));
foreach (var t in threads) t.Dispose();
}
uint ThreadProc(IntPtr _)
{
if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_STATUS.WAIT_OBJECT_0)
return 0;
InitOnceExecuteOnce(ref gInitOnce, InitFunc, default, out var ctx);
return (uint)ctx.ToInt32();
}
bool InitFunc(ref INIT_ONCE InitOnce, IntPtr Parameter, out IntPtr Context)
{
//Sleep(500);
Context = (IntPtr)ctxVal;
InterlockedIncrement(ref initCount);
return true;
}
}
[Test]
public void InitOnceSyncNoCallbackTest()
{
const int ctxVal = 1 << INIT_ONCE_CTX_RESERVED_BITS;
SafeEventHandle gStartEvent;
InitOnceInitialize(out var gInitOnce);
using (gStartEvent = CreateEvent(null, true, false, null))
{
Assert.That(gStartEvent.IsNull, Is.False);
var threads = new SafeHTHREAD[10];
for (var i = 0; i < threads.Length; i++)
{
threads[i] = CreateThread(null, 0, ThreadProc, default, 0, out _);
Assert.That(threads[i].IsNull, Is.False);
}
Assert.That(SignalObjectAndWait(gStartEvent, threads[0], INFINITE, false), ResultIs.Value(WAIT_STATUS.WAIT_OBJECT_0));
WaitForMultipleObjects(threads, true, INFINITE);
Assert.That(threads.Select(t => GetExitCodeThread(t, out var c) ? c : 0), Has.All.EqualTo(ctxVal));
foreach (var t in threads) t.Dispose();
}
uint ThreadProc(IntPtr _)
{
// Start all threads at the same time
if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_STATUS.WAIT_OBJECT_0)
return 0;
InitOnceBeginInitialize(ref gInitOnce, 0, out var pending, out var ctx);
if (pending)
{
var newctx = (IntPtr)ctxVal;
InitOnceComplete(ref gInitOnce, 0, newctx);
return (uint)newctx.ToInt32();
}
return (uint)ctx.ToInt32();
}
}
[Test]
public void InterlockedCompareExchangeTest()
{
const int dest = 128;
var destVar = dest;
Assert.That(InterlockedCompareExchange(ref destVar, 64, dest), Is.EqualTo(dest));
Assert.That(destVar, Is.EqualTo(64));
}
[Test]
public unsafe void WakeByWaitOnAddressTest()
{
uint g_ulState = 0;
var cnt = 0;
var threads = new SafeHTHREAD[10];
for (var i = 0; i < threads.Length; i++)
{
threads[i] = CreateThread(null, 0, ThreadProc, &g_ulState, 0, out _);
Assert.That(threads[i].IsNull, Is.False);
}
for (var i = threads.Length / 2; i >= 0; i--)
{
g_ulState = (uint)i;
if (g_ulState == 0)
{
WakeByAddressAll(&g_ulState);
break;
}
WakeByAddressSingle(&g_ulState);
}
WaitForMultipleObjects(threads, true, INFINITE);
foreach (var t in threads) t.Dispose();
Assert.That(cnt, Is.EqualTo(threads.Length));
unsafe uint ThreadProc(void* pState)
{
uint ulUndesire = 0;
WaitOnAddress(pState, &ulUndesire, sizeof(uint), INFINITE);
InterlockedIncrement(ref cnt);
return 0;
}
}
}
}