mirror of https://github.com/dahall/Vanara.git
1001 lines
28 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |