WorkShift
The dispatcher needs to do different things at different,
non-overlapped times. For example, non-parallel tests may
not be run at the same time as parallel tests. We model
this using the metaphor of a working shift. The WorkShift
class associates one or more WorkItemQueues with one or
more TestWorkers.
Work in the queues is processed until all queues are empty
and all workers are idle. Both tests are needed because a
worker that is busy may end up adding more work to one of
the queues. At that point, the shift is over and another
shift may begin. This cycle continues until all the tests
have been run.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace NUnit.Framework.Internal.Execution
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public class WorkShift
{
private static readonly Logger Log = InternalTrace.GetLogger("WorkShift");
private readonly object _syncRoot = new object();
private int _busyCount;
private bool _firstStart = true;
public string Name { get; }
public bool IsActive { get; set; }
public bool HasWork {
get {
foreach (WorkItemQueue queue in Queues) {
if (!queue.IsEmpty)
return true;
}
return false;
}
}
internal IList<WorkItemQueue> Queues { get; } = new List<WorkItemQueue>();
internal IList<TestWorker> Workers { get; } = new List<TestWorker>();
[System.Runtime.CompilerServices.Nullable(2)]
[method: System.Runtime.CompilerServices.NullableContext(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
public event ShiftChangeEventHandler EndOfShift;
public WorkShift(string name)
{
Name = name;
IsActive = false;
}
public void AddQueue(WorkItemQueue queue)
{
Log.Debug("{0} shift adding queue {1}", Name, queue.Name);
Queues.Add(queue);
if (IsActive)
queue.Start();
}
public void Assign(TestWorker worker)
{
Log.Debug("{0} shift assigned worker {1}", Name, worker.Name);
Workers.Add(worker);
}
public void Start()
{
Log.Info("{0} shift starting", Name);
IsActive = true;
if (_firstStart) {
_firstStart = false;
StartWorkers();
}
foreach (WorkItemQueue queue in Queues) {
queue.Start();
}
}
private void StartWorkers()
{
foreach (TestWorker worker in Workers) {
worker.Busy += delegate {
Interlocked.Increment(ref _busyCount);
};
worker.Idle += delegate {
if (Interlocked.Decrement(ref _busyCount) == 0 && !HasWork) {
lock (_syncRoot) {
if (_busyCount == 0 && !HasWork)
EndShift();
}
}
};
worker.Start();
}
}
public void EndShift()
{
Log.Info("{0} shift ending", Name);
IsActive = false;
foreach (WorkItemQueue queue in Queues) {
queue.Pause();
}
this.EndOfShift?.Invoke(this);
}
public void ShutDown()
{
IsActive = false;
foreach (WorkItemQueue queue in Queues) {
queue.Stop();
}
}
public void Cancel(bool force)
{
if (force)
IsActive = false;
foreach (TestWorker worker in Workers) {
worker.Cancel(force);
}
}
}
}