//******************************************************************************************************
// ThreadContainerBase.cs - Gbtc
//
// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 03/08/2014 - Steven E. Chisholm
// Generated original version of source code.
//
//******************************************************************************************************
//------------------------------------------------------------------------------------------------------
// WARNING: This class contains very low-level logic and is optimized to have minimal locking. Before
// making any changes, be sure to consult the author as any bugs can introduce a race
// condition that will be very difficult to detect and fix. Additional desired functionality
// should likely result in another class being created rather than modifying this one.
//------------------------------------------------------------------------------------------------------
using System;
using System.Runtime.CompilerServices;
using System.Threading;
#pragma warning disable 420
namespace GSF.Threading
{
internal abstract class ThreadContainerBase
{
public class CallbackArgs
{
public bool ShouldDispose;
///
/// Gets if StartDisposal() method is the only item that triggered this run.
///
public bool StartDisposalCallSuccessful;
public void Clear()
{
ShouldDispose = false;
StartDisposalCallSuccessful = false;
}
}
///
/// State variables for the internal state machine.
///
private static class State
{
///
/// Indicates that the task is not running.
///
public const int NotRunning = 1;
///
/// Indicates that the task is scheduled to execute after a user specified delay
///
public const int ScheduledToRunAfterDelay = 2;
///
/// Indicates the task has been queue for immediate execution, but has not started running yet.
///
public const int ScheduledToRun = 3;
///
/// Once in a running state, only the worker thread can change its state.
///
public const int Running = 4;
///
/// Once reaching this state, the effect of RunAgain being set will no longer be valid.
///
public const int AfterRunning = 5;
///
/// A disposed state
///
public const int Disposed = 6;
public const int Invalid = -1;
}
private volatile bool m_runAgain;
///
/// A value less than 0 means false.
///
private volatile int m_runAgainAfterDelay;
private volatile int m_state;
private bool m_disposeOnShutdown;
private readonly WeakAction m_callback;
private readonly WeakAction m_disposeAndWaitCallback;
private CallbackArgs m_args;
private volatile bool m_startDisposalCallSuccessful = false;
private bool m_ignoreShutdownHandler;
protected ThreadContainerBase(Action callback, Action disposeAndWaitCallback, bool disposeOnShutdown)
{
m_disposeOnShutdown = disposeOnShutdown;
m_runAgain = false;
m_runAgainAfterDelay = -1;
m_args = new CallbackArgs();
m_args.Clear();
m_callback = new WeakAction(callback);
m_disposeAndWaitCallback = new WeakAction(disposeAndWaitCallback);
m_state = State.NotRunning;
}
protected void OnRunning()
{
SpinWait wait = new SpinWait();
while (true)
{
int state = m_state;
if (state == State.ScheduledToRun && Interlocked.CompareExchange(ref m_state, State.Running, State.ScheduledToRun) == State.ScheduledToRun)
break;
if (state == State.ScheduledToRunAfterDelay && Interlocked.CompareExchange(ref m_state, State.Running, State.ScheduledToRunAfterDelay) == State.ScheduledToRunAfterDelay)
break;
wait.SpinOnce();
}
wait.Reset();
m_runAgain = false;
m_runAgainAfterDelay = -1;
Thread.MemoryBarrier();
m_args.StartDisposalCallSuccessful = m_startDisposalCallSuccessful;
bool failedRun = !m_callback.TryInvoke(m_args);
if (m_args.ShouldDispose || failedRun)
{
InternalDispose_FromWorkerThread();
Interlocked.Exchange(ref m_state, State.Disposed);
return;
}
Interlocked.Exchange(ref m_state, State.AfterRunning); //Notifies that the RunAgain and RunAgainAfterDelay variables are going to be used
// to make decisions. Therefore, if setting these variables after this point, modifying the state machine will be
// necessary
if (m_runAgain)
{
Interlocked.Exchange(ref m_state, State.ScheduledToRun);
InternalStart_FromWorkerThread();
}
else if (m_runAgainAfterDelay >= 0)
{
InternalStart_FromWorkerThread(m_runAgainAfterDelay);
Interlocked.Exchange(ref m_state, State.ScheduledToRunAfterDelay);
}
else
{
InternalDoNothing_FromWorkerThread();
Interlocked.Exchange(ref m_state, State.NotRunning);
}
}
///
/// Same as Start() except notifies on the callback during a race condition that this is the one that was first to schedule the task.
///
///
public void StartDisposal()
{
m_runAgain = true;
SpinWait wait = new SpinWait();
while (true)
{
int state = Interlocked.CompareExchange(ref m_state, 0, 0);
switch (state)
{
case State.Disposed:
case State.ScheduledToRun:
case State.Running:
return;
case State.NotRunning:
if (Interlocked.CompareExchange(ref m_state, State.Invalid, State.NotRunning) == State.NotRunning)
{
m_startDisposalCallSuccessful = true;
InternalStart();
Interlocked.Exchange(ref m_state, State.ScheduledToRun);
return;
}
break;
case State.ScheduledToRunAfterDelay:
if (Interlocked.CompareExchange(ref m_state, State.Invalid, State.ScheduledToRunAfterDelay) == State.ScheduledToRunAfterDelay)
{
InternalCancelTimer();
Interlocked.Exchange(ref m_state, State.ScheduledToRun);
return;
}
break;
case State.AfterRunning:
case State.Invalid:
wait.SpinOnce();
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start()
{
if (!m_runAgain)
StartSlower();
}
void StartSlower()
{
m_runAgain = true;
SpinWait wait = new SpinWait();
while (true)
{
int state = Interlocked.CompareExchange(ref m_state, 0, 0);
switch (state)
{
case State.Disposed:
case State.ScheduledToRun:
case State.Running:
return;
case State.NotRunning:
if (Interlocked.CompareExchange(ref m_state, State.Invalid, State.NotRunning) == State.NotRunning)
{
InternalStart();
Interlocked.Exchange(ref m_state, State.ScheduledToRun);
return;
}
break;
case State.ScheduledToRunAfterDelay:
if (Interlocked.CompareExchange(ref m_state, State.Invalid, State.ScheduledToRunAfterDelay) == State.ScheduledToRunAfterDelay)
{
InternalCancelTimer();
Interlocked.Exchange(ref m_state, State.ScheduledToRun);
return;
}
break;
case State.AfterRunning:
case State.Invalid:
wait.SpinOnce();
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start(int delay)
{
if (m_runAgainAfterDelay >= 0)
return;
StartSlower(delay);
}
void StartSlower(int delay)
{
if (delay < 0)
throw new ArgumentException("Cannot be less than zero", nameof(delay));
SpinWait wait = new SpinWait();
m_runAgainAfterDelay = delay;
while (true)
{
int state = Interlocked.CompareExchange(ref m_state, 0, 0);
switch (state)
{
case State.Disposed:
case State.ScheduledToRun:
case State.Running:
case State.ScheduledToRunAfterDelay:
return;
case State.NotRunning:
if (Interlocked.CompareExchange(ref m_state, State.Invalid, State.NotRunning) == State.NotRunning)
{
InternalStart(delay);
Interlocked.Exchange(ref m_state, State.ScheduledToRunAfterDelay);
}
break;
case State.AfterRunning:
case State.Invalid:
wait.SpinOnce();
break;
}
}
}
///
/// For foreground threads, a shutdown handler is registered to dispose of the Thread so it doesn't keep the process running.
/// However, for the Logger, shutting down this thread will prevent shutdown messages from showing up in the logger.
/// By calling this method, it declares that the coder will dispose of this class when it is finished and does not want the
/// Shutdown handler to do it.
///
internal void IgnoreShutdownEvent()
{
m_ignoreShutdownHandler = true;
}
protected void Shutdown()
{
if (m_ignoreShutdownHandler)
return;
//If the caller did not want to be notified on shutdown
// or they did but the callback failed.
// clear all callbacks and initiate a disposal.
if (!m_disposeOnShutdown && !m_disposeAndWaitCallback.TryInvoke())
{
m_callback.Clear();
m_disposeAndWaitCallback.Clear();
StartDisposal();
}
}
protected abstract void InternalStart_FromWorkerThread();
protected abstract void InternalStart_FromWorkerThread(int delay);
protected abstract void InternalDispose_FromWorkerThread();
protected abstract void InternalDoNothing_FromWorkerThread();
protected abstract void InternalStart();
protected abstract void InternalStart(int delay);
protected abstract void InternalCancelTimer();
}
}