Fix Genesis scheduling and synchronization for up to 10 workers
This commit is contained in:
parent
52bd0d01ce
commit
684513ee59
@ -1,4 +1,4 @@
|
|||||||
///
|
///
|
||||||
/// Copyright (c) 2015-2019 Sensus Metering Systems
|
/// Copyright (c) 2015-2019 Sensus Metering Systems
|
||||||
/// Author: Milan Hanajík
|
/// Author: Milan Hanajík
|
||||||
///
|
///
|
||||||
@ -7,8 +7,44 @@ using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
|||||||
|
|
||||||
namespace TBF.Rig.TestMethods.GenesisCommunication
|
namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||||
{
|
{
|
||||||
|
/// <summary>Tracks one completion per worker, activity and group.</summary>
|
||||||
|
public sealed class GenesisWorkerGroupCompletion
|
||||||
|
{
|
||||||
|
private readonly int workerCount;
|
||||||
|
private readonly System.Collections.Generic.HashSet<int> completed = new System.Collections.Generic.HashSet<int>();
|
||||||
|
private int activity, group;
|
||||||
|
private bool released;
|
||||||
|
public GenesisWorkerGroupCompletion(int workerCount)
|
||||||
|
{
|
||||||
|
if (workerCount < 1 || workerCount > 10) throw new ArgumentOutOfRangeException(nameof(workerCount));
|
||||||
|
this.workerCount = workerCount;
|
||||||
|
}
|
||||||
|
public void Begin(int activityStep, int groupNumber)
|
||||||
|
{
|
||||||
|
lock (completed) { activity = activityStep; group = groupNumber; released = false; completed.Clear(); }
|
||||||
|
}
|
||||||
|
public bool Complete(int activityStep, int groupNumber, int worker)
|
||||||
|
{
|
||||||
|
lock (completed)
|
||||||
|
{
|
||||||
|
if (released || activityStep != activity || groupNumber != group || worker < 0 || worker >= workerCount) return false;
|
||||||
|
if (!completed.Add(worker) || completed.Count != workerCount) return false;
|
||||||
|
released = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static System.Collections.Generic.IEnumerable<int> BoardIndexes(int worker, int workers, int boards)
|
||||||
|
{
|
||||||
|
if (workers < 1 || workers > 10 || worker < 0 || worker >= workers || boards < 0) throw new ArgumentOutOfRangeException();
|
||||||
|
for (int index = worker; index < boards; index += workers) yield return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class CommCompletedEventArgs : EventArgs
|
public class CommCompletedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public bool WorkerGroupCompleted;
|
||||||
|
public int ActivityStep;
|
||||||
|
public int Group;
|
||||||
public int ThreadId;
|
public int ThreadId;
|
||||||
public int WMNr0; /// 0-based water meter position
|
public int WMNr0; /// 0-based water meter position
|
||||||
public GenesisSmartReader Ihead;
|
public GenesisSmartReader Ihead;
|
||||||
|
|||||||
@ -204,14 +204,14 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
static IList<iPerlCommunicationParams> multiTestParams;
|
static IList<iPerlCommunicationParams> multiTestParams;
|
||||||
|
|
||||||
|
|
||||||
static int currentActivityStep;
|
static volatile int currentActivityStep;
|
||||||
static int currentGroup; /// form -> worker thread (0 = none)
|
static volatile int currentGroup; /// form -> worker thread (0 = none)
|
||||||
static int lastGroup;
|
static int lastGroup;
|
||||||
static int completedCommCount; /// Number of completed communication steps
|
private GenesisWorkerGroupCompletion groupCompletion;
|
||||||
|
|
||||||
static IList<Thread> workerThreads;
|
static IList<Thread> workerThreads;
|
||||||
static IList<int> muxBrdOrGroup14Nrs;
|
static IList<int> muxBrdOrGroup14Nrs;
|
||||||
static bool stopWorkerThreads; /// form -> worker thread
|
static volatile bool stopWorkerThreads; /// form -> worker thread
|
||||||
|
|
||||||
|
|
||||||
/// <summary> Parameterless constructor (without watermeters, threads) </summary>
|
/// <summary> Parameterless constructor (without watermeters, threads) </summary>
|
||||||
@ -384,7 +384,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
///
|
///
|
||||||
currentActivityStep = 0;
|
currentActivityStep = 0;
|
||||||
currentGroup = 0;
|
currentGroup = 0;
|
||||||
completedCommCount = 0;
|
|
||||||
stopWorkerThreads = false;
|
stopWorkerThreads = false;
|
||||||
|
|
||||||
/// group numbers are >=1, lastGroup == 0 means there is no group
|
/// group numbers are >=1, lastGroup == 0 means there is no group
|
||||||
@ -409,6 +408,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
if (!muxBrdOrGroup14Nrs.Contains(iPerl.MuxBoardNrOrGroup14)) muxBrdOrGroup14Nrs.Add(iPerl.MuxBoardNrOrGroup14);
|
if (!muxBrdOrGroup14Nrs.Contains(iPerl.MuxBoardNrOrGroup14)) muxBrdOrGroup14Nrs.Add(iPerl.MuxBoardNrOrGroup14);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groupCompletion = new GenesisWorkerGroupCompletion(workerThreads.Count);
|
||||||
|
groupCompletion.Begin(0, 1);
|
||||||
|
log.InfoFormat("Genesis workers configured: Threads={0}, Group1Count={1}, LastGroup2={2}", workerThreads.Count, muxBrdOrGroup14Nrs.Count, lastGroup);
|
||||||
log.WarnFormat("nrThreads = {0}", workerThreads.Count);
|
log.WarnFormat("nrThreads = {0}", workerThreads.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,6 +668,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
|
|
||||||
for (int i = 0; i < multiTestParams.Count; i++ )
|
for (int i = 0; i < multiTestParams.Count; i++ )
|
||||||
{
|
{
|
||||||
|
while (activityStep != currentActivityStep && !stopWorkerThreads) Thread.Sleep(50);
|
||||||
|
if (stopWorkerThreads) break;
|
||||||
Test currentTest = tests[i];
|
Test currentTest = tests[i];
|
||||||
iPerlCommunicationParams currentTestParams = multiTestParams[i];
|
iPerlCommunicationParams currentTestParams = multiTestParams[i];
|
||||||
string currentActivity = currentTestParams.Activity; /// Current activity
|
string currentActivity = currentTestParams.Activity; /// Current activity
|
||||||
@ -707,7 +711,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
}
|
}
|
||||||
|
|
||||||
//HOLD ON - if activity is like HoldSlotStr, ignore loop and do activity like HoldSlotStr
|
//HOLD ON - if activity is like HoldSlotStr, ignore loop and do activity like HoldSlotStr
|
||||||
if (currentActivity.ToLower().Equals(HoldSlotStr.ToLower()))
|
bool isHoldActivity = currentActivity.ToLower().Equals(HoldSlotStr.ToLower());
|
||||||
|
if (isHoldActivity)
|
||||||
{
|
{
|
||||||
|
|
||||||
log.Debug($"Activity '{currentActivity}' is HoldSlotStr, skipping loop and performing HoldOn operation. Thread: {threadID}");
|
log.Debug($"Activity '{currentActivity}' is HoldSlotStr, skipping loop and performing HoldOn operation. Thread: {threadID}");
|
||||||
@ -716,7 +721,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
string resultStr = string.Empty;
|
string resultStr = string.Empty;
|
||||||
error = HoldOn(threadID, ref resultStr); // wait for the hold on to complete - one time for each thread
|
error = HoldOn(threadID, ref resultStr); // wait for the hold on to complete - one time for each thread
|
||||||
ProcessWaterMetersDialogConfirmation(threadID, resultStr,error);
|
ProcessWaterMetersDialogConfirmation(threadID, resultStr,error);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int group = 1; group <= lastGroup; group++)
|
for (int group = 1; group <= lastGroup; group++)
|
||||||
@ -729,10 +733,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
|
|
||||||
if (stopWorkerThreads) break;
|
if (stopWorkerThreads) break;
|
||||||
|
|
||||||
|
if (!isHoldActivity)
|
||||||
|
{
|
||||||
#if TURA_SPECIAL
|
#if TURA_SPECIAL
|
||||||
int threadIx = threadID; /// Just one thread for TURA_SPECIAL
|
int threadIx = threadID; /// Just one thread for TURA_SPECIAL
|
||||||
#else
|
#else
|
||||||
for (int threadIx = threadID; threadIx < threadID + 10; threadIx += cfg.NrThreads)// - max threads = 4 => 10
|
foreach (int threadIx in GenesisWorkerGroupCompletion.BoardIndexes(threadID, workerThreads.Count, muxBrdOrGroup14Nrs.Count))
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
bool wmFound = false;
|
bool wmFound = false;
|
||||||
@ -884,8 +890,13 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
if (stopWorkerThreads) break;
|
if (stopWorkerThreads) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopWorkerThreads) break;
|
}
|
||||||
} /// for (int group
|
|
||||||
|
if (stopWorkerThreads) break;
|
||||||
|
log.DebugFormat("Genesis worker group completed: Activity={0}, Group2={1}, Worker={2}", activityStep, group, threadID);
|
||||||
|
OnCommCompleted(null, new CommCompletedEventArgs(threadID, -1, null, null, string.Empty, CommErr.None)
|
||||||
|
{ WorkerGroupCompleted = true, ActivityStep = activityStep, Group = group });
|
||||||
|
} /// for (int group
|
||||||
|
|
||||||
TBF.UiBridge.Bridge.OnTestProgress(null, new TBF.UiBridge.TestProgressEventArgs(tests[i], Progress.Completed));
|
TBF.UiBridge.Bridge.OnTestProgress(null, new TBF.UiBridge.TestProgressEventArgs(tests[i], Progress.Completed));
|
||||||
activityStep++;
|
activityStep++;
|
||||||
@ -1608,21 +1619,17 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !TURA_SPECIAL
|
// Per-meter results update the UI only. Advance after every worker
|
||||||
///
|
// has finished all of its Group 1 assignments for this Group 2.
|
||||||
/// Branch
|
if (!data.WorkerGroupCompleted || !groupCompletion.Complete(data.ActivityStep, data.Group, data.ThreadId)) return;
|
||||||
///
|
log.InfoFormat("Genesis group completed: Activity={0}, Group2={1}, Workers={2}", data.ActivityStep, data.Group, workerThreads.Count);
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
if (++completedCommCount < 4) return;
|
|
||||||
completedCommCount = 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (currentGroup < lastGroup)
|
if (currentGroup < lastGroup)
|
||||||
{
|
{
|
||||||
/// Go to the next step / next group
|
/// Go to the next step / next group
|
||||||
currentGroup++;
|
currentGroup++;
|
||||||
|
groupCompletion.Begin(currentActivityStep, currentGroup);
|
||||||
}
|
}
|
||||||
else if (currentActivityStep + 1 < multiTestParams.Count)
|
else if (currentActivityStep + 1 < multiTestParams.Count)
|
||||||
{
|
{
|
||||||
@ -1630,16 +1637,17 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
|||||||
currentActivityStep++;
|
currentActivityStep++;
|
||||||
activityLabel.Text = multiTestParams[currentActivityStep].Activity;
|
activityLabel.Text = multiTestParams[currentActivityStep].Activity;
|
||||||
currentGroup++;
|
currentGroup++;
|
||||||
|
groupCompletion.Begin(currentActivityStep, currentGroup);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log.Debug("Wait for finish workerThreads 4 seconds");
|
log.Debug("Waiting for all Genesis workers; results remain visible for 4 seconds afterwards.");
|
||||||
|
|
||||||
Thread lastThread = workerThreads[data.ThreadId];
|
Thread[] finishingThreads = workerThreads.ToArray();
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
lastThread.Join(2000);
|
foreach (var thread in finishingThreads) thread.Join();
|
||||||
|
|
||||||
if (!CanUseUi()) return;
|
if (!CanUseUi()) return;
|
||||||
|
|
||||||
|
|||||||
213
TBFTests/GenesisParallelSchedulingTests.cs
Normal file
213
TBFTests/GenesisParallelSchedulingTests.cs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.TestMethods.GenesisCommunication;
|
||||||
|
|
||||||
|
namespace TBFTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
[TestCategory("GenesisParallelScheduling")]
|
||||||
|
public class GenesisParallelSchedulingTests
|
||||||
|
{
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
|
||||||
|
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
|
||||||
|
public void EveryBoardIsAssignedExactlyOnceForAllSupportedWorkerCounts(int workers)
|
||||||
|
{
|
||||||
|
foreach (int boards in new[] { 0, 1, 2, 7, 10 })
|
||||||
|
{
|
||||||
|
var actual = Enumerable.Range(0, workers)
|
||||||
|
.SelectMany(worker => GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards)).OrderBy(x => x).ToArray();
|
||||||
|
CollectionAssert.AreEqual(Enumerable.Range(0, boards).ToArray(), actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
|
||||||
|
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
|
||||||
|
public void GroupAdvancesOnlyAfterEveryDistinctWorkerFinishes(int count)
|
||||||
|
{
|
||||||
|
var completion = new GenesisWorkerGroupCompletion(count);
|
||||||
|
completion.Begin(0, 1);
|
||||||
|
Assert.IsFalse(completion.Complete(0, 2, 0));
|
||||||
|
Assert.IsFalse(completion.Complete(1, 1, 0));
|
||||||
|
Assert.IsFalse(completion.Complete(0, 1, -1));
|
||||||
|
Assert.IsFalse(completion.Complete(0, 1, count));
|
||||||
|
for (int worker = 0; worker < count; worker++)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(worker == count - 1, completion.Complete(0, 1, worker));
|
||||||
|
Assert.IsFalse(completion.Complete(0, 1, worker), "Duplicate completion must not advance the group.");
|
||||||
|
}
|
||||||
|
completion.Begin(0, 2);
|
||||||
|
Assert.IsFalse(completion.Complete(0, 1, 0), "Late event from the previous group.");
|
||||||
|
for (int worker = count - 1; worker >= 0; worker--)
|
||||||
|
Assert.AreEqual(worker == 0, completion.Complete(0, 2, worker));
|
||||||
|
completion.Begin(1, 1);
|
||||||
|
Assert.IsFalse(completion.Complete(0, 2, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MultipleActivitiesAndGroupsRequireAllWorkersIncludingIdleWorkers()
|
||||||
|
{
|
||||||
|
var completion = new GenesisWorkerGroupCompletion(10);
|
||||||
|
for (int activity = 0; activity < 3; activity++)
|
||||||
|
for (int group = 1; group <= 10; group++)
|
||||||
|
{
|
||||||
|
completion.Begin(activity, group);
|
||||||
|
Assert.IsFalse(completion.Complete(activity - 1, group, 0));
|
||||||
|
Assert.IsFalse(completion.Complete(activity, group - 1, 0));
|
||||||
|
// Also models HOLD ON: no board requests, but each worker must finish.
|
||||||
|
for (int worker = 0; worker < 10; worker++)
|
||||||
|
Assert.AreEqual(worker == 9, completion.Complete(activity, group, worker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TenWorkersCanRunConcurrentlyAndSlowTenthWorkerHoldsGroup()
|
||||||
|
{
|
||||||
|
var completion = new GenesisWorkerGroupCompletion(10);
|
||||||
|
completion.Begin(0, 1);
|
||||||
|
using (var started = new CountdownEvent(10))
|
||||||
|
using (var firstNine = new CountdownEvent(9))
|
||||||
|
using (var run = new ManualResetEventSlim(false))
|
||||||
|
using (var slow = new ManualResetEventSlim(false))
|
||||||
|
{
|
||||||
|
int advances = 0;
|
||||||
|
var threads = Enumerable.Range(0, 10).Select(worker => new Thread(() =>
|
||||||
|
{
|
||||||
|
started.Signal();
|
||||||
|
run.Wait();
|
||||||
|
if (worker == 9) slow.Wait();
|
||||||
|
if (completion.Complete(0, 1, worker)) Interlocked.Increment(ref advances);
|
||||||
|
if (worker != 9) firstNine.Signal();
|
||||||
|
}) { IsBackground = true }).ToArray();
|
||||||
|
foreach (var thread in threads) thread.Start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Assert.IsTrue(started.Wait(5000), "All ten workers must start before any finishes.");
|
||||||
|
run.Set();
|
||||||
|
Assert.IsTrue(firstNine.Wait(5000));
|
||||||
|
Assert.AreEqual(0, Volatile.Read(ref advances), "Nine completions must not release a ten-worker group.");
|
||||||
|
slow.Set();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
run.Set(); slow.Set();
|
||||||
|
foreach (var thread in threads) thread.Join(5000);
|
||||||
|
}
|
||||||
|
Assert.AreEqual(1, advances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Parallel_Group1OneToTen_Group2One_AllTenCallsOverlap()
|
||||||
|
{
|
||||||
|
VerifyProcessingScenario(10, 10, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Serial_Group1One_Group2OneToTen_NoCallsOverlap()
|
||||||
|
{
|
||||||
|
VerifyProcessingScenario(10, 1, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Combined_FiveGroup1Boards_TwoGroup2Groups_ParallelWithinSerialBetween()
|
||||||
|
{
|
||||||
|
VerifyProcessingScenario(10, 5, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Combined_FourWorkers_TenBoards_ThreeGroups_EveryMeterRunsOnce()
|
||||||
|
{
|
||||||
|
VerifyProcessingScenario(4, 10, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Serial_OneWorker_TenGroup1Boards_AllCallsRunOnce()
|
||||||
|
{
|
||||||
|
VerifyProcessingScenario(1, 10, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardware-free harness using the production assignment and completion helpers.
|
||||||
|
// Calls are held at a rendezvous, so overlap is proven without timing guesses.
|
||||||
|
private static void VerifyProcessingScenario(int workers, int boards, int groups)
|
||||||
|
{
|
||||||
|
var completion = new GenesisWorkerGroupCompletion(workers);
|
||||||
|
completion.Begin(0, 1);
|
||||||
|
var sync = new object();
|
||||||
|
int currentGroup = 1, active = 0, advances = 0;
|
||||||
|
bool abort = false;
|
||||||
|
var calls = new int[groups, boards];
|
||||||
|
var finished = new int[groups];
|
||||||
|
var peaks = new int[groups];
|
||||||
|
var failures = new System.Collections.Generic.List<Exception>();
|
||||||
|
int parallelism = Math.Min(workers, boards);
|
||||||
|
var rendezvous = Enumerable.Range(0, groups).Select(_ => new CountdownEvent(parallelism)).ToArray();
|
||||||
|
var threads = Enumerable.Range(0, workers).Select(worker => new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int group = 1; group <= groups; group++)
|
||||||
|
{
|
||||||
|
lock (sync)
|
||||||
|
{
|
||||||
|
while (currentGroup != group && !abort)
|
||||||
|
if (!Monitor.Wait(sync, 10000)) throw new TimeoutException("Group did not advance.");
|
||||||
|
if (abort) return;
|
||||||
|
}
|
||||||
|
bool firstCall = true;
|
||||||
|
foreach (int board in GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards))
|
||||||
|
{
|
||||||
|
lock (sync)
|
||||||
|
{
|
||||||
|
for (int previous = 0; previous < group - 1; previous++)
|
||||||
|
Assert.AreEqual(boards, finished[previous], "Next Group 2 started before previous group completed.");
|
||||||
|
calls[group - 1, board]++;
|
||||||
|
active++;
|
||||||
|
peaks[group - 1] = Math.Max(peaks[group - 1], active);
|
||||||
|
}
|
||||||
|
if (firstCall)
|
||||||
|
{
|
||||||
|
rendezvous[group - 1].Signal();
|
||||||
|
Assert.IsTrue(rendezvous[group - 1].Wait(10000), "Assigned workers did not enter calls concurrently.");
|
||||||
|
firstCall = false;
|
||||||
|
}
|
||||||
|
lock (sync) { active--; finished[group - 1]++; }
|
||||||
|
}
|
||||||
|
if (completion.Complete(0, group, worker))
|
||||||
|
{
|
||||||
|
lock (sync)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(boards, finished[group - 1]);
|
||||||
|
Assert.AreEqual(0, active);
|
||||||
|
advances++;
|
||||||
|
completion.Begin(0, group + 1);
|
||||||
|
currentGroup++;
|
||||||
|
Monitor.PulseAll(sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
lock (sync) { failures.Add(error); abort = true; Monitor.PulseAll(sync); }
|
||||||
|
}
|
||||||
|
}) { IsBackground = true }).ToArray();
|
||||||
|
foreach (var thread in threads) thread.Start();
|
||||||
|
bool allJoined = true;
|
||||||
|
foreach (var thread in threads) allJoined &= thread.Join(15000);
|
||||||
|
Assert.IsTrue(allJoined, "Workers did not terminate.");
|
||||||
|
foreach (var item in rendezvous) item.Dispose();
|
||||||
|
Assert.AreEqual(0, failures.Count, string.Join("\n", failures.Select(x => x.ToString())));
|
||||||
|
Assert.AreEqual(groups, advances);
|
||||||
|
for (int group = 0; group < groups; group++)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(parallelism, peaks[group], "Unexpected maximum concurrent calls.");
|
||||||
|
for (int board = 0; board < boards; board++)
|
||||||
|
Assert.AreEqual(1, calls[group, board], "Meter was skipped or called more than once.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -231,7 +231,7 @@
|
|||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /></ItemGroup>
|
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /><Compile Include="GenesisParallelSchedulingTests.cs" /></ItemGroup>
|
||||||
<!-- Project dependencies may copy an older SQLite interop DLL with a newer timestamp. -->
|
<!-- Project dependencies may copy an older SQLite interop DLL with a newer timestamp. -->
|
||||||
<Target Name="EnsureMatchingSQLiteInterop" AfterTargets="Build">
|
<Target Name="EnsureMatchingSQLiteInterop" AfterTargets="Build">
|
||||||
<Copy SourceFiles="@(SQLiteInteropFiles)"
|
<Copy SourceFiles="@(SQLiteInteropFiles)"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user