using System;
using System.Threading;
using System.Collections.Generic;
using NUnit.Framework;
using MPFramework.AppCore.QualityAssurance;
using MPFramework.AppCore.PlatformUtilities;
using MPFramework.AppCore.Manufacturing.SpecializedTypes;
namespace MPFramework.AppCore.MultiProcessing
{
#region Delegates for Testing Utilities
/// <summary>
/// This delegate is used to implement a wrapper for a caller for delegated
/// Int32 operations. It accepts a delegate that performs a binary operation
/// on Int32 variables. A source of data and a writable target to be modified
/// by the operation are also provided as arguments. The delegate is used in
/// various experiments regarding concurrent access to the writable target.
/// </summary>
/// <param name="target">
/// This is the output target of the operation. It is typically accessed concurrently
/// by multiple <see cref="System.Threading.Thread"/>s to evaluate concurrency
/// issues.
/// </param>
/// <param name="source">
/// This is the source of the operation. It is used to somehow modify the target.
/// An example would be an integer to add to the target.
/// </param>
/// <param name="oP">
/// This is the delegate that performs the integer operation. Again, a simple
/// example would be an addition operation.
/// </param>
/// <param name="lastValue">
/// This is the original value that was contained in the target if there were
/// no collisions. If there were collisions (another Thread snuck in and modified
/// the value <see paramref="target"/> during the operation) the last updated
/// value is returned.
/// </param>
/// <returns>
/// The return value indicates whether or not another Thread modified the target
/// during the operation. In this case, the method will use the most recently
/// loaded target value, which will usually be different than the target's value
/// before this method is called.
/// </returns>
/// <remarks>
/// This delegate is used to evaluate the performance of "retry loops", among
/// other concurrency management techniques. One version of a retry loop utilizes
/// the <see cref="System.Threading.Interlocked.CompareExchange(ref int, int, int)"/>
/// method. The <see paramref="target"/>, <see paramref="source"/> and <see paramref="lastValue"/>
/// parameters are employed to support concurrency experiments that utilize this
/// method.
/// </remarks>
public delegate System.Boolean Int32ConcurrentOpCaller(ref System.Int32 target,
System.Int32 source, Int32Op oP, out System.Int32 lastValue);
/// <summary>
/// This delegate is designed to perform the same function, this time on our
/// wrapped value Type, (an "RVT"), which must implement
/// <see cref="IReferencedValueType<UValueType>"/>. In this case, the
/// delegate outputs a ValueType in the "out" parameter, which is the old value
/// that was wrapped by the target. Also note that the target is no longer
/// passed with the "ref" keyword, since it is a class (passed by reference
/// already) and we perform operations on Types wrapped inside the class.
/// </summary>
public delegate System.Boolean RVTTypeConcurrentOpCaller<T,UValueType>(
T target, T source, RVTOp<T,UValueType> rVToP, out UValueType lastValue)
where T : class, IReferencedValueType<UValueType> where UValueType : struct;
/// <summary>
/// This one handles an RVT just by it's interface. It's generally preferable
/// to handle a Type through an interface when access to the full Type is
/// not required. Note that source and target arguments, being Interfaces
/// are again passed by reference, so there is no need for the "ref" keyword.
/// </summary>
public delegate System.Boolean IRVTConcurrentOpCaller<UValueType>(
IReferencedValueType<UValueType> target, IReferencedValueType<UValueType> source,
IRVTOp<UValueType> rVToP, out UValueType lastValue)
where UValueType : struct;
#endregion // Delegates for Testing Utilities
#region Static Test Data Class
/// <summary>
/// Container for common test data. These data may be specific to MSWindows.
/// The data may need to be changed for other platforms and languages. We
/// refer to this class as the "static data store".
/// </summary>
[PlatformSpecific(PlatformsSupported = CLRPlatformsSupported.MSWNT)]
public static class QATester_MultiProcessing_Testdata
{
#region Static Fields
// These are the "standard" settings. Create your own versions for custom
// tests.
/// <summary>
/// The number of Threads we allocate for our tests. This default is used
/// for extreme stress testing. 500 CLR Threads do work nicely most of the
/// time on a capable XP machine. For most tests we set it to 5 or 10 or so.....
/// </summary>
internal static readonly int s_numThreadsAllocated = 500;
/// <summary>
/// The number of Threads we allocate for our tests.
/// </summary>
internal static readonly int s_defaultNumThreadsInUse = s_numThreadsAllocated;
/// <summary>
/// The number of Threads in use for a particular test.
/// </summary>
internal static int s_numThreadsInUse = s_defaultNumThreadsInUse;
#region Int32 Stuff
///////////////////////////////////////////////////////////////////////
// This region contains static data for Int32 tests. More complex tests
// allocate data in local test classes.
///////////////////////////////////////////////////////////////////////
/// <summary>
/// A set of Threads for the tests.
/// </summary>
internal static readonly Int32ThreadData[] s_testInt32ThreadData = null;
/// <summary>
/// This variable is loaded with the Main Thread's information in a
/// given test if we are interested in examining it after the test.
/// </summary>
internal static readonly Int32ThreadData s_mainInt32ThreadData = null;
/// <summary>
/// Random number generator for Thread start/stop and various simulated
/// processing delays. This is the static one that is not Thread-dependent.
/// We spawn generators off this one for each Thread.
/// </summary>
internal static readonly IAuditingGenerator<Int32> s_Int32RngDelays;
/// <summary>
/// Random number generator for generation of random Int's. This is the static
/// one that is not Thread-dependent. We spawn generators off this one for
/// each Thread.
/// </summary>
internal static readonly IAuditingGenerator<Int32> s_Int32RngNumbers;
/// <summary>
/// A target for testing Thread collisions. This one's not readonly, since we
/// use it for I/O.
/// </summary>
public static Int32 s_multiaccessInt32Target = 0;
/// <summary>
/// The default initial value for the target.
/// </summary>
public static Int32 s_defaultMultiaccessInt32TargetInitialValue = 0;
/// <summary>
/// Reporting on/off.
/// </summary>
public static Boolean s_reportToScreen = true;
#endregion // Int32 Stuff
#endregion // Static Fields
#region Static Constructor
/// <summary>
/// Constructor sets up the default RNG's and does the allocation for
/// the "standard" Int32 Worker Thread data.
/// </summary>
static QATester_MultiProcessing_Testdata()
{
// This is the default copy of the delay generator. It's set up to
// cause some collisions in the CE-based retry loop. Seed 0, Delays
// range from 1 to 1000 - no special OP needed (null), OP state
// initialized from target, no logging (false).
s_Int32RngDelays
= new AuditingInt32RNGenerator(
0, 1, 1000, null, s_multiaccessInt32Target, false);
// This is the default Int32 data generator. Seed 1025, Numbers range
// from -100 to 100 - no special op needed, OP state initialized from
// target, no logging.
s_Int32RngNumbers
= new AuditingInt32RNGenerator(
1025, -100, 100, null, s_multiaccessInt32Target, false);
// Allocate the data for the Threads.
s_testInt32ThreadData = new Int32ThreadData[s_numThreadsAllocated];
// Set 'em up.
for(int i = 0; i < s_numThreadsAllocated; i++)
s_testInt32ThreadData[i] = new Int32ThreadData();
// Don't forget the Main Thread...
s_mainInt32ThreadData = new Int32ThreadData();
}
#endregion // Static Constructor
#region Static Class Methods
#region Utility Methods
///////////////////////////////////////////////////////////////////////
//// This region contains a few methods to manipulate the test class'
//// basic data for test setup.
///////////////////////////////////////////////////////////////////////
/// <summary>
/// This one aborts all Threads and waits until they are gone, then
/// clears out the CallerObjects by setting to <c>null</c>. Then it
/// clears out the Main Thread's ThreadData so a Main Thread is no
/// longer under test. Note that this method only works on the default
/// Threads provided within <see cref=" QATester_MultiProcessing_Testdata"/>.
/// If you build your own Threads or data locally, you have to clean them
/// up.
/// </summary>
internal static void ClearThreads()
{
// Kill all the Threads and monitor their death.
foreach(Int32ThreadData tD in s_testInt32ThreadData) {
while((tD.m_thread != null) && (tD.m_thread.IsAlive)) {
tD.m_thread.Abort();
}
}
// Unregister them.
foreach(Int32ThreadData tD in s_testInt32ThreadData) {
tD.m_callerObject = null;
tD.m_thread = null;
}
// Unregister the Main Thread.
s_mainInt32ThreadData.m_callerObject = null;
s_mainInt32ThreadData.m_thread = null;
}
/// <summary>
/// This one digs out the Thread data corresponding to the Id. Again, only
/// works on <see cref=" QATester_MultiProcessing_Testdata"/>.
/// </summary>
internal static Int32ThreadData GetThreadData(int managedThreadId)
{
foreach(Int32ThreadData tD in s_testInt32ThreadData)
if((tD.m_thread != null) && (tD.m_thread.ManagedThreadId == managedThreadId)) return tD;
// If it's not a spawned Thread, it must be the current.
return QATester_MultiProcessing_Testdata.s_mainInt32ThreadData;
}
/// <summary>
/// This method resets the test data to their initial default values.
/// </summary>
/// <remarks>
/// In any test, this method can be called to reset the static data to its
/// default values, then the data can be "recustomized" by the test designer.
/// </remarks>
internal static void ResetData()
{
// Just copy stuff over.
s_numThreadsInUse = s_defaultNumThreadsInUse;
s_multiaccessInt32Target = s_defaultMultiaccessInt32TargetInitialValue;
}
#endregion // Utility Methods
#region Static Test Methods
#region TestRunners (Process Controllers)
///////////////////////////////////////////////////////////////////////
// This region contains a number of testrunner "Shells" that are
// designed to set up and monitor the operation of multiple Threads
// which are operating concurrently. We use these TestRunners in our
// testing procedures to evaluate various concurrency issues. They also
// provide an elementary starting point for the development of more
// sophisticated controllers that might be used in practical
// applications.
///////////////////////////////////////////////////////////////////////
/// <summary>
/// This method is a testrunner shell for use in QA testing of multithreaded
/// constructs. It uses <see cref="QATester_MultiProcessing_Testdata.PIInt32CallerProcess"/>
/// as a delegate process to be passed to a Thread constructor as the
/// <see cref="ParameterizedThreadStart"/> argument. This delegate takes a
/// <see cref="System.Object"/> argument at Thread start time which is either
/// populated by the user and passed in or taken from preset static data.
/// This TestRunner attempts to give a simple demonstration of how to start
/// and monitor a number of Threads without using any Generics.
/// </summary>
/// <param name="int32CallerObject">
/// This is a CallerObject that contains data for the various Threads, including
/// the Main Thread, if <see paramref="processOnMain"/> is <c>true</c>. This
/// parameter will be accessed if the CallerObjects have not been set in the
/// ThreadData array for the spawned Threads (if any), or if the CallerObject
/// has not been set for the Main Thread (if <see paramref="processOnMain"/>
/// is <c>true</c>). If this CallerObject is accessed, it is copied to the
/// Main Thread and Worker Thread CallerObjects (as needed) and the new objects
/// have their generators initialized with a new state that is derived from the
/// Thread ID. If all Threads that are to be used in a given test have their
/// CallerObject preloaded, this argument may be passed as <c>null</c>.
/// </param>
/// <param name="processOnMain">
/// If <c>true</c>, this parameter signals that a copy of the processing
/// delegate should be fired up on the Main Thread. This copy of the
/// delegate is called after all the Worker Threads have been started. The
/// Worker Threads are joined by the Main Thread after the Main Thread
/// returns from its processing.
/// </param>
/// <remarks>
/// <para>
/// Different operations with different random characteristics can be associated
/// with different Threads (both Worker Threads and Main Thread) by presetting
/// the CallerObjects for each of these in the static data store.
/// </para>
/// <para>
/// This TestRunner utilizes a fixed CallerProcess delegate,
/// <see cref="QATester_MultiProcessing_Testdata.PIInt32CallerProcess"/>.
/// </para>
/// </remarks>
public static void Int32TestRunner(Int32ConcurrentOpCallerObject int32CallerObject,
bool processOnMain)
{
// This is the "standard" caller delegate we use in this method.
ParameterizedThreadStart Int32CallerProcessDelegate
= QATester_MultiProcessing_Testdata.PIInt32CallerProcess;
// Unregister Main Thread.
QATester_MultiProcessing_Testdata.s_mainInt32ThreadData.m_thread = null;
// If the user has decided to also process on the Main Thread, initialize it.
if(processOnMain)
QATester_MultiProcessing_Testdata.s_mainInt32ThreadData.m_thread = Thread.CurrentThread;
// In this loop, we create each Thread and spawn a new CallerObject for it
// if it has not been preloaded by the user.
Thread newThread;
for(int i = 0; i < QATester_MultiProcessing_Testdata.s_numThreadsInUse; i++) {
newThread = new Thread(new ParameterizedThreadStart(Int32CallerProcessDelegate));
newThread.Name = String.Format("Worker Thread #{0}", i + 1);
QATester_MultiProcessing_Testdata.s_testInt32ThreadData[i].m_thread = newThread;
// If any given CallerObject is not preset, spawn a new one from the default.
if(QATester_MultiProcessing_Testdata.s_testInt32ThreadData[i].m_callerObject == null) {
QATester_MultiProcessing_Testdata.s_testInt32ThreadData[i].m_callerObject
= int32CallerObject.SpawnCallerObject(newThread.ManagedThreadId);
}
}
// If Main Thread has been loaded, we must set it up.
Int32ThreadData mainThreadData = QATester_MultiProcessing_Testdata.s_mainInt32ThreadData;
if(mainThreadData.m_thread != null) {
// Ensure it has data.
if(mainThreadData.m_callerObject == null) {
mainThreadData.m_callerObject
= int32CallerObject.SpawnCallerObject(mainThreadData.m_thread.ManagedThreadId);
}
// Give it a name if it doesn't have one.
if(String.IsNullOrEmpty(mainThreadData.m_thread.Name))
mainThreadData.m_thread.Name = String.Format("Main");
}
// In this loop, we fire the Threads up.
for(int i = 0; i < QATester_MultiProcessing_Testdata.s_numThreadsInUse; i++) {
Thread thread = QATester_MultiProcessing_Testdata.s_testInt32ThreadData[i].m_thread;
Console.WriteLine("Starting Thread: \"{0}\", ManagedThreadID #: {1}",
thread.Name, thread.ManagedThreadId);
thread.Start(QATester_MultiProcessing_Testdata.s_testInt32ThreadData[i].m_callerObject);
}
// If Main Thread should work, let it.
if(mainThreadData.m_thread != null) {
Console.WriteLine("Starting Thread: \"{0}\", ManagedThreadID #: {1}",
mainThreadData.m_thread.Name, mainThreadData.m_thread.ManagedThreadId);
Int32CallerProcessDelegate(mainThreadData.m_callerObject);
}
// Wait for all the Threads to finish.
for(int i = 0; i < QATester_MultiProcessing_Testdata.s_numThreadsInUse; i++) {
QATester_MultiProcessing_Testdata.s_testInt32ThreadData[i].m_thread.Join();
}
}
/// <summary>
/// This is a test runner for a Generic RVT. This runner utilizes the caller
/// that is parameterized by a Generic class wrapping a Generic ValueType.
/// </summary>
/// <typeparam name="T">
/// This is the class that is wrapping the Generic struct that we want to
/// manipulate.
/// </typeparam>
/// <typeparam name="UValueType">
/// This is the Generic struct.
/// </typeparam>
/// <param name="rVTCallerObject">
/// A <see cref="RVTTypeConcurrentOpCallerObject<T, UValueType>"/> that
/// carries default Thread data.
/// </param>
/// <param name="threadDataArray">
/// This may be <c>null</c>. If so, it will be created and populated with
/// copies of the random # generator on the incoming <see paramref="rVTCallerObject"/>
/// which have been initialized with the Thread Id's of the Threads in use. If
/// it is not <c>null</c>, it must be of the correct dimension to handle
/// all Threads, including the Main, if it is in use. It is not necessary to
/// load any element of the array with a CallerObject. Any CallerObject not
/// present will be created internally by spawning a CallerObject as described
/// in <see cref="QATester_MultiProcessing_Testdata.Int32TestRunner"/> above.
/// The array must be dimensioned correctly if it is not <c>null</c>, however.
/// </param>
/// <param name="numSpawnedThreads">
/// This is the number of Worker Threads that are spawned by the Main Thread.
/// The total number of active Threads will be numSpawnwdThreads + 1 if
/// <see paramref="processOnMain"/> is <c>true</c>.
/// </param>
/// <param name="processOnMain">
/// If <c>true</c>, this parameter signals that a copy of the processing
/// delegate should be fired up on the Main Thread. This copy of the
/// delegate is called after all the Worker Threads have been started. The
/// Worker Threads are joined by the Main Thread after the Main Thread
/// returns from its processing.
/// </param>
/// <Exceptions>
/// <Exception>
/// An <see cref="ApplicationException"/> is generated with the message
/// "Bad size on threadDataArray" if the <see paramref="threadDataArray"/>
/// is not <c>null</c> but not not of adequate size.
/// </Exception>
/// </Exceptions>
/// <remarks>
/// <para>
/// This method generalizes things by allowing an arbitrary Generic Type (within
/// our constraints) to be operated upon and also allows Thread data to be passed
/// in as an argument for individual Threads. The number of Worker Threads that
/// are created (spawned) by the TestRunner is also passed as a parameter. These
/// data are no longer taken from the static store as in
/// <see cref="QATester_MultiProcessing_Testdata.Int32TestRunner"/>.
/// </para>
/// <para>
/// This TestRunner utilizes a fixed CallerProcess delegate,
/// <see cref="QATester_MultiProcessing_Testdata.RVTConcurrentCallerProcess<T, UValueType>"/>.
/// </para>
/// </remarks>
public static void RVTTypeTestRunner<T, UValueType>(
RVTTypeConcurrentOpCallerObject<T, UValueType> rVTCallerObject,
RVTTypeThreadData<T, UValueType>[] threadDataArray, Int32 numSpawnedThreads,
bool processOnMain)
where T : class, IReferencedValueType<UValueType>, new()
where UValueType : struct
{
// This is the "standard" caller delegate we use in this method.
ParameterizedThreadStart rVTCallerDelegate
= QATester_MultiProcessing_Testdata.RVTConcurrentCallerProcess<T, UValueType>;
// Total number of Threads, including Main.
Int32 totalNumThreads = numSpawnedThreads;
// Gotta' bump it up if Main is processing.
if(processOnMain) totalNumThreads++;
// We allow null - we just do everything internally....
if(threadDataArray == null) {
// Build a new ThreadData array.
threadDataArray = new RVTTypeThreadData<T, UValueType>[totalNumThreads];
for(Int32 i = 0; i < totalNumThreads; i++)
threadDataArray[i] = new RVTTypeThreadData<T, UValueType>();
}
else {
// Well, we might as well do some checking.
if(threadDataArray.Length < totalNumThreads)
throw new ApplicationException("Bad size on threadDataArray");
}
// If the user has decided to also process on the Main Thread, initialize it.
if(processOnMain)
threadDataArray[numSpawnedThreads].m_thread = Thread.CurrentThread;
// In this loop, we create each Thread and spawn a new CallerObject for it
// if it has not been preloaded by the user.
Thread newThread;
for(int i = 0; i < numSpawnedThreads; i++) {
newThread = new Thread(new ParameterizedThreadStart(rVTCallerDelegate));
newThread.Name = String.Format("Worker Thread #{0}", i + 1);
threadDataArray[i].m_thread = newThread;
// If any given CallerObject is not preset, spawn a new one from the default.
if(threadDataArray[i].m_callerObject == null) {
threadDataArray[i].m_callerObject
= rVTCallerObject.SpawnCallerObject(newThread.ManagedThreadId);
}
}
// If Main Thread has been loaded, we must set it up.
if(processOnMain) {
// Ensure it has data.
if(threadDataArray[numSpawnedThreads].m_callerObject == null) {
threadDataArray[numSpawnedThreads].m_callerObject
= rVTCallerObject.SpawnCallerObject(threadDataArray[numSpawnedThreads].m_thread.ManagedThreadId);
}
// Give it a name if it doesn't have one.
if(String.IsNullOrEmpty(threadDataArray[numSpawnedThreads].m_thread.Name))
threadDataArray[numSpawnedThreads].m_thread.Name = String.Format("Main");
}
// In this loop, we fire the Threads up.
for(int i = 0; i < numSpawnedThreads; i++) {
Thread thread = threadDataArray[i].m_thread;
Console.WriteLine("Starting Thread: \"{0}\", ManagedThreadID #: {1}",
thread.Name, thread.ManagedThreadId);
thread.Start(threadDataArray[i].m_callerObject);
}
// If Main Thread should work, let it.
if(processOnMain) {
Console.WriteLine("Starting Thread: \"{0}\", ManagedThreadID #: {1}",
threadDataArray[numSpawnedThreads].m_thread.Name,
threadDataArray[numSpawnedThreads].m_thread.ManagedThreadId);
rVTCallerDelegate(threadDataArray[numSpawnedThreads].m_callerObject);
}
// Wait for all the Threads to finish.
for(int i = 0; i < numSpawnedThreads; i++) {
threadDataArray[i].m_thread.Join();
}
}
/// <summary>
/// This is a test runner for a Generic IRVT. Substantially the same as
/// <see cref="RVTTypeTestRunner<T, UValueType>"/>, except we deal
/// with the IRVT's, not the full Generic classes. We pass the CallerProcess
/// method to this method through it's delegate. We also add one more
/// feature. We read the <c>NumCallsOnThread</c> property on the Main Thread's
/// data, if <see paramref="processOnMain"/> is <c>true</c>. If this is set
/// to -1, we demonstrate how to gracefully terminate the Worker Thread's
/// processing by changing a data item that the Worker Threads have access to.
/// </summary>
/// <typeparam name="UValueType">
/// This is the Generic struct.
/// </typeparam>
/// <param name="iRVTCallerProcessDelegate">
/// This is the delegate that performs processing on Threads. This single
/// delegate is common across all Threads.
/// </param>
/// <param name="iRVTCallerObject">
/// A <see cref="IRVTConcurrentOpCallerObject<T>"/> that carries
/// default Thread data.
/// </param>
/// <param name="threadDataArray">
/// This may be <c>null</c>. If so, it will be created and populated with
/// copies of the random # generator on the incoming <see paramref="iRVTCallerObject"/>
/// which have been initialized with the Thread Id's of the Threads in use. If
/// it is not <c>null</c>, it must be of the correct dimension to handle
/// all Threads, including the Main, if it is in use. It is not necessary to
/// load any elment of the array with a CallerObject. Any CallerObject not
/// present will be created internally by spawning a CallerObject as described
/// in <see cref="QATester_MultiProcessing_Testdata.Int32TestRunner"/> above.
/// The array must be dimensioned correctly if it is not <c>null</c>, however.
/// </param>
/// <param name="numSpawnedThreads">
/// This is the number of Worker Threads that are spawned by the Main Thread.
/// The total number of active Threads will be numSpawnwdThreads + 1 if
/// <see paramref="processOnMain"/> is <c>true</c>.
/// </param>
/// <param name="processOnMain">
/// If <c>true</c>, this parameter signals that a copy of the processing
/// delegate should be fired up on the Main Thread. This copy of the
/// delegate is called after all the Worker Threads have been started. The
/// Worker Threads are joined by the Main Thread after the Main Thread
/// returns from its processing.
/// </param>
/// <Exceptions>
/// <Exception>
/// An <see cref="ApplicationException"/> is generated with the message
/// "Bad size on threadDataArray" if the <see paramref="threadDataArray"/>
/// is not <c>null</c> but not not of adequate size.
/// </Exception>
/// </Exceptions>
/// <remarks>
/// <para>
/// <see paramref="processOnMain"/> must be enabled (<c>true</c>) for the
/// Main Thread to employ the internal StatusRegister and ControlRegister
/// to demonstrate the control of Worker Threads.
/// </para>
/// <para>
/// Note that, although we do not attempt it within these classes, it is
/// perfectly feasible to pass an array of <see cref="ParameterizedThreadStart"/>
/// objects to allow different Threads to be running different processes.
/// </para>
/// </remarks>
public static void IRVTTestRunner<UValueType>(
ParameterizedThreadStart iRVTCallerProcessDelegate,
IRVTConcurrentOpCallerObject<UValueType> iRVTCallerObject,
IRVTThreadData<UValueType>[] threadDataArray, Int32 numSpawnedThreads,
bool processOnMain)
where UValueType : struct
{
// Total number of Threads, including Main.
Int32 totalNumThreads = numSpawnedThreads;
// Gotta' bump it up if Main is processing.
if(processOnMain) totalNumThreads++;
// We allow null - we just do everything internally....
if(threadDataArray == null) {
// Build a new ThreadData array.
threadDataArray = new IRVTThreadData<UValueType>[totalNumThreads];
for(Int32 i = 0; i < totalNumThreads; i++)
threadDataArray[i] = new IRVTThreadData<UValueType>();
}
else {
// Well, we might as well do some checking.
if(threadDataArray.Length < totalNumThreads)
throw new ApplicationException("Bad size on threadDataArray");
}
// If the user has decided to also process on the Main Thread, initialize
// it.
if(processOnMain)
threadDataArray[numSpawnedThreads].m_thread = Thread.CurrentThread;
// In this loop, we create each Thread and spawn a new CallerObject for it
// if it has not been preloaded by the user.
Thread newThread;
for(int i = 0; i < numSpawnedThreads; i++) {
newThread = new Thread(new ParameterizedThreadStart(iRVTCallerProcessDelegate));
newThread.Name = String.Format("Worker Thread #{0}", i + 1);
threadDataArray[i].m_thread = newThread;
// If a particular CallerObject is not preset, spawn a new one from the default.
if(threadDataArray[i].m_callerObject == null) {
threadDataArray[i].m_callerObject
= iRVTCallerObject.SpawnCallerObject(newThread.ManagedThreadId);
}
}
// If Main Thread has been loaded, we must set it up.
if(processOnMain) {
// Ensure it has data.
if(threadDataArray[numSpawnedThreads].m_callerObject == null) {
threadDataArray[numSpawnedThreads].m_callerObject
= iRVTCallerObject.SpawnCallerObject(threadDataArray[numSpawnedThreads].m_thread.ManagedThreadId);
}
// Give it a name if it doesn't have one.
if(String.IsNullOrEmpty(threadDataArray[numSpawnedThreads].m_thread.Name))
threadDataArray[numSpawnedThreads].m_thread.Name = String.Format("Main");
}
// In this loop, we fire the Threads up.
for(int i = 0; i < numSpawnedThreads; i++) {
Thread thread = threadDataArray[i].m_thread;
Console.WriteLine("Starting Thread: \"{0}\", ManagedThreadID #: {1}",
thread.Name, thread.ManagedThreadId);
thread.Start(threadDataArray[i].m_callerObject);
}
// If Main Thread should work, let it.
if(processOnMain) {
Console.WriteLine("Starting Thread: \"{0}\", ManagedThreadID #: {1}",
threadDataArray[numSpawnedThreads].m_thread.Name,
threadDataArray[numSpawnedThreads].m_thread.ManagedThreadId);
iRVTCallerProcessDelegate(threadDataArray[numSpawnedThreads].m_callerObject);
// The Main Thread uses the "ControlRegister" on it's Thread object to
// indicate that Worker Threads should somehow be controlled. In this
// example test runner, we just set it to a positive. IRVTConcurrentCaller
// is set up to interpret this as a stop command.
//
// We, ourselves, use a -1 to indicate that we are in "control" mode,
// otherwise our own Thread process would get shut down early.
if(threadDataArray[numSpawnedThreads].m_callerObject.m_controlRegister == -1){
// We put in a completely arbitrary test here that waits until
// every Thread has done at least 3 iterations before shutting
// them all down. This is a simple example of how Worker Threads
// can communicate their status back to a controller through a
// data item. This is an inefficient waiting loop (no timers,
// no waits, etc.) that just monitors the StatusRegisters associated
// with each Worker Thread.
bool stopThreads = false;
while(!stopThreads) {
// Assume we will stop.
stopThreads = true;
for(int i = 0; i < numSpawnedThreads; i++) {
if(threadDataArray[i].m_callerObject.m_statusRegister < 3)
// If somebody isn't finished, we can't stop yet.
stopThreads = false;
}
}
// Tell all the Threads to stop at the end of the current
// loop iteration.
for(int i = 0; i < numSpawnedThreads; i++) {
threadDataArray[i].m_callerObject.m_controlRegister = 1;
Thread thread = threadDataArray[i].m_thread;
Console.WriteLine("Stopping Thread: \"{0}\" , ManagedThreadID #: {1}",
thread.Name, thread.ManagedThreadId);
}
}
}
// Wait for all the Threads to finish.
for(int i = 0; i < numSpawnedThreads; i++) {
threadDataArray[i].m_thread.Join();
}
}
#endregion //TestRunners (Process Controllers)
#region Worker Thread Processes
///////////////////////////////////////////////////////////////////////
// This section contain testing processes that call operations on Threads
// with random delays and random data to simulate actual operation of
// Threads accessing data concurrently. These processes are designed to
// be used as ParameterizedThreadStart delegates that are available
// under .Net 2.0. The processes accept an Object that contains per-Thread
// data to be passed to Threads.
///////////////////////////////////////////////////////////////////////
/// <summary>
/// This is a procedure that runs on each Thread and accesses shared
/// data. It is specialized to use only Int32 data - it is designed to
/// call a CallerObject's Int32 generator and use it to provide a source
/// of Int32 numbers for testing multithreaded access to an Int32 variable.
/// It is designed to be passed to the parameterized <see cref="ThreadStart"/>
/// constructor.
/// </summary>
/// <param name="threadObject">
/// This object must be a <see cref="Int32ConcurrentOpCallerObject"/>.
/// </param>
public static void PIInt32CallerProcess(System.Object threadObject)
{
// A bool needed to report a collision.
bool collision = false;
// To catch the last value.
Int32 lastValue;
Int32ConcurrentOpCallerObject callerObject
= (Int32ConcurrentOpCallerObject)threadObject;
// Two for handling our currently executing Thread.
Int32 iD = Thread.CurrentThread.ManagedThreadId;
string threadName = Thread.CurrentThread.Name;
// Fetch the generator from the Thread data. AuditingInt32RNGenerator
// is handled on the CallerObject as an IAuditingGenererator<Int32>.
// We want to handle it here as a AuditingInt32RNGenerator to demonstrate
// how the same generator can be handled as a concrete closure defined
// with a derived Type. This technique would be useful if we wanted to
// ensure that a specialized generator, optimized for Int32's is used
// in a critical application. The cast will fail at run-time, of course,
// if the generator instatntiated on the CallerObject was not an
// AuditingInt32RNGenerator. However, we happen to know that it is
// constructed in the static data store as "new AuditingInt32RNGenerator(....)".
// Note that it would have been possible to define the generator on
// CallerObject as AuditingInt32RNGenerator if we wanted to ensure
// compile-time Type safety in a specialized application using only
// Int32's.
AuditingInt32RNGenerator generator
= (AuditingInt32RNGenerator) GetThreadData(iD).m_callerObject.NumberGenerator;
// Perform the number of calls requested, using the generator.
for(int i = 0; i < callerObject.NumCallsOnThread; i++) {
// Issue the call.
collision
= callerObject.Caller(ref QATester_MultiProcessing_Testdata.s_multiaccessInt32Target,
generator.Next(), callerObject.Op, out lastValue);
// We just indicate a collision on the console.
if(collision)
Console.WriteLine("Collision with Thread: \"{0}\"", threadName);
}
}
/// <summary>
/// This is a Generic method that runs on each Thread and accesses shared
/// data. It is designed to call a CallerObject's Int32 generator and use it to
/// provide a source of Type T numbers for testing multithreaded access to
/// a Type T variable. It is designed to be passed to the parameterized
/// <see cref="ThreadStart"/> constructor.
/// </summary>
/// <param name="threadObject">
/// This object must be a <see cref="RVTTypeConcurrentOpCallerObject<T, UValueType>"/>.
/// </param>
/// <remarks>
/// <para>
/// This process delegate allows us to perform concurrency tests on any
/// object implementing <see cref="IReferencedValueType<UValueType>"/>.
/// This may be a simple default <see cref="ReferencedValueType<UValueType>"/>,
/// or a complicated Type implementing the interface, perhaps containing a
/// simpler <see cref="IReferencedValueType<UValueType>"/> to implement
/// a lock object.
/// </para>
/// <para>
/// This method imposed an additional constraint on <see typeparamref="T"/>,
/// namely the "new()" constraint. Because we must construct a local working
/// copy of <see typeparamref="T"/>, we must know at language compile time
/// that it is possible to instantiate a default copy. The "new()" constraint
/// requires that <see typeparamref="T"/> have a "default" or parameterless
/// constructor.
/// </para>
/// </remarks>
public static void RVTConcurrentCallerProcess<T, UValueType>(System.Object threadObject)
where T : class, IReferencedValueType<UValueType>, new()
where UValueType : struct
{
// A bool needed to report a collision.
bool collision = false;
// This cast will generate an exception if we don't have the correct
// type of Thread object coming in.
RVTTypeConcurrentOpCallerObject<T, UValueType> callerObject
= (RVTTypeConcurrentOpCallerObject<T, UValueType>)threadObject;
// For reporting on our currently executing Thread.
string threadName = Thread.CurrentThread.Name;
// Fetch the generator from the Thread data.
IAuditingGenerator<Int32> generator
= callerObject.NumberGenerator;
// We need to create a copy of the Type from its default constructor.
// This will fail at runtime if the Type T does not have a default
// constructor. Thus the new() constraint in the method definition.
// Here, we really just wanted to demonstrate to folks how to use
// Activator for a Generic. Note that at runtime the Activator will
// attempt to create a default version of whatever the loader/JIT
// resolves for T at loadtime.
T randomRVT = Activator.CreateInstance<T>();
// This works, too, because the method has the new() constraint.
// The difference is that Activator does not need the new() constraint.
// It would fail at runtime, however, when the reflection subsystem
// tried to locate a parameterless constructor, if T did not implement
// one. Note that
// "MPFramework.AppCore.Manufacturing.SpecializedTypes.ReferencedValueType<T>"
// implements a default public parameterized constructor. If the method
// did not specify the "new()" constraint, the following line would receive
// an error at language compile time.
randomRVT = new T(); // Wouldn't compile without default constructor on T.
// Need a UValueType to receive a converted copy of the rn. Note that
// UValueType is constrained to be a struct, so we get a copy of UValueType's
// databytes in a HOME in local storage. Note that we could not have
// created T this way. T, being constrained to be a C# class
// (a non-ValueType .Net Class) would result in a null reference being
// returned from default(T).
UValueType uValueType = default(UValueType);
// One for the "out" variable, too.
UValueType outUValueType = default(UValueType);
// Perform the number of calls requested, using the generator.
for(int i = 0; i < callerObject.NumCallsOnThread; i++) {
// Generate the rn and leave it in the generator.
generator.Next();
// Convert it using our catch-all ConvertNumber method.
// Note this line will not compile, since we have defined the
// generator as an IAuditingGenerator<Int32>.
// uValueType = generator.Next(); !!!!!! This won't compile.
//
// Let the generator convert the rn. It figures out whether it
// can convert to us or not. Generator will throw the exception
// - this cast will always succeed if the generator returns something.
//
// Of course, if this were a real program, we'd catch or document
// the exception..........
uValueType = (UValueType)generator.ConvertNumber(typeof(UValueType));
// Now wrap it in the RVT.
randomRVT.Value = uValueType;
// Issue the call.
collision = callerObject.Caller(callerObject.m_target,
randomRVT, callerObject.Op, out outUValueType);
// Just indicate a collision on the console.
if(collision)
Console.WriteLine("Collision with Thread: \"{0}\"", threadName);
}
}
/// <summary>
/// This is the Thread process for IRVT's. It is almost the same as the
/// previous one, except it handles an RVT through its interface, which
/// gives us more flexibility in adding IRVT functionality to a general
/// object. It also demonstrates the ability to control a "well-behaved"
/// Thread by setting a data item that the executing Thread can read.
/// </summary>
/// <param name="threadObject">
/// This object must be a <see cref="IRVTConcurrentOpCallerObject<UValueType>"/>.
/// </param>
/// <remarks>
/// This process delegate allows us to perform concurrency tests on any
/// object implementing <see cref="IReferencedValueType<UValueType>"/>.
/// This may be a simple default <see cref="ReferencedValueType<UValueType>"/>,
/// or a complicated Type implementing the interface, perhaps containing a
/// simpler <see cref="IReferencedValueType<UValueType>"/> to implement
/// a lock object.
/// </remarks>
public static void IRVTConcurrentCallerProcess<UValueType>(System.Object threadObject)
where UValueType : struct
{
// A bool needed to report a collision.
bool collision = false;
// This cast will generate an exception if we don't have the correct
// type of Thread object coming in.
IRVTConcurrentOpCallerObject<UValueType> callerObject
= (IRVTConcurrentOpCallerObject<UValueType>)threadObject;
// For reporting on our currently executing Thread.
string threadName = Thread.CurrentThread.Name;
// Fetch the generator from the Thread data. In this case, the generator
// is instantiated as an Int32 generator. We do this to show the possibility
// of using the "back door" in our generator, the ConvertNumber function,
// to employ an Int32 generator to produce an arbitrary Type at run
// time (anything we've designed it internally to convert, anyway).
IAuditingGenerator<UValueType> generator
= callerObject.NumberGenerator;
// We need something wearing IReferencedValueType<UValueType> to wrap
// rn's. - a default ReferencedValueType<UValueType> is fine. Note
// that this does not require any "new()" constraint on the method,
// since we are constructing a specific closed Type (closed on T) that
// is known at language compile time.
IReferencedValueType<UValueType> randomRVT
= new ReferencedValueType<UValueType>();
// Need a UValueType to receive a converted copy of the rn.
UValueType uValueType = default(UValueType);
// One for the "out" variable, too.
UValueType outUValueType = default(UValueType);
// Perform the number of calls requested, using the generator.
Int32 iterationNumber = -1;
while(++iterationNumber < callerObject.NumCallsOnThread) {
// Generate the rn. Note we can capture the output value directly,
// since the generator is defined as an IAuditingGenerator<UValueType>.
uValueType = generator.Next();
// Now wrap it in the RVT.
randomRVT.Value = uValueType;
// The nice thing is that we can always call ConvertNumber to produce
// an arbitrary Type, no matter what IAuditingGenerator<U> uses for "U".
// Let's try a single-precision float, shall we?
System.Single testSingle
= (System.Single)generator.ConvertNumber(typeof(System.Single));
// Issue the call.
collision = callerObject.Caller(callerObject.m_target,
randomRVT, callerObject.Op, out outUValueType);
// Indicate a collision on the console.
if(collision)
Console.WriteLine("Collision with Thread: \"{0}\"", threadName);
// We report our status to the TestRunner as the iteration number.
callerObject.m_statusRegister = iterationNumber;
}
// We give a printout of our final iteration count.
Console.WriteLine("Thread: \"{0}\" Stopping after iteration#: " + iterationNumber.ToString(),
threadName);
}
/// <summary>
/// This is a CallerProcess for IRVT's. It is almost the same as the
/// previous one, except it has delays in the processing loop to handle
/// Thread-specific delays for our "WorkerProcessControl" demo. This method
/// also allows the TestRunner to issue control commands through the CallerObject.
/// This is the last one - we promise!!
/// </summary>
/// <param name="threadObject">
/// This object must be a <see cref="IRVTConcurrentOpCallerObject<UValueType>"/>.
/// </param>
/// <remarks>
/// We use delays outside of the Op delegate, up here in the CallerProcess.
/// Hopefully, we've convinced ourselves by now that "lock" really locks and
/// we don't need the delays to create collisions anymore. Up here in the
/// CallerProcess, we use it to simulate Thread processing delays to demonstrate
/// Thread loading and control issues.
/// </remarks>
public static void IRVTDelayedConcurrentCallerProcess<UValueType>(System.Object threadObject)
where UValueType : struct
{
// A bool needed to report a collision.
bool collision = false;
// This cast will generate an exception if we don't have the correct
// type of CallerObject coming in.
IRVTConcurrentOpCallerObject<UValueType> callerObject
= (IRVTConcurrentOpCallerObject<UValueType>)threadObject;
// For reporting on our currently executing Thread.
string threadName = Thread.CurrentThread.Name;
// Fetch the generators from the Thread data.
IAuditingGenerator<UValueType> numberGenerator
= callerObject.NumberGenerator;
// We know the delay generator always has to be Int32.
IAuditingGenerator<Int32> delayGenerator
= callerObject.DelayGenerator;
// We need something wearing IReferencedValueType<UValueType> to wrap
// rn's. A default ReferencedValueType<UValueType> is fine.
IReferencedValueType<UValueType> randomRVT
= new ReferencedValueType<UValueType>();
// Need a UValueType to receive a converted copy of the rn.
UValueType uValueType = default(UValueType);
// One for the "out" variable, too.
UValueType outUValueType = default(UValueType);
// Perform the number of calls requested, using the generator.
Int32 iterationNumber = -1;
// In this case, we abstract the loop control - just a little bit.
// Note, though, we could have put the composite loop test entirely
// into ThreadStopNow.
while((!(callerObject.ThreadStopNow))
&& (++iterationNumber < callerObject.NumCallsOnThread)) {
// Generate the delay and use it in Delay().
Delay(delayGenerator.Next());
// Generate the random data.
uValueType = numberGenerator.Next();
// Now wrap it in the RVT.
randomRVT.Value = uValueType;
// Issue the call.
collision = callerObject.Caller(callerObject.m_target,
randomRVT, callerObject.Op, out outUValueType);
// Indicate a collision on the console.
if(collision)
Console.WriteLine("Collision with Thread: \"{0}\"", threadName);
// We report our status as the iteration number.
callerObject.m_statusRegister = iterationNumber;
}
// We give a printout of our final iteration count.
Console.WriteLine("Thread: \"{0}\" Stopping after iteration#: " + iterationNumber.ToString(),
threadName);
// Tells whether we stopped early.
if(callerObject.ThreadStopNow)
Console.WriteLine("STOP command received by Thread: \"{0}\" ",
threadName);
}
#endregion // Worker Thread Processes
#region Callers for Atomic Delegates
/// <summary>
/// Caller for an Int32 that uses a "retry loop" for synchonized concurrent
/// access to a shared data item.
/// </summary>
/// <param name="target">
/// This is the <see cref="System.Int32"/> target that is designed to be
/// modified by multiple Threads or processes. It is a ValueType and thus,
/// must be passed by reference.
/// </param>
/// <param name="source">
/// This is the source value that will be used to somehow modify the target
/// by the OP delegate.
/// </param>
/// <param name="oP">
/// This is a delegate for the binary operation to be performed on the
/// source and destination.
/// </param>
/// <param name="lastValue">
/// A copy of the <see cref="System.Int32"/> value written to the target.
/// </param>
/// <returns>
/// <c>true</c> if a Thread collision has been detected.
/// </returns>
public static System.Boolean RetryLOOPPseudoAtomicInt32OpCaller(ref System.Int32 target,
System.Int32 source, Int32Op oP, out System.Int32 lastValue)
{
// This variable stores a local copy of the original value of the target
// that is read before the OP and the atomic exchange. It is used to
// compare with the most recently fetched value from the atomic exchange
// to see if it has been modified by another Thread.
System.Int32 workingValue = target;
// This variable stores a local copy of the most recent value of the
// target as returned from the atomic exchange operation.
System.Int32 lastUpdatedValue = target;
// This variable will be set to <c>true</c> if we undergo a collision.
System.Boolean hadCollision = false;
// Result for our OP;
Int32 opResult;
// Repeat until we get no more collisions.
do {
// Set the working value to the last update.
workingValue = lastUpdatedValue;
// Perform the OP on the local opResult variable.
opResult = workingValue;
oP(ref opResult, source);
// Make the switch only if the target has not changed from our
// last workingValue. lastUpdatedValue always receives the current
// value of target as returned by CompareExchange.
lastUpdatedValue
= Interlocked.CompareExchange(ref target, opResult, workingValue);
// Report progress if we want....
if(QATester_MultiProcessing_Testdata.s_reportToScreen)
Console.WriteLine(" >Processed a Number on ManagedThreadID #: "
+ Thread.CurrentThread.ManagedThreadId.ToString());
lastValue = lastUpdatedValue;
// If the two values are different, this means that we had a collision.
if(lastUpdatedValue != workingValue) hadCollision = true;
// Keep LOOPing and RETRYing if we have had a collision.
} while(lastUpdatedValue != workingValue);
return hadCollision;
}
/// <summary>
/// Generic caller useful for an open Generic RVT. This is a caller that
/// accepts a specific Type, not just its <see cref="IReferencedValueType<UValueType>"/>
/// interface.
/// <typeparam name="T">
/// This is the actual Type implementing IRVT.
/// </typeparam>
/// <typeparam name="UValueType">
/// This is the contained Type that the <see typeparamref="T"/> manipulates
/// and provides access to through the IRVT interface.
/// </typeparam>
/// <param name="target">
/// The target <see typeparamref="T"/>-valued Type that is to be modified.
/// This is the first argument to the <see paramref="oP"/> delegate.
/// </param>
/// <param name="source">
/// The source <see typeparamref="T"/>-valued Type that is to be used to modify
/// the target. This is the first argument to the <see paramref="oP"/> delegate.
/// </param>
/// <param name="oP">
/// The <see typeparamref="T"/>-valued OP that is to be performed on the
/// source and target.
/// </param>
/// <param name="lastValue">
/// A copy of the <see typeparamref="UValueType"/> value written inside the
/// target <see typeparamref="T"/>.
/// </param>
/// <returns>
/// <c>true</c> if a Thread collision has been detected.
/// </returns>
/// </summary>
/// <remarks>
/// Note that target does not need to be passed by reference, since we
/// are modifying the internal struct of the target class, not the
/// class instance reference itself.
/// </remarks>
public static System.Boolean RVTTypeConcurrentOpCaller<T, UValueType>(T target,
T source, RVTOp<T, UValueType> oP, out UValueType lastValue)
where T : class, IReferencedValueType<UValueType>
where UValueType : struct
{
// These variables are working storage for our internal Generic
// UValueType.
UValueType oldValue;
UValueType opResult;
bool hadCollision = false;
// Lock the RVT and perform the OP.
lock(target) {
// Save the last value.
oldValue = target.Value;
// Do the OP.
opResult = oP(target, source);
lastValue = opResult;
}
// Report progress if we want....
if(QATester_MultiProcessing_Testdata.s_reportToScreen)
Console.WriteLine(" >Processed a Number on ManagedThreadID #: "
+ Thread.CurrentThread.ManagedThreadId.ToString());
// If the two values are different, this means that we had a collision.
// Note that you should define a Type-specific "Equals" on your own
// closed Types. CLR must do it through reflection on arbitrary structs,
// but you don't if you have a specific closure!! This will also generate
// a BOXing operation. Sometimes better to provide Equals(UValueType,UValueType),
// since you then don't have to be worried about overloading HashCode(), too.
//
// This obviously should never be true, since we have the target locked.
if(!Equals(oldValue, opResult)) hadCollision = true;
return hadCollision;
}
/// <summary>
/// Generic caller useful for a Generic IRVT. See the RVT caller for details.
/// This one handles RVTs only by their interface.
/// </summary>
/// <typeparam name="UValueType">
/// This is the contained Type that the IRVT manipulates.
/// </typeparam>
/// <param name="target">
/// Target of the OP delegate. See the RVT caller for details. Same, except
/// now we work on the internal <see typeparamref="UValueType"/> through
/// its Interface. Note that this method argument can still be passed by
/// value, (not with an "out" keyword), since an Interface is a ReferenceType.
/// </param>
/// <param name="source">
/// See the RVT caller for details. Same, except now we work on the internal
/// <see typeparamref="UValueType"/>.
/// </param>
/// <param name="oP">
/// The OP that operates on the raw <see paramref="UValueType"/>s.
/// </param>
/// <param name="lastValue">
/// Copy of the last <see paramref="UValueType"/> output on the target.
/// </param>
/// <returns>
/// <c>true</c> if a Thread collision has been detected.
/// </returns>
public static System.Boolean IRVTConcurrentOpCaller<UValueType>(
IReferencedValueType<UValueType> target, IReferencedValueType<UValueType> source,
IRVTOp<UValueType> oP, out UValueType lastValue)
where UValueType : struct
{
// These variables are working storage for our internal Generic
// UValueType.
UValueType oldValue;
UValueType opResult;
// This one has the usual purpose - it should never be true in this
// method, however....
bool hadCollision = false;
// Lock the IRVT's underlying System.Object and perform the OP.
lock(target) {
// Save the last value.
oldValue = target.Value;
// Do the OP.
opResult = oP(target, source);
lastValue = opResult;
}
// Report progress if we want....
if(QATester_MultiProcessing_Testdata.s_reportToScreen)
Console.WriteLine(" >Processed a Number on ManagedThreadID #: "
+ Thread.CurrentThread.ManagedThreadId.ToString());
// If the two values are different, this means that we had a
// collision. Note that you should define a Type-specific "Equals"
// on your own UValueType since the CLR does it through reflection
// on arbitrary structs (slow), but you don't have to if you have
// a specific concrete closure!! Note also, that we will be BOXing
// again if we use something expecting a System.Object, so you
// probably would want to define an UValueType.Equals(UValueType)
// ala an implicit interface implementation of IEquatable<UValueType>
// if you needed an equality comparison.
//
// This obviously should never be true, since we have the target locked.
if(!Equals(oldValue, opResult)) hadCollision = true;
return hadCollision;
}
/// <summary>
/// This caller tries to demonstrate what's wrong with thinking that an OP wrapping
/// an atomic OP constitutes an atomic OP. This one does the locking only after the
/// read and subsequent binary operation. It is designed to simulate the old Int32
/// situation with RVTs and a "lock" statement as opposed to a CompareExchange.
/// </summary>
public static System.Boolean IRVTPseudoAtomicOpCaller<UValueType>(
IReferencedValueType<UValueType> target, IReferencedValueType<UValueType> source,
IRVTOp<UValueType> oP, out UValueType lastValue)
where UValueType : struct
{
// This variable stores a local copy of the original value of the target
// that is read before the OP and the atomic exchange. It is used to
// compare with the most recently fetched value from the atomic exchange
// to see if it has been modified by another Thread.
UValueType workingValue = target.Value;
// This variable stores a local copy of the most recent value of the
// target as returned from the atomic exchange operation.
UValueType lastUpdatedValue = workingValue;
// This variable will be set to <c>true</c> if we undergo a collision.
System.Boolean hadCollision = false;
// Result for our OP - an RVT<UValueType> is fine to receive it.
ReferencedValueType<UValueType> opResult = new ReferencedValueType<UValueType>();
// Repeat until we get no more collisions.
do {
// Set the working value to the last update.
workingValue = lastUpdatedValue;
// Perform the OP on the local opResult variable.
opResult.Value = workingValue;
oP(opResult, source);
// Make the switch only if the target has not changed from our
// last workingValue. lastUpdatedValue always receives the current
// value of target.
lock(target) {
//// Next two instructions simulate the function of CompareExchange.
// Since the target is locked, this is an atomic operation.
lastUpdatedValue = target.Value;
// We assume in this test caller that any UValueType we would
// supply overrides Equals. This is true of any CLR primitive
// Types like Boolean, Int32, etc.. This tests to see if another
// Thread has replaced the target with a different value before
// we locked it.
if(lastUpdatedValue.Equals(workingValue))
target.Value = source.Value;
}
// Report progress if we want....
if(QATester_MultiProcessing_Testdata.s_reportToScreen)
Console.WriteLine(" >Processed a Number on ManagedThreadID #: "
+ Thread.CurrentThread.ManagedThreadId.ToString());
lastValue = lastUpdatedValue;
// If the two values are different, this means that we had a collision.
if(!Equals(lastUpdatedValue, workingValue)) hadCollision = true;
// Keep going if we have had a collision.
} while(!Equals(lastUpdatedValue, workingValue));
return hadCollision;
}
/// <summary>
/// Generic caller for calling <see cref="Interlocked.CompareExchange<T>"/>
/// internally to update the <see paramref="target"/> after the Generic operation
/// has been completed. The Generic CompareExchange method has a class constraint
/// so we must use it here.
/// See <see cref="RVTConcurrentCallerProcess<T, UValueType>"/> for params.
/// </summary>
/// <remarks>
/// Unfortunatly, <see cref="System.Threading.Interlocked.CompareExchange<T>"/>
/// uses ReferenceEquals, just like the object version. Thus, this caller is not useful,
/// even for demonstration purposes.
/// </remarks>
internal static System.Boolean RetryLOOPPseudoAtomicOpCaller<T, UValueType>(ref T target,
T source, RVTOp<T, UValueType> oP, out UValueType lastValue)
where T : class, IReferencedValueType<UValueType>
where UValueType : struct
{
// This variable stores a local copy of the original value of the target.
// It is also used to compare with the most recently fetched value to
// see if it has been modified by another Thread.
T workingValue = target;
// This variable stores a local copy of the most recent value of the target.
T lastUpdatedValue = target;
// This variable will be set to <c>true</c> if we encounter a collision.
System.Boolean hadCollision = false;
// Result for our OP;
T opResult;
// Repeat until we get no more collisions.
do {
// Set the working value to the last update.
workingValue = lastUpdatedValue;
// Make the switch only if the target has not changed from our
// last working value. lastUpdatedValue always receives the current
// value of target.
opResult = workingValue;
oP(opResult, source);
lastUpdatedValue
= Interlocked.CompareExchange(ref target, opResult, workingValue);
if(QATester_MultiProcessing_Testdata.s_reportToScreen)
Console.WriteLine(" >Processed a Number on ManagedThreadID #: "
+ Thread.CurrentThread.ManagedThreadId.ToString());
lastValue = lastUpdatedValue.Value;
// If the two values are different, this means that we had a collision.
if(!lastUpdatedValue.IsValueSameAs(workingValue)) hadCollision = true;
// Keep going if we have had a collision.
} while(!lastUpdatedValue.IsValueSameAs(workingValue));
return hadCollision;
}
#endregion // Callers for Atomic Delegates.
#region Test Operations Implementing OP Delegates
#region Int32 Ops
/// <summary>
/// The default operation that just writes the source to the target.
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be written to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static Int32 Int32ReplaceOp(ref Int32 target, Int32 source)
{
Int32 oldTarget = target;
// Just move it over.
target = source;
return oldTarget;
}
/// <summary>
/// The default operation that just writes the source to the target after
/// a delay that is derived from the static delay generator. The use of this
/// OP assumes that we don't care about a fixed sequence of delays being
/// associated with any particular Thread if the OP is being called on
/// multiple Threads. If we care, then a separate generator must be associated
/// with each OP delegate. We don't really need this in most of our tests.
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be written to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static Int32 Int32DelayedReplaceOp(ref Int32 target, Int32 source)
{
Int32 oldTarget = target;
Int32 delay = s_Int32RngDelays.Next();
if(delay > 0) Delay(delay);
// Just move it over.
target = source;
return oldTarget;
}
/// <summary>
/// This operation writes the sum of source and target to the target.
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be added to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static Int32 Int32AddOp(ref Int32 target, Int32 source)
{
Int32 oldTarget = target;
// Just move it over.
target += source;
return oldTarget;
}
/// <summary>
/// This operation writes the sum of source and target to the target
/// with a delay. Same deal as DelayedReplace.......
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be added to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static Int32 Int32DelayedAddOp(ref Int32 target, Int32 source)
{
Int32 oldTarget = target;
Int32 delay = s_Int32RngDelays.Next();
if(delay > 0) Delay(delay);
// Just move it over.
target += source;
return oldTarget;
}
/// <summary>
/// This operation raises the target to the exponent represented
/// by <see paramref="source"/>.
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number of times that the target is to be multiplied by itself.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static Int32 Int32ExpOp(ref Int32 target, Int32 source)
{
Int32 oldTarget = target;
Int32 workingVar = target;
for(int i = 0; i < source; i++)
workingVar *= oldTarget;
// Just move it over.
target = workingVar;
return oldTarget;
}
#endregion // Int32 Ops
#region Open RVTOps
/// <summary>
/// This operation just writes the source to the target after
/// a delay that is derived from the static delay generator. The use of this
/// Op assumes that we don't care about a fixed sequence of delays being
/// associated with any particular Thread if the Op is being called on
/// multiple Threads. If we care, then a separate generator must be associated
/// with each Op delegate. We don't really need this in most of our tests.
/// This is really the only binary OP that can be sensibly defined for a
/// Generic <see cref="ReferencedValueType<UValueType>"/>.
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be written to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static UValueType RVTDelayedReplaceOp<T, UValueType>(T target, T source)
where T : class, IReferencedValueType<UValueType>
where UValueType : struct
{
UValueType oldTarget = target.Value;
Int32 delay = s_Int32RngDelays.Next();
if(delay > 0) Delay(delay);
// Just move it over.
target.SwapValues(source);
return oldTarget;
}
/// <summary>
/// This operation just writes the source to the target after
/// a delay that is derived from the static delay generator. The use of this
/// OP assumes that we don't care about a fixed sequence of delays being
/// associated with any particular Thread if the OP is being called on
/// multiple Threads. If we care, then a separate generator must be associated
/// with each OP delegate. We don't really need this in most of our tests.
/// This is really the only binary OP that can be sensibly defined for a
/// Generic <see cref="IReferencedValueType<UValueType>"/>.
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be written to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static UValueType IRVTDelayedReplaceOp<UValueType>(
IReferencedValueType<UValueType> target, IReferencedValueType<UValueType> source)
where UValueType : struct
{
UValueType oldTarget = target.Value;
Int32 delay = s_Int32RngDelays.Next();
if(delay > 0) Delay(delay);
// Just move it over.
target.Value = source.Value;
return oldTarget;
}
/// <summary>
/// Same thing, but no delay.......
/// </summary>
/// <param name="target">
/// The number to be overwritten.
/// </param>
/// <param name="source">
/// The number to be written to the target.
/// </param>
/// <returns>
/// Original value of the target.
/// </returns>
public static UValueType IRVTReplaceOp<UValueType>(
IReferencedValueType<UValueType> target, IReferencedValueType<UValueType> source)
where UValueType : struct
{
UValueType oldTarget = target.Value;
// Just move it over.
target.Value = source.Value;
return oldTarget;
}
#endregion // Open RVTOps
#endregion // Test Operations Implementing OP Delegates
#region Utility Methods
/// <summary>
/// Method that does some OPs to simulate processing delay.
/// </summary>
/// <param name="delay">
/// The number of time units to delay.
/// </param>
public static void Delay(Int32 delay)
{
Int32 numIterations = 10000;
Int32 workingVar = 0;
for(int i = 0; i < delay * numIterations; i++)
workingVar *= workingVar;
}
#endregion // Utility Methods
#endregion // Static Test Methods
#endregion // Static Class Methods
}
#endregion
#region Tests
/// <summary>
/// Level 2 tests for functions in AtomicUtils. These are attributed for Nunit as
/// level 2 since they take some time due to delays introduced for threading.
/// </summary>
[TestFixture]
[Category(QAUtils.TestLevel2Name)]
public class QATester_AtomicUtils_2
{
//// Here follows a number of class-level data objects that are used for
//// The various tests.
//
//// These are for the basic Int32 tests.
///////////////////////////////////////////////////////////////////////
// CallerObject for the Int32 RetryLoop caller calling the default OP - no delay.
Int32ConcurrentOpCallerObject m_RetryLoopInt32ReplaceCallerObject;
// CallerObject for the Int32 RetryLoop caller calling the default OP - random OP delay.
Int32ConcurrentOpCallerObject m_RetryLoopInt32DelayedReplaceCallerObject;
// CallerObject for the Int32 RetryLoop caller calling the add OP - no delay.
Int32ConcurrentOpCallerObject m_RetryLoopInt32AddCallerObject;
// CallerObject for the Int32 RetryLoop caller calling the exp OP - no delay.
Int32ConcurrentOpCallerObject m_RetryLoopInt32ExpCallerObject;
//// These are for the IRVT closed as an Int32.
///////////////////////////////////////////////////////////////////////
// CallerObject with the delayed replace OP.
IRVTConcurrentOpCallerObject<Int32> m_IRVTCallerObjectAsInt32DelayedReplace;
// Set up a CallerObject for the non-atomic caller.
IRVTConcurrentOpCallerObject<Int32> m_IRVTPseudoAtomicCallerObjectAsInt32DelayedReplace;
//// For the RVTConcurrentCaller test.
///////////////////////////////////////////////////////////////////////
// CallerObject with the delayed replace OP.
RVTTypeConcurrentOpCallerObject<ReferencedValueType<Int32>, Int32>
m_RVTTypeCallerObjectAsInt32DelayedReplace;
//// These are for the RVT closed as an Int32.
///////////////////////////////////////////////////////////////////////
// These are class-level variables that are used to hold targets for the Threads.
ReferencedValueType<Int32> multiAccessTargetInt32RVT = new ReferencedValueType<Int32>();
///////////////////////////////////////////////////////////////////////
//// For the WorkerProcessControl test.
// CallerObject with the replace OP. This is the default CallerObject.
IRVTConcurrentOpCallerObject<Int32> m_IRVTCallerObjectAsInt32Replace;
// CallerObject with the replace OP - this one is for a special
// Main control Thread.
IRVTConcurrentOpCallerObject<Int32> m_IRVTCallerObjectAsInt32Replace_Main;
// CallerObject with the replace OP - this one is for a special
// Worker Thread.
IRVTConcurrentOpCallerObject<Int32> m_IRVTCallerObjectAsInt32Replace_LongRunningThread;
// Same thing here....
IRVTConcurrentOpCallerObject<Int32> m_IRVTCallerObjectAsInt32Replace_SlowRunningThread;
/// <summary>
/// Obligatory default constructor (for NUnit, that is). This one actually
/// does something :-)
/// </summary>
public QATester_AtomicUtils_2()
{
// Here we build CallerObjects for the Thread process. These have the standard
// versions of a delegate for the caller, a delegate for the OP, random
// data generator, random delay generator and work out of the static
// target int. This one takes the default OP (the replacement OP) with no delay.
// 10 iterations.
m_RetryLoopInt32ReplaceCallerObject = new Int32ConcurrentOpCallerObject(
QATester_MultiProcessing_Testdata.RetryLOOPPseudoAtomicInt32OpCaller,
QATester_MultiProcessing_Testdata.Int32ReplaceOp, 10,
QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
QATester_MultiProcessing_Testdata.s_multiaccessInt32Target);
// This one takes the Delayed delegate, but is otherwise the same.
m_RetryLoopInt32DelayedReplaceCallerObject = new Int32ConcurrentOpCallerObject(
QATester_MultiProcessing_Testdata.RetryLOOPPseudoAtomicInt32OpCaller,
QATester_MultiProcessing_Testdata.Int32DelayedReplaceOp, 10,
QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
QATester_MultiProcessing_Testdata.s_multiaccessInt32Target);
// This one takes the adder delegate, but is otherwise the same as the first.
m_RetryLoopInt32AddCallerObject = new Int32ConcurrentOpCallerObject(
QATester_MultiProcessing_Testdata.RetryLOOPPseudoAtomicInt32OpCaller,
QATester_MultiProcessing_Testdata.Int32AddOp, 10,
QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
QATester_MultiProcessing_Testdata.s_multiaccessInt32Target);
// This one takes the exponentiation delegate, but is otherwise the same as the first.
m_RetryLoopInt32ExpCallerObject = new Int32ConcurrentOpCallerObject(
QATester_MultiProcessing_Testdata.RetryLOOPPseudoAtomicInt32OpCaller,
QATester_MultiProcessing_Testdata.Int32ExpOp, 10,
QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
QATester_MultiProcessing_Testdata.s_multiaccessInt32Target);
// This is for the GenericRVTCaller experiment. This is a CallerObject
// for the Thread process, designed to be passed to an RVT - based Caller.
// This has the standard version of a delegate for the caller, a delegate
// for the OP, random delay generator and work out of a local target int.
// This one takes the default IRVTOp (the replacement OP) with delay.
// 10 iterations.
//
// We first have to make an IAuditingGenerator<T>, because that's what
// The CallerObject uses.
m_RVTTypeCallerObjectAsInt32DelayedReplace
= new RVTTypeConcurrentOpCallerObject<ReferencedValueType<Int32>,Int32>(
QATester_MultiProcessing_Testdata.RVTTypeConcurrentOpCaller<ReferencedValueType<Int32>, Int32>,
QATester_MultiProcessing_Testdata.RVTDelayedReplaceOp<ReferencedValueType<Int32>,Int32>,
10, QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
multiAccessTargetInt32RVT);
// The following are CallerObjects for the Thread process, this time designed
// to be passed to an IRVT. These have the standard versions of a delegate
// for the caller, a delegate for the OP, random data generator, random delay
// generator and work out of a local target int. This one takes the
// default IRVTOp (the replacement OP) with delay. 10 iterations.
m_IRVTCallerObjectAsInt32DelayedReplace
= new IRVTConcurrentOpCallerObject<Int32>(
QATester_MultiProcessing_Testdata.IRVTConcurrentOpCaller<Int32>,
QATester_MultiProcessing_Testdata.IRVTDelayedReplaceOp<Int32>,
10, QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
multiAccessTargetInt32RVT);
// Same thing again, but using the non-atomic caller.
m_IRVTPseudoAtomicCallerObjectAsInt32DelayedReplace
= new IRVTConcurrentOpCallerObject<Int32>(
QATester_MultiProcessing_Testdata.IRVTPseudoAtomicOpCaller<Int32>,
QATester_MultiProcessing_Testdata.IRVTDelayedReplaceOp<Int32>,
10, QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
multiAccessTargetInt32RVT);
///////////////////////////////////////////////////////////////////
// In this section, we set up for the Worker Thread "control"
// experiment that we run in test "WorkerProcessControl". We set up
// data for a a Main Thread and some Workers.
///////////////////////////////////////////////////////////////////
// This is the default CallerObject with non-delayed replace.
m_IRVTCallerObjectAsInt32Replace
= new IRVTConcurrentOpCallerObject<Int32>(
QATester_MultiProcessing_Testdata.IRVTConcurrentOpCaller<Int32>,
QATester_MultiProcessing_Testdata.IRVTReplaceOp<Int32>,
10, QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
QATester_MultiProcessing_Testdata.s_Int32RngDelays,
multiAccessTargetInt32RVT);
// We are now going to make a separate CallerObject for a Main Thread to
// show how the test runners can set up different Threads with different
// characteristics. We can't do much with changing the OP, since IRVT's
// are manipulators of a rather abstract nature. It would be entirely
// possible, however, to define additional interfaces that provided
// additional operations (arirhmetic, comparisons, who knows what else).
// We don't do so here. We can, however, change random number generators
// and change other things. In particular, we kill two birds with one
// stone and set ControlRegister on the Main Thread to exercise some
// control over Worker Thread operation in addition to changing iterations
// and delays on Threads. See IRVTTestRunner() for what we do with it
// the ControlRegister and StatusRegister internally.
//
// First build a new generator for Main with shorter delays.
// Seed 10000, Delays range from 1 to 100 - no special OP needed,
// no logging. Delays are short because we want Main to finish
// early so it can manipulate Worker Threads before they finish.
// Initialize the generator with a zero data state (we actually
// don't use it, so we don't care). Note that these all use the
// non-delayed version of the replace OP, since delays are incorporated
// in the Thread process in IRVTDelayedConcurrentCallerThreadProc().
AuditingRNGenerator<Int32> new_Int32RngDelays
= new AuditingRNGenerator<Int32>(10000, 1, 100, null, 0, false);
// This is the CallerObject for the Main Thread process. We build it
// with the delay generator we just created - otherwise it's the same.
m_IRVTCallerObjectAsInt32Replace_Main
= new IRVTConcurrentOpCallerObject<Int32>(
QATester_MultiProcessing_Testdata.IRVTConcurrentOpCaller<Int32>,
QATester_MultiProcessing_Testdata.IRVTReplaceOp<Int32>,
10, QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
new_Int32RngDelays, multiAccessTargetInt32RVT);
// We also set it's ControlRegister to -1. We'll use this to demonstrate
// how to control Threads. The testrunner "IRVTTestRunner" uses this
// internally to perform that demonstration.
m_IRVTCallerObjectAsInt32Replace_Main.m_controlRegister = -1;
// We spawn a CallerObject for a second Thread to run under control
// of Main. We would like this object to specify a large number of
// iterations - just to show how to set up a single Worker Thread that
// will definately have to be stopped early. We spawn it with an initial
// state of 1234567 from the CallerObject we built for Main.
m_IRVTCallerObjectAsInt32Replace_LongRunningThread
= m_IRVTCallerObjectAsInt32Replace_Main.SpawnCallerObject(1234567);
// We would like to make this Thread run much longer than the others,
// so we'll set more iterations. It is already going to run much
// faster, since we are spawning Main's generator with its shorter
// delay. We decreased delays by a factor of 10 and increased iterations
// by a factor of 100 - let's hope it works :-)
m_IRVTCallerObjectAsInt32Replace_LongRunningThread.m_numCallsOnThread = 1000;
// We create a CallerObject for a second Worker Thread to run under control
// of Main. We would like this CallerObject to specify a long delay - we
// want the Main Thread program to have to wait for this Thread to complete
// a certain number of operations (only three) - this Thread will be the
// laggard in this test.
//
// First create a new generator - fixed delay of 2000.
new_Int32RngDelays
= new AuditingRNGenerator<Int32>(10001, 2000, 2000, null, 0, false);
// Run 10 operations on this Thread. Keep in mind that each OP will take
// a long time.
m_IRVTCallerObjectAsInt32Replace_SlowRunningThread
= new IRVTConcurrentOpCallerObject<Int32>(
QATester_MultiProcessing_Testdata.IRVTConcurrentOpCaller<Int32>,
QATester_MultiProcessing_Testdata.IRVTReplaceOp<Int32>,
10, QATester_MultiProcessing_Testdata.s_Int32RngNumbers,
new_Int32RngDelays, multiAccessTargetInt32RVT);
}
/// <summary>
/// </summary>
[TestFixtureSetUp]
public void TestFixtureSetup()
{
// Shut off reporting for autotests under Nunit. The tests in this file
// all just scroll to the screen, so if you uncomment this, the tests
// are totally useless. Someday, if somebody wants to put quantitative
// tests in here then this is one way to shut off the screen ....
//s_reportToScreen = false;
}
/// <summary>
/// </summary>
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
}
/// <summary>
/// This test runs the
/// <see cref="QATester_MultiProcessing_Testdata.RetryLOOPPseudoAtomicInt32OpCaller"/>
/// with multiple Threads to demonstrate that it does not provide atomic operation. You
/// might see a couple of collisions in the test.
/// </summary>
[Test(Description = "Level 2 series a tests for RetryLoopInt32PseudoAtomicOpCaller")]
public void QATester_RunTest_RetryLoopInt32PseudoAtomicOpCaller_a()
{
Console.WriteLine("Running RetryLoopInt32PseudoAtomicOpCaller_a");
// We don't want any old Threads running or old data.
QATester_MultiProcessing_Testdata.ClearThreads();
// We also want the standard default settings for this test.
QATester_MultiProcessing_Testdata.ResetData();
// Set the number of Threads.
QATester_MultiProcessing_Testdata.s_numThreadsInUse = 10;
QATester_MultiProcessing_Testdata.Int32TestRunner(m_RetryLoopInt32ReplaceCallerObject, true);
}
/// <summary>
/// This test runs the
/// <see cref="QATester_MultiProcessing_Testdata.RetryLOOPPseudoAtomicInt32OpCaller"/>
/// with multiple Threads to demonstrate that it does not provide atomic operation. This
/// time we use delays - things get much worse.
/// </summary>
[Test(Description = "Level 2 series b tests for RetryLoopInt32PseudoAtomicOpCaller")]
public void QATester_RunTest_RetryLoopInt32PseudoAtomicOpCaller_b()
{
// Took out "b" for the article printout.
// This is the one we use for the article's first printout.
// Console.WriteLine("Running RetryLoopInt32PseudoAtomicOpCaller_b");
Console.WriteLine("Running RetryLoopInt32PseudoAtomicOpCaller");
// Usual setup jazz.
QATester_MultiProcessing_Testdata.ClearThreads();
QATester_MultiProcessing_Testdata.ResetData();
// Set the number of Threads.
QATester_MultiProcessing_Testdata.s_numThreadsInUse = 3;
QATester_MultiProcessing_Testdata.Int32TestRunner(m_RetryLoopInt32DelayedReplaceCallerObject, true);
}
/// <summary>
/// This test runs the
/// <see cref="QATester_MultiProcessing_Testdata.IRVTConcurrentOpCaller"/>
/// with multiple Threads to test it for atomic operation. In this case we
/// use delays. We want to demonstrate the obvious - access to a locked
/// object is thread-safe. The caller is closed as an Int32. This test run
/// passes no Thread data (null), so it will be created internally based
/// on Thread Id's for generator seeds.
/// </summary>
[Test(Description = "Level 2 series a tests for IRVTConcurrentCaller")]
public void QATester_RunTest_IRVTConcurrentCaller_a()
{
Console.WriteLine("Running IRVTConcurrentCaller_a");
// Usual setup jazz.
QATester_MultiProcessing_Testdata.ClearThreads();
QATester_MultiProcessing_Testdata.ResetData();
// Set the number of Threads locally.
Int32 numSpawnedThreadsInUse = 10;
QATester_MultiProcessing_Testdata.IRVTTestRunner<Int32>(
QATester_MultiProcessing_Testdata.IRVTConcurrentCallerProcess<Int32>,
m_IRVTCallerObjectAsInt32DelayedReplace,
null, numSpawnedThreadsInUse, true);
}
/// <summary>
/// This test runs the
/// <see cref="QATester_MultiProcessing_Testdata.RVTTypeConcurrentOpCaller"/>.
/// We want to test the operation of a Thread process with the Caller designed
/// to take an actual full constructed Type, not just the IRVT Interface.
/// Working with the IRVT only is usually a better way to go, but sometimes
/// the RVT-based one is needed. Again, the Generic is just closed on a simple
/// Int32. See ReferencedValueTypes.cs for the more complicated stuff.
/// </summary>
[Test(Description = "Level 2 series a tests for RVTConcurrentCaller")]
public void QATester_RunTest_RVTConcurrentCaller_a()
{
Console.WriteLine("Running RVTConcurrentCaller_a");
// Usual setup jazz.
QATester_MultiProcessing_Testdata.ClearThreads();
QATester_MultiProcessing_Testdata.ResetData();
// Set the number of Threads locally.
Int32 numSpawnedThreadsInUse = 10;
// We are going to send in a null ThreadData array. We already demonstrated
// how to do customized Threads in WorkerProcessControl_a. All Thread
// CallerObject's (10 in use here - no Main Thread) will be synthesized
// internally by the test runner.
RVTTypeThreadData<ReferencedValueType<Int32>, Int32>[] threadDataArray = null;
QATester_MultiProcessing_Testdata.RVTTypeTestRunner<ReferencedValueType<Int32>,Int32>(
m_RVTTypeCallerObjectAsInt32DelayedReplace, threadDataArray,
numSpawnedThreadsInUse, true);
}
/// <summary>
/// This test runs the
/// <see cref="QATester_MultiProcessing_Testdata.IRVTPseudoAtomicOpCaller"/>.
/// to test it for atomic operation. Pretty much the same as the
/// previous test. Again, we want to demonstrate the obvious - access to
/// an object that is not locked all the way through the access is NOT
/// thread-safe. The caller is closed as an Int32, once again. You should
/// see collisions.
/// </summary>
[Test(Description = "Level 2 series a tests for IRVTPseudoAtomicConcurrentCaller")]
public void QATester_RunTest_IRVTPseudoAtomicConcurrentCaller_a()
{
Console.WriteLine("Running IRVTPseudoAtomicConcurrentCaller_a");
// Usual setup jazz.
QATester_MultiProcessing_Testdata.ClearThreads();
QATester_MultiProcessing_Testdata.ResetData();
// Set the number of Threads locally.
Int32 numSpawnedThreadsInUse = 10;
QATester_MultiProcessing_Testdata.IRVTTestRunner<Int32>(
QATester_MultiProcessing_Testdata.IRVTConcurrentCallerProcess<Int32>,
m_IRVTPseudoAtomicCallerObjectAsInt32DelayedReplace, null, numSpawnedThreadsInUse, true);
}
/// <summary>
/// This test again runs the
/// <see cref="QATester_MultiProcessing_Testdata.IRVTConcurrentOpCaller"/>
/// with multiple/ Threads. In this test, we will demonstrate two things.
/// First, we demonstrate how to set up different Threads by hand, which
/// have different characteristics. The second thing we want to demonstrate
/// is how "well-behaved" Threads can communicate with a controller through
/// jointly accessable data items. We use the "ControlRegister" and
/// "StatusRegister" fields in the CallerObject for this purpose. The
/// test will employ processing on Main, since this must be turned on for
/// the status and control feature to be demonstrated.
/// </summary>
[Test(Description = "Level 2 series b tests for IRVTConcurrentCaller")]
public void QATester_RunTest_IRVTConcurrentCaller_b()
{
// Made nice for article. Console.WriteLine("Running IRVTConcurrentCaller_b");
Console.WriteLine("Running WorkerProcessControl");
// Usual setup jazz.
QATester_MultiProcessing_Testdata.ClearThreads();
QATester_MultiProcessing_Testdata.ResetData();
// Set the number of Threads locally - we need at least three Threads to demonstrate
// everything we'd like. This is the number of Worker Threads.
Int32 numSpawnedThreadsInUse = 3;
// Build a new ThreadData array.
IRVTThreadData<Int32>[] threadDataArray = new IRVTThreadData<Int32>[numSpawnedThreadsInUse+1];
for(Int32 i = 0; i <= numSpawnedThreadsInUse; i++)
threadDataArray[i] = new IRVTThreadData<Int32>();
// Install our Main's CallerObject.
threadDataArray[numSpawnedThreadsInUse].m_callerObject
= m_IRVTCallerObjectAsInt32Replace_Main;
// Install our long running Thread's CallerObject if we are not just processing
// on Main. It could be anywhere, but we'll just make it first.
if(numSpawnedThreadsInUse > 0)
threadDataArray[0].m_callerObject = m_IRVTCallerObjectAsInt32Replace_LongRunningThread;
// Same deal for the slow running Thread.
if(numSpawnedThreadsInUse > 1)
threadDataArray[1].m_callerObject = m_IRVTCallerObjectAsInt32Replace_SlowRunningThread;
// All of the rest of the Thread CallerObject's (if any) will be synthesized
// internally by the TestRunner. We have three Worker Threads altogether, so
// the TestRunner will be synthesizing just one.
QATester_MultiProcessing_Testdata.IRVTTestRunner<Int32>(
QATester_MultiProcessing_Testdata.IRVTDelayedConcurrentCallerProcess<Int32>,
m_IRVTCallerObjectAsInt32Replace, threadDataArray,
numSpawnedThreadsInUse, true);
}
}
#endregion // Tests
#region Helper Classes
#region Thread Data Classes
///////////////////////////////////////////////////////////////////////////
// The classes in this region were constructed for use within the
// demonstration system. These are used to hold information for each Thread
// that is to be run in multithreading experiments contained herein. They
// allow information to set up in arrays (the Thread object itself and it's
// data) to be passed to the test running executives (the "TestRunners").
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// This class holds a <see cref="System.Threading.Thread"/> reference and
/// also the "CallerObject" for Threads processing Int32's.
/// </summary>
public class Int32ThreadData
{
/// <summary>
/// The <see cref="System.Threading.Thread"/>.
/// </summary>
public Thread m_thread;
/// <summary>
/// The data for an Int32 CallerProcess.
/// </summary>
public Int32ConcurrentOpCallerObject m_callerObject;
}
/// <summary>
/// This is the Generic version for RVT's.
/// </summary>
public class RVTTypeThreadData<T, UValueType>
where T : class, IReferencedValueType<UValueType>
where UValueType : struct
{
/// <summary>
/// The <see cref="System.Threading.Thread"/>.
/// </summary>
public Thread m_thread;
/// <summary>
/// The data for a Generic RVT CallerProcess.
/// </summary>
public RVTTypeConcurrentOpCallerObject<T, UValueType> m_callerObject;
}
/// <summary>
/// This is the Generic version for IRVT's.
/// </summary>
public class IRVTThreadData<UValueType>
where UValueType : struct
{
/// <summary>
/// The <see cref="System.Threading.Thread"/>.
/// </summary>
public Thread m_thread;
/// <summary>
/// The data for a Generic IRVT CallerProcess.
/// </summary>
public IRVTConcurrentOpCallerObject<UValueType> m_callerObject;
}
#endregion // Thread Data Classes
#region CallerObjects
///////////////////////////////////////////////////////////////////////////
// The classes contained herein are containers for parameters and data for
// for the various <see cref="System.Threading.ParameterizedThreadStart"/>
// delegates that are used in multithreading tests. They are designed to be
// populated with information parameters and then passed in to a Thread
// upon startup. The method running on the Thread then picks off the
// parameters for the test.
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/// <summary>
/// This CallerObject is designed to support the simple non-Generic Int32
/// tests. It is designed to support the simple <see cref="Int32ConcurrentOpCaller"/>
/// delegate which invokes Int32 operations by calling an <see cref="Int32Op"/>
/// delegate.
/// </summary>
public class Int32ConcurrentOpCallerObject
{
/// <summary>
/// This is the Int32 - specific OP caller.
/// </summary>
public Int32ConcurrentOpCaller m_caller;
/// <summary>
/// This is the delegate that will perform operations on the passed
/// Int32 when processing occurs.
/// </summary>
public Int32Op m_oP;
/// <summary>
/// This parameter is the number of iterations the internal Caller
/// running on a Thread will perform in a given experiment.
/// </summary>
public Int32 m_numCallsOnThread;
/// <summary>
/// This is the generator for random Int32 data.
/// </summary>
public IAuditingGenerator<Int32> m_numberGenerator;
/// <summary>
/// This is the generator for random delay data. Delays are always Int32's
/// in all of our experiments.
/// </summary>
public IAuditingGenerator<Int32> m_delayGenerator;
/// <summary>
/// This is used to hold a copy of the common target that can be used to
/// record its initial value at the initialization of an experiment.
/// </summary>
public Int32 m_target;
/// <summary>
/// Main constructor just sets everything.
/// </summary>
public Int32ConcurrentOpCallerObject(Int32ConcurrentOpCaller caller,
Int32Op oP, Int32 numCallsOnThread, IAuditingGenerator<Int32> numberGenerator,
IAuditingGenerator<Int32> delayGenerator, Int32 target)
{
m_caller = caller;
m_oP = oP;
m_numCallsOnThread = numCallsOnThread;
m_numberGenerator = numberGenerator;
m_delayGenerator = delayGenerator;
m_target = target;
}
/// <summary>
/// Copy constructor. Makes shallow copies of everything.
/// </summary>
public Int32ConcurrentOpCallerObject(Int32ConcurrentOpCallerObject callerObject)
{
m_caller = callerObject.m_caller;
m_oP = callerObject.m_oP;
m_numCallsOnThread = callerObject.m_numCallsOnThread;
m_numberGenerator = callerObject.m_numberGenerator;
m_delayGenerator = callerObject.m_delayGenerator;
m_target = callerObject.m_target;
}
#region Methods
/// <summary> Getter for the field. </summary>
public object GetReferenceTarget()
{ return m_target; }
/// <summary>
/// This method spawns a new CallerObject by copying the old over and
/// spawning two new generators for it.
/// </summary>
/// <param name="initialState">
/// This is the initial state that the new delay and number generators are
/// initialized with.
/// </param>
/// <returns>
/// The new CallerObject that has new independent generators with the
/// same parameters as the generators on the current instance, but with
/// different initial states.
/// </returns>
public Int32ConcurrentOpCallerObject SpawnCallerObject(Int32 initialState)
{
Int32ConcurrentOpCallerObject newObject
= new Int32ConcurrentOpCallerObject(this);
newObject.m_numberGenerator = newObject.m_numberGenerator.SpawnGenerator(initialState);
newObject.m_delayGenerator = newObject.m_delayGenerator.SpawnGenerator(initialState);
return newObject;
}
#endregion // Methods
#region Properties
//// These are all sort of redundant, since the fileds are public.
/// <summary> Getter for the field. </summary>
public Int32ConcurrentOpCaller Caller
{ get { return m_caller; } }
/// <summary> Getter for the field. </summary>
public Int32Op Op
{ get { return m_oP; } }
/// <summary> Getter for the field. </summary>
public Int32 NumCallsOnThread
{ get { return m_numCallsOnThread; } }
/// <summary> Getter for the field. </summary>
public IAuditingGenerator<Int32> NumberGenerator
{ get { return m_numberGenerator; } }
/// <summary> Getter for the field. </summary>
public IAuditingGenerator<Int32> DelayGenerator
{ get { return m_delayGenerator; } }
#endregion // Properties
}
/// <summary>
/// This CallerObject is designed to support the full Generic RVT "Type" in
/// the concurrency tests. It is designed to support the
/// <see cref="RVTTypeConcurrentOpCaller<T, UValueType>"/> caller delegate
/// which invokes T - valued operations by calling an <see cref="RVTOp<T, UValueType>"/>
/// delegate.
/// </summary>
public class RVTTypeConcurrentOpCallerObject<T, UValueType>
where T : class, IReferencedValueType<UValueType>
where UValueType : struct
{
/// <summary>
/// This is a caller that takes the full <see typeparamref="T"/>.
/// </summary>
public RVTTypeConcurrentOpCaller<T, UValueType> m_caller;
/// <summary>
/// This is the delegate that will perform operations on the passed
/// RVT of type <see typeparamref="T"/> when processing occurs.
/// </summary>
public RVTOp<T, UValueType> m_rVToP;
/// <summary> Same old. </summary>
public Int32 m_numCallsOnThread;
/// <summary> Defined as Int32 generator to demo ConvertNumber. </summary>
public IAuditingGenerator<Int32> m_numberGenerator;
/// <summary> Always use an Int32 generator for delays. </summary>
public IAuditingGenerator<Int32> m_delayGenerator;
/// <summary>
/// This points to the target. Note <see typeparamref="T"/> is a class
/// so this is a reference - it can be read/write.
/// </summary>
public T m_target;
/// <summary>
/// Main constructor just sets everything. The internal value of target is
/// set to default(UValueType).
/// </summary>
public RVTTypeConcurrentOpCallerObject(RVTTypeConcurrentOpCaller<T, UValueType> caller,
RVTOp<T, UValueType> rVToP, Int32 numCallsOnThread, IAuditingGenerator<Int32> numberGenerator,
IAuditingGenerator<Int32> delayGenerator, T target)
{
m_caller = caller;
m_rVToP = rVToP;
m_numCallsOnThread = numCallsOnThread;
m_numberGenerator = numberGenerator;
m_delayGenerator = delayGenerator;
// Point to the target.
m_target = target;
// Initialize it.
((IReferencedValueType<UValueType>)m_target).Value = default(UValueType);
}
/// <summary>
/// Copy constructor. Makes shallow copies of everything.
/// </summary>
public RVTTypeConcurrentOpCallerObject(RVTTypeConcurrentOpCallerObject<T, UValueType> callerObject)
{
m_caller = callerObject.m_caller;
m_rVToP = callerObject.m_rVToP;
m_numCallsOnThread = callerObject.m_numCallsOnThread;
m_numberGenerator = callerObject.m_numberGenerator;
m_delayGenerator = callerObject.m_delayGenerator;
m_target = callerObject.m_target;
}
#region Methods
/// <summary> Getter for the field. </summary>
public T GetReferenceTarget()
{ return m_target; }
/// <summary>
/// This method spawns a new CallerObject by copying the old over and
/// spawning two new generators for it. The generators have the same
/// parameters, but a different initial state.
/// </summary>
/// <param name="initialState">
/// This is the initial state that the new delay and number generators are
/// initialized with.
/// </param>
/// <returns>
/// A copy of the old object with an independent copy of the generators.
/// </returns>
public RVTTypeConcurrentOpCallerObject<T, UValueType> SpawnCallerObject(Int32 initialState)
{
RVTTypeConcurrentOpCallerObject<T, UValueType> newObject
= new RVTTypeConcurrentOpCallerObject<T, UValueType>(this);
newObject.m_numberGenerator = newObject.m_numberGenerator.SpawnGenerator(initialState);
newObject.m_delayGenerator = newObject.m_delayGenerator.SpawnGenerator(initialState);
return newObject;
}
#endregion // Methods
#region Properties
//// Properties are again redundant, but it's good to show how to build
//// Generic Properties.
/// <summary>
/// In this case the Property provides access to a Generic delegate. A delegate
/// is no different than any other Type that is exposed through a Property.
/// Neither is a Generic any different from any other delegate from this perspective.
/// This Property also has a Set method. The Set method could be used to change
/// the delegate at any time.
/// </summary>
public RVTTypeConcurrentOpCaller<T, UValueType> Caller
{ get { return m_caller; } set { m_caller = value; } }
/// <summary> Getter for the field. Same sort of comment for a Generic OP. </summary>
public RVTOp<T, UValueType> Op
{ get { return m_rVToP; }}
/// <summary> Getter for the field. </summary>
public Int32 NumCallsOnThread
{ get { return m_numCallsOnThread; } }
// Both generators are Int32.
/// <summary> Getter for the field. </summary>
public IAuditingGenerator<Int32> NumberGenerator
{ get { return m_numberGenerator; } }
/// <summary> Getter for the field. </summary>
public IAuditingGenerator<Int32> DelayGenerator
{ get { return m_delayGenerator; } }
#endregion // Properties
}
/// <summary>
/// This CallerObject is designed to support the Generic IRVT tests. In these
/// tests, RVTs are handled only through their interface,
/// <see cref="IReferencedValueType<UValueType>"/>.
/// This CallerObject is designed to support the
/// <see cref="IRVTConcurrentOpCaller<UValueType>"/> caller delegate
/// which invokes UValueType - valued operations by calling an
/// <see cref="IRVTOp<UValueType>"/> delegate. Like the delegate
/// it supports, the RVT is handled through its interface only.
/// </summary>
/// <remarks>
/// This class adds support for the "Thread control" demonstration by
/// including a "StatusRegister" and a "ControlRegister" and some specialized
/// Properties to access them.
/// </remarks>
public class IRVTConcurrentOpCallerObject<UValueType>
where UValueType : struct
{
/// <summary>
/// This is the delegate that will perform operations on the passed
/// IRVT when processing occurs.
/// </summary>
public IRVTConcurrentOpCaller<UValueType> m_caller;
/// <summary> The Generic IRVT OP. </summary>
public IRVTOp<UValueType> m_iRVToP;
/// <summary>
/// See <see cref="RVTTypeConcurrentOpCallerObject<T, UValueType>"/>.
/// </summary>
public Int32 m_numCallsOnThread;
/// <summary>
/// Generator for the data - this time a Generic generator.
/// </summary>
public IAuditingGenerator<UValueType> m_numberGenerator;
/// <summary>
/// Always use an Int32 generator for delays.
/// </summary>
public IAuditingGenerator<Int32> m_delayGenerator;
/// <summary>
/// This points to the target, whose access is to be synchronized. This
/// time it's handled through the interface.
/// </summary>
public IReferencedValueType<UValueType> m_target;
/// <summary>
/// This is a simple example of a jointly accessible data item that can be used
/// to signal a cooperating Thread process to stop. This data item is
/// only written by the "control program" and is only read by the Worker
/// Thread, so there is no need for synchronization. It needs to be "volatile",
/// though, to make sure it is written out to core as soon as it written
/// by a "control program".
/// </summary>
public volatile Int32 m_controlRegister;
/// <summary>
/// This is a status variable that is written by the Worker Thread
/// and read by the control program. Again, this would not require
/// synchronization, since it is only written by one participant.
/// Should be volatile again, though.
/// </summary>
public volatile Int32 m_statusRegister;
/// <summary>
/// Main constructor just sets everything. The internal value of target is
/// set to default(UValueType).
/// </summary>
public IRVTConcurrentOpCallerObject(IRVTConcurrentOpCaller<UValueType> caller,
IRVTOp<UValueType> iRVToP, Int32 numCallsOnThread, IAuditingGenerator<UValueType> numberGenerator,
IAuditingGenerator<Int32> delayGenerator, IReferencedValueType<UValueType> target)
{
m_caller = caller;
m_iRVToP = iRVToP;
m_numCallsOnThread = numCallsOnThread;
m_numberGenerator = numberGenerator;
m_delayGenerator = delayGenerator;
// Point to the target.
m_target = target;
// Initialize it.
m_target.Value = default(UValueType);
// The ControlRegister and StatusRegister are examples of "one-way"
// synchronization data items that are either read-only or write-only
// for a participant (either the MultiProcess or the MultiProcessController)
// in a given ConcurrencyTransaction. These are normally accessed through a
// "split interface", where one interface has the "get" method and one
// interface has the "set" method.
m_controlRegister = 0;
m_statusRegister = 0;
}
/// <summary>
/// Copy constructor. Makes shallow copies of everything.
/// </summary>
public IRVTConcurrentOpCallerObject(IRVTConcurrentOpCallerObject<UValueType> callerObject)
{
m_caller = callerObject.m_caller;
m_iRVToP = callerObject.m_iRVToP;
m_numCallsOnThread = callerObject.m_numCallsOnThread;
m_numberGenerator = callerObject.m_numberGenerator;
m_delayGenerator = callerObject.m_delayGenerator;
m_target = callerObject.m_target;
m_controlRegister = callerObject.m_controlRegister;
m_statusRegister = callerObject.m_statusRegister;
}
#region Methods
/// <summary>
/// This method spawns a new CallerObject by copying the old over and
/// spawning two new generators for it. The generators have the same
/// parameters, but a different initial state.
/// </summary>
/// <param name="initialState">
/// This is the initial state that the new delay and number generators are
/// initialized with.
/// </param>
/// <returns>
/// A copy of the old object with an independent copy of the generators.
/// </returns>
public IRVTConcurrentOpCallerObject<UValueType> SpawnCallerObject(Int32 initialState)
{
IRVTConcurrentOpCallerObject<UValueType> newObject
= new IRVTConcurrentOpCallerObject<UValueType>(this);
newObject.m_numberGenerator = newObject.m_numberGenerator.SpawnGenerator(initialState);
newObject.m_delayGenerator = newObject.m_delayGenerator.SpawnGenerator(initialState);
return newObject;
}
#endregion // Methods
#region Properties
/// <summary> Getter for the field. </summary>
public IRVTConcurrentOpCaller<UValueType> Caller
{ get { return m_caller; } }
/// <summary> Getter for the field. </summary>
public IRVTOp<UValueType> Op
{ get { return m_iRVToP; } }
/// <summary> Getter for the field. </summary>
public Int32 NumCallsOnThread
{ get { return m_numCallsOnThread; } }
/// <summary> Getter for the field. </summary>
public IAuditingGenerator<UValueType> NumberGenerator
{ get { return m_numberGenerator; } }
/// <summary> Getter for the field. </summary>
public IAuditingGenerator<Int32> DelayGenerator
{ get { return m_delayGenerator; } }
/// <summary> Getter for the field. </summary>
public IReferencedValueType<UValueType> GetReferenceTarget()
{ return m_target; }
//// The following two are not redundant with the fields. They expose our
//// ControlRegister and our StatusRegister in a special way. Well, the
//// ThreadStopNow does, anyway. You get the picture......
/// <summary>
/// Reads the internal register and says to stop if it's positive. Designed
/// to be read by a CallerProcess.
/// </summary>
public bool ThreadStopNow
{ get { if(m_controlRegister > 0) return true; else return false; } }
/// <summary>
/// Reads the internal register and reports iteration number. Designed
/// to be read by a MultiProcessController (in our examples this is the
/// TestRunnner).
/// </summary>
public Int32 CurrentIteration
{ get { return m_statusRegister;} }
#endregion // Properties
}
#endregion // CallerObjects
#region Number Generators
/// <summary>
/// This interface defines a class of number generators that provide stimulus
/// for testing of MultiProcess contention. It provides the ability to create
/// a new generator and get numbers from it. It also provides the ability
/// to spawn a "subgenerator" with an initial state that can be derived from
/// a Thread's ID or some other source. Generators can be of a statistical or
/// deterministic nature. The generator is assumed to produce Int32 numbers
/// internally, which are mapped to other numbers of type <see typeparamref="T"/>
/// for output.
/// </summary>
/// <typeparam name="T">
/// This is the Type of the number to be generated,
/// </typeparam>
public interface IAuditingGenerator<T>
{
/// <summary>
/// Converts the internal number to an arbitrary Type. This Type is dynamically
/// determined on each call and is not necessarily equal to <see typeparamref="T"/>.
/// If the Type is a <see cref="System.ValueType"/>, it places it in a box.
/// </summary>
/// <returns>
/// A ReferenceType or boxed version of a ValueType. This will be <c>null</c>
/// if the conversion cannot be accomplished.
/// </returns>
System.Object ConvertNumber(Type outputType);
/// <summary>
/// Gets the generator's history of generated numbers.
/// </summary>
/// <returns>
/// The Int32-valued list. May be <c>null</c> if auditing not enabled.
/// </returns>
List<Int32> GetNumberHistory();
/// <summary>
/// Gets the generator's history of generated operations by the OP delegate.
/// </summary>
/// <returns>
/// The T-valued list. May be <c>null</c> if auditing not enabled.
/// </returns>
List<T> GetOperationHistory();
/// <summary>
/// This method makes a copy of an existing generator and sets its initial
/// state to a specified value, but otherwise initializes the generator to
/// the initial values specified at construction time for the original
/// generator, not the CURRENT state of the original generator. This includes
/// clearing histories (if on) and resetting the operation state.
/// </summary>
/// <param name="initialState">
/// This is the specified initial state that the new generator should take.
/// This is usually an initial value for a random generator or an initial
/// value for a counter, etc.
/// </param>
/// <returns>
/// The new <see cref="IAuditingGenerator<T>"/> with the prescribed
/// state.
/// </returns>
IAuditingGenerator<T> SpawnGenerator(Int32 initialState);
/// <summary>
/// This gets the next number in sequence from the generator.
/// </summary>
/// <returns>
/// The T-valued number.
/// </returns>
T Next();
/// <summary>
/// Resets the generator to it's original state.
/// </summary>
void Reset();
/// <summary>
/// Resets the generator to a specified state.
/// </summary>
/// <param name="initialState">
/// The specified Int32-valued state to reset the generator to.
/// </param>
void Reset(Int32 initialState);
}
/// <summary>
/// A generator for Int32 random #s. The class is designed to keep
/// track of the result of operations that are taking place across multiple
/// locked Threads and compare the results using various synchronization
/// schemes with the "true" results. Closed Int32 implementation of the
/// Generic interface <see cref="IAuditingGenerator<T>"/>.
/// </summary>
public class AuditingInt32RNGenerator : AuditingRandomGenerator, IAuditingGenerator<Int32>
{
#region Class Fields
// The initial value of the "state" of the Int32 operation. This is the
// state that is reset with "reset".
Int32 m_initialOpState;
// The "state" of the Int32 operation.
Int32 m_opState;
// The delegate that defines the Int32 operation. Default is not to
// operate an internal delegate.
Int32Op m_opDelegate = null;
// List of Op history.
List<Int32> m_opHistory;
#endregion // Class Fields
#region Constructors
/// <summary>
/// Default constructor for inheritance support.
/// </summary>
public AuditingInt32RNGenerator() { }
/// <summary>
/// Main constructor sets up the Int32 internal generator and the Int32
/// OP-related items.
/// </summary>
/// <param name="seed">
/// Initial seed defining the "state" of the generator.
/// </param>
/// <param name="minValue">
/// Minimum (inclusive) value that Int32 numbers will take on.
/// </param>
/// <param name="maxValue">
/// Maximum (inclusive) value that Int32 numbers will take on.
/// </param>
/// <param name="oP">
/// The OP delegate to be used inside the generator.
/// </param>
/// <param name="initialOpState">
/// The initial state of the OP output.
/// </param>
/// <param name="historyOn">
/// <c>true</c> if the random number history and the OP history are to
/// be captured into the internal lists.
/// </param>
public AuditingInt32RNGenerator(Int32 seed, Int32 minValue, Int32 maxValue,
Int32Op oP, Int32 initialOpState, System.Boolean historyOn)
: base(seed, minValue, maxValue, historyOn)
{
// Add our Int32 - specific stuff.
m_opDelegate = oP;
m_initialOpState = initialOpState;
if(m_historyOn) {
m_opHistory = new List<Int32>();
}
}
/// <summary>
/// Copy constructor. Makes a copy of the generator and initializes it to
/// it's initial state according to the original inititial state of the
/// original generator, not the CURRENT state of the original generator.
/// It also clears the lists (if any) and sets the oPstate to it's initial
/// value.
/// </summary>
/// <param name="generator">
/// The existing generator to make a copy of.
/// </param>
public AuditingInt32RNGenerator(AuditingInt32RNGenerator generator)
: base(generator)
{
// Copy the Int32 - specific stuff.
m_opDelegate = generator.m_opDelegate;
m_initialOpState = generator.m_initialOpState;
m_historyOn = generator.m_historyOn;
if(m_historyOn) {
m_opHistory = new List<Int32>();
}
}
#endregion // Constructors
#region Methods
/// <summary>
/// Gets the operation history.
/// </summary>
/// <returns>
/// A <See cref="System.Collections.Generic.List<Int32>"/> if history
/// was turned on in the constructor, otherwise <c>null</c>.
/// </returns>
public List<Int32> GetOperationHistory()
{
return m_opHistory;
}
/// <summary>
/// Gets the next integer from the internal RNG and accumulates it through
/// the OP.
/// </summary>
/// <returns>
/// The <see cref="System.Int32"/> value produced by the random number
/// generator.
/// </returns>
/// <remarks>
/// If we were completely and absolutely fastidious about not duplicating
/// code anywhere, we'd break up this method into a base part in
/// <see cref="AuditingRandomGenerator"/> and a Type - specific part here.
/// But we're not........
/// </remarks>
public virtual Int32 Next()
{
// .Net's generator is exclusive on the high end (+1).
Int32 rn = m_rng.Next(m_minValue, m_maxValue + 1);
m_lastInt32Num = rn;
// If we have a delegate, this particular generator accumulates into
// its Opstate.
if(m_opDelegate != null)
m_opState = m_opDelegate(ref m_opState, rn);
// Store variables in the history if history is on.
if(m_historyOn) {
m_numHistory.Add(rn);
m_opHistory.Add(m_opState);
}
return rn;
}
/// <summary>
/// Creates a new copy of the generator with a given initial state.
/// </summary>
/// <returns>
/// An <see cref="IAuditingGenerator<Int32>"/> with the same parameters
/// as the parent generator (this), but a different internal state.
/// </returns>
public IAuditingGenerator<Int32> SpawnGenerator(Int32 initialState)
{
IAuditingGenerator<Int32> generator = new AuditingInt32RNGenerator(this);
generator.Reset(initialState);
return generator;
}
/// <summary>
/// Resets the generator and the states of everything and flushes history
/// lists if they are in use. We need an override to reset OP's etc.
/// </summary>
public override void Reset()
{
// Call base to do most things.
base.Reset();
// Add on our Int32 - specific things.
m_opState = m_initialOpState;
if(m_historyOn) {
m_opHistory.Clear();
}
}
#endregion // Methods
}
/// <summary>
/// A generator for random #s. The class is designed to keep
/// track of the result of operations that are taking place across multiple
/// locked Threads and compare the results using various synchronization
/// schemes with the "true" results. Open Generic implementation of the
/// Generic interface <see cref="IAuditingGenerator<T>"/>.
/// </summary>
public class AuditingRNGenerator<T> : AuditingRandomGenerator, IAuditingGenerator<T>
{
#region Class Fields
// The initial value of the "state" of the Int32 operation. This is the
// state that is reset with "reset".
T m_initialOpState;
// The "state" of the Type T operation.
T m_opState;
// The delegate that defines the Generic "OP". Default is not to
// operate an internal delegate (null).
Op<T> m_opDelegate = null;
// List of OP history.
List<T> m_opHistory;
///////////////////////////////////////////////////////////////////////
// A little extra bookeeping we like/need to do for a Generic
// generator.
///////////////////////////////////////////////////////////////////////
// Hold on to our Type.
Type m_typeType = typeof(T);
// The last number that was generated and then converted. We need this
// field to be able to hold the number for conversion out the "back door",
// using ConvertNumber(), among other reasons.
T m_lastNum;
#endregion // Class Fields
#region Constructors
/// <summary>
/// Default constructor for inheritance support.
/// </summary>
public AuditingRNGenerator(){}
/// <summary>
/// Main constructor sets up the Int32 internal generator and the Type T
/// oP-related items.
/// </summary>
/// <param name="seed">
/// Initial seed defining the "state" of the generator.
/// </param>
/// <param name="minValue">
/// Minimum (inclusive) value that Int32 numbers will take on.
/// </param>
/// <param name="maxValue">
/// Maximum (inclusive) value that Int32 numbers will take on.
/// </param>
/// <param name="oP">
/// The OP delegate to be used inside the generator.
/// </param>
/// <param name="initialOpState">
/// The initial state of the OP output.
/// </param>
/// <param name="historyOn">
/// <c>true</c> if the random number history and the OP history are to
/// be captured into the internal lists.
/// </param>
/// <remarks>
/// This constructor calls the bases to do most of the work.
/// </remarks>
public AuditingRNGenerator(Int32 seed, Int32 minValue, Int32 maxValue,
Op<T> oP, T initialOpState, System.Boolean historyOn)
: base(seed, minValue, maxValue, historyOn)
{
// Do the Type T specific stuff.
m_opDelegate = oP;
m_initialOpState = initialOpState;
if(m_historyOn) {
m_opHistory = new List<T>();
}
}
/// <summary>
/// Copy constructor. Makes a copy of the generator and initializes it to
/// it's initial state according to the original inititial state of the
/// original generator, not the CURRENT state of the original generator.
/// It also clears the lists (if any) and sets the oPstate to it's initial
/// value.
/// </summary>
/// <param name="generator">
/// The existing generator to make a copy of.
/// </param>
public AuditingRNGenerator(AuditingRNGenerator<T> generator)
: base(generator)
{
// Copy the Type T - specific stuff.
m_opDelegate = generator.m_opDelegate;
m_initialOpState = generator.m_initialOpState;
if(m_historyOn) {
m_opHistory = new List<T>();
}
}
#endregion // Constructors
#region Methods
/// <summary>
/// Gets the operation history.
/// </summary>
/// <returns>
/// A <See cref="System.Collections.Generic.List<Int32>"/> if history
/// was turned on in the constructor, otherwise <c>null</c>.
/// </returns>
public List<T> GetOperationHistory()
{
return m_opHistory;
}
/// <summary>
/// Gets the next integer from the internal RNG and accumulates it through
/// the OP.
/// </summary>
/// <returns>
/// The <see cref="System.Int32"/> value produced by the random number
/// generator.
/// </returns>
public virtual T Next()
{
// .Net's generator is exclusive on the high end (+1).
Int32 rn = m_rng.Next(m_minValue, m_maxValue + 1);
m_lastInt32Num = rn;
m_lastNum = ConvertToT(rn);
// If we have a delegate, this particular generator accumulates into
// its Opstate.
if(m_opDelegate != null)
m_opState = m_opDelegate(ref m_opState, m_lastNum);
// Store variables in the history if history is on.
if(m_historyOn) {
m_numHistory.Add(rn);
m_opHistory.Add(m_opState);
}
return m_lastNum;
}
/// <summary>
/// Creates a new copy of the generator with a given initial state.
/// </summary>
/// <returns>
/// An <see cref="IAuditingGenerator<T>"/> with the same parameters
/// as the parent generator (this), but a different internal state.
/// </returns>
public IAuditingGenerator<T> SpawnGenerator(Int32 initialState)
{
IAuditingGenerator<T> generator = new AuditingRNGenerator<T>(this);
generator.Reset(initialState);
return generator;
}
/// <summary>
/// Resets the generator and the states of everything and flushes history
/// lists if they are in use. We need an override to reset OP's etc.
/// </summary>
public override void Reset()
{
// Call base to do most things.
base.Reset();
// Add on our Type T - specific things.
m_opState = m_initialOpState;
if(m_historyOn) {
m_opHistory.Clear();
}
}
/// <summary>
/// Convert's the .Net's generator output (Int32's) into whatever the
/// Type T is.
/// </summary>
/// <returns>
/// The converted <see cref="System.Int32"/> value.
/// </returns>
/// <Exceptions>
/// <Exception>
/// An <see cref="ApplicationException"/> is generated with the message
/// "Can't convert random # to T in AuditingGenerator<T>" if the
/// conversion fails.
/// </Exception>
/// </Exceptions>
/// <remarks>
/// <para>
/// This simple method just calls <see cref="AuditingRandomGenerator.ConvertToAny"/>
/// interface method to do a conversion to another .Net Type. Override this
/// method if you need something more.
/// </para>
/// <para>
/// Note that this method is not specified in <see cref="IAuditingGenerator<T>"/>.
/// This is an example of a method that could support an inheriting Type that
/// wished to employ this additional functionality. This could be an Interface
/// inheriting from <see cref="IAuditingGenerator<T>"/>, a completely
/// different Interface or a class deriving from this class.
/// </para>
/// </remarks>
protected virtual T ConvertToT(Int32 int32Rn)
{
try {
// Do it with a regular conversion.
return (T)(ConvertToAny(int32Rn, m_typeType));
}
catch {
Type typeType = typeof(T);
// We can't really do much if we can't convert to T.
throw new ApplicationException("Can't convert random # to \"T\" ("
+ typeType.ToString() + ") in AuditingGenerator<T>");
}
}
#endregion // Methods
}
/// <summary>
/// A generator base class for generating random #s. The class is designed
/// to keep track of the result of operations that are taking place across
/// multiple locked Threads and compare the results using various synchronization
/// schemes with the "true" results. This class implements the common internal
/// bookeeping operations needed for any random generator which has a copy
/// of <see cref="System.Random"/> inside.
/// </summary>
public abstract class AuditingRandomGenerator
{
#region Class Fields
/// <summary>
/// For first time warning about conversions. A conversion failure in
/// ConvertNumber() does not generate an exception in this base class.
/// The first time a conversion failure occurs, however, we announce it
/// at the Console.
/// </summary>
protected bool m_firstTimeWarning = true;
/// <summary>
/// Seed from constructor.
/// </summary>
protected Int32 m_seed;
/// <summary>
/// The last number that was generated.
/// </summary>
protected Int32 m_lastInt32Num;
/// <summary>
/// The minimum value of the integer to deliver (inclusive)
/// </summary>
protected Int32 m_minValue;
/// <summary>
/// The maximum value of the integer to deliver (inclusive)
/// </summary>
protected Int32 m_maxValue;
/// <summary>
/// Contained .Net generator.
/// </summary>
protected Random m_rng;
/// <summary>
/// Tells if we are accumulating histories of the generator output and
/// the OP result output.
/// </summary>
protected System.Boolean m_historyOn;
/// <summary>
/// List of past RN's.
/// </summary>
protected List<Int32> m_numHistory;
#endregion // Class Fields
#region Constructors
/// <summary>
/// Default constructor for inheritance support.
/// </summary>
public AuditingRandomGenerator() { }
/// <summary>
/// Main constructor sets up the Int32 internal generator.
/// </summary>
/// <param name="seed">
/// Initial seed defining the "state" of the generator.
/// </param>
/// <param name="minValue">
/// Minimum (inclusive) value that Int32 numbers will take on.
/// </param>
/// <param name="maxValue">
/// Maximum (inclusive) value that Int32 numbers will take on.
/// </param>
/// <param name="historyOn">
/// <c>true</c> if the random number history is to be captured into the
/// internal list.
/// </param>
public AuditingRandomGenerator(Int32 seed, Int32 minValue, Int32 maxValue,
System.Boolean historyOn)
: this()
{
m_seed = seed;
m_lastInt32Num = seed;
m_minValue = minValue;
m_maxValue = maxValue;
m_rng = new Random(m_seed);
m_historyOn = historyOn;
if(m_historyOn) {
m_numHistory = new List<Int32>();
}
}
/// <summary>
/// Copy constructor. Makes a copy of the generator and initializes it to
/// it's initial state according to the original inititial state of the
/// original generator, not the CURRENT state of the original generator.
/// It also clears the list (if set).
/// </summary>
/// <param name="generator">
/// The existing generator to make a copy of.
/// </param>
public AuditingRandomGenerator(AuditingRandomGenerator generator)
: this()
{
m_seed = generator.m_seed;
m_lastInt32Num = generator.m_lastInt32Num;
m_minValue = generator.m_minValue;
m_maxValue = generator.m_maxValue;
m_rng = new Random(m_seed);
m_historyOn = generator.m_historyOn;
if(m_historyOn) {
m_numHistory = new List<Int32>();
}
}
#endregion // Constructors
#region Methods
/// <summary>
/// Gets a converted and boxed version of the last random number the
/// generator produced.
/// </summary>
/// <param name="outputType">
/// This is the Type that the Int32 number has to be converted to. It can
/// be a reference (class) or ValueType (primitive Type or custom struct).
/// Value Types are boxed.
/// </param>
/// <returns>
/// A reference to a box containing a class or ValueType. Note that this
/// reference must be <see cref="System.Object"/> and not not specialized
/// to <see cref="System.ValueType"/>, since we may wish to convert to
/// a C# class, not a boxed C# struct.
/// </returns>
public virtual System.Object ConvertNumber(Type outputType)
{
// Reference to what we've produced.
System.Object convertedNumber = null;
// We'll try this only if we know we might succeed. Problem is that T
// could be anything - we don't want to be Type and platform-specific
// by using our knowledge of what specific Types can be converted
// a-priori.
if(m_firstTimeWarning) {
// Do it with a regular conversion.
if((convertedNumber = ConvertToAny(m_lastInt32Num, outputType)) == null) {
// Give warning, but only first time through - we might only care about
// delays and collisions and we don't want to scroll to the screen.
// (Conversions are usually only used for data).
if(m_firstTimeWarning) {
Console.WriteLine("Can't convert random # to" +
outputType.ToString() + "in AuditingGenerator<T>");
m_firstTimeWarning = false;
}
}
}
return convertedNumber;
}
/// <summary>
/// Gets the random number generator history.
/// </summary>
/// <returns>
/// A <See cref="System.Collections.Generic.List<Int32>"/> if history
/// was turned on in the constructor, otherwise <c>null</c>.
/// </returns>
public List<Int32> GetNumberHistory()
{
return m_numHistory;
}
/// <summary>
/// Resets the generator and the states of everything and flushes history
/// lists if they are in use.
/// </summary>
public virtual void Reset()
{
m_rng = new Random(m_seed);
m_lastInt32Num = m_seed;
if(m_historyOn) {
m_numHistory.Clear();
}
}
/// <summary>
/// Resets the generator and the states of everything and flushes history
/// lists if they are in use. This version uses a parameter to initialize
/// the internal state of the generator.
/// </summary>
/// <param name="initialState">
/// This is a number that will set the internal initial state or "seed"
/// of the generator.
/// </param>
public virtual void Reset(Int32 initialState)
{
// Change the seed and call reset.
m_seed = initialState;
Reset();
}
/// <summary>
/// Convert's the .Net's generator output (Int32's) into any Type at all.
/// The Type is examined dynamically and can change from call to call.
/// This method will box primitive Types (anything the CLR's conversion
/// of Int32 will support) that are produced. The client should override
/// this method to convert to other Types of interest.
/// </summary>
/// <param name="int32Rn">
/// The integer number to be converted.
/// </param>
/// <param name="outputType">
/// The <see cref="System.Type"/> of the class or BOXed output struct
/// or primitive Type.
/// </param>
/// <returns>
/// The converted <see cref="System.Int32"/> value. A <c>null</c> value
/// is returned if the conversion cannot be accomplished.
/// </returns>
/// <remarks>
/// This simple method just uses Int32's IConvertible interface. Users
/// needing to convert to something more exotic need to override this.
/// </remarks>
protected static System.Object ConvertToAny(Int32 int32Rn, Type outputType)
{
// Don't convert if we don't need to.
if(outputType == typeof(Int32)) return int32Rn;
// Do it with a regular conversion.
try {
return ((IConvertible)int32Rn).ToType(outputType, null);
}
catch {
}
return null;
}
#endregion // Methods
}
#endregion // Random Number Generators
#endregion // Helper Classes
}