|
|
using System;
|
|
|
|
|
|
namespace Implab.Components {
|
|
|
public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable {
|
|
|
enum Commands {
|
|
|
Ok = 0,
|
|
|
Fail,
|
|
|
Init,
|
|
|
Start,
|
|
|
Stop,
|
|
|
Dispose,
|
|
|
Last = Dispose
|
|
|
}
|
|
|
|
|
|
class StateMachine {
|
|
|
static readonly ExecutionState[,] _transitions;
|
|
|
|
|
|
static StateMachine() {
|
|
|
_transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
|
|
|
|
|
|
Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init);
|
|
|
Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose);
|
|
|
|
|
|
Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok);
|
|
|
Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail);
|
|
|
|
|
|
Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start);
|
|
|
Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose);
|
|
|
|
|
|
Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok);
|
|
|
Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail);
|
|
|
Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop);
|
|
|
Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose);
|
|
|
|
|
|
Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail);
|
|
|
Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop);
|
|
|
Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose);
|
|
|
|
|
|
Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail);
|
|
|
Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
|
|
|
|
|
|
Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose);
|
|
|
}
|
|
|
|
|
|
static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
|
|
|
_transitions[(int)s1, (int)cmd] = s2;
|
|
|
}
|
|
|
|
|
|
public ExecutionState State {
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public StateMachine(ExecutionState initial) {
|
|
|
State = initial;
|
|
|
}
|
|
|
|
|
|
public bool Move(Commands cmd) {
|
|
|
var next = _transitions[(int)State, (int)cmd];
|
|
|
if (next == ExecutionState.Undefined)
|
|
|
return false;
|
|
|
State = next;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
IPromise m_pending;
|
|
|
Exception m_lastError;
|
|
|
|
|
|
readonly StateMachine m_stateMachine;
|
|
|
|
|
|
protected RunnableComponent(bool initialized) {
|
|
|
m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created);
|
|
|
}
|
|
|
|
|
|
protected virtual int DisposeTimeout {
|
|
|
get {
|
|
|
return 10000;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void ThrowInvalidCommand(Commands cmd) {
|
|
|
if (m_stateMachine.State == ExecutionState.Disposed)
|
|
|
throw new ObjectDisposedException(ToString());
|
|
|
|
|
|
throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State));
|
|
|
}
|
|
|
|
|
|
void Move(Commands cmd) {
|
|
|
if (!m_stateMachine.Move(cmd))
|
|
|
ThrowInvalidCommand(cmd);
|
|
|
}
|
|
|
|
|
|
void Invoke(Commands cmd, Action action) {
|
|
|
lock (m_stateMachine)
|
|
|
Move(cmd);
|
|
|
|
|
|
try {
|
|
|
action();
|
|
|
lock(m_stateMachine)
|
|
|
Move(Commands.Ok);
|
|
|
|
|
|
} catch (Exception err) {
|
|
|
lock (m_stateMachine) {
|
|
|
Move(Commands.Fail);
|
|
|
m_lastError = err;
|
|
|
}
|
|
|
throw;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) {
|
|
|
IPromise promise = null;
|
|
|
IPromise prev;
|
|
|
|
|
|
var task = new ActionChainTask(action, null, null, true);
|
|
|
|
|
|
lock (m_stateMachine) {
|
|
|
Move(cmd);
|
|
|
|
|
|
prev = m_pending;
|
|
|
|
|
|
promise = task.Then(
|
|
|
() => {
|
|
|
lock(m_stateMachine) {
|
|
|
if (m_pending == promise) {
|
|
|
Move(Commands.Ok);
|
|
|
m_pending = null;
|
|
|
}
|
|
|
}
|
|
|
}, e => {
|
|
|
lock(m_stateMachine) {
|
|
|
if (m_pending == promise) {
|
|
|
Move(Commands.Fail);
|
|
|
m_pending = null;
|
|
|
m_lastError = e;
|
|
|
}
|
|
|
}
|
|
|
throw new PromiseTransientException(e);
|
|
|
}
|
|
|
);
|
|
|
|
|
|
m_pending = promise;
|
|
|
}
|
|
|
|
|
|
if (prev == null)
|
|
|
task.Resolve();
|
|
|
else
|
|
|
chain(prev, task);
|
|
|
|
|
|
return promise;
|
|
|
}
|
|
|
|
|
|
|
|
|
#region IInitializable implementation
|
|
|
|
|
|
public void Init() {
|
|
|
Invoke(Commands.Init, OnInitialize);
|
|
|
}
|
|
|
|
|
|
protected virtual void OnInitialize() {
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region IRunnable implementation
|
|
|
|
|
|
public IPromise Start() {
|
|
|
return InvokeAsync(Commands.Start, OnStart, null);
|
|
|
}
|
|
|
|
|
|
protected virtual IPromise OnStart() {
|
|
|
return Promise.SUCCESS;
|
|
|
}
|
|
|
|
|
|
public IPromise Stop() {
|
|
|
return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose);
|
|
|
}
|
|
|
|
|
|
protected virtual IPromise OnStop() {
|
|
|
return Promise.SUCCESS;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Stops the current operation if one exists.
|
|
|
/// </summary>
|
|
|
/// <param name="current">Current.</param>
|
|
|
/// <param name="stop">Stop.</param>
|
|
|
protected virtual void StopPending(IPromise current, IDeferred stop) {
|
|
|
if (current == null) {
|
|
|
stop.Resolve();
|
|
|
} else {
|
|
|
// связваем текущую операцию с операцией остановки
|
|
|
current.On(
|
|
|
stop.Resolve, // если текущая операция заверщилась, то можно начинать остановку
|
|
|
stop.Reject, // если текущая операция дала ошибку - то все плохо, нельзя продолжать
|
|
|
e => stop.Resolve() // если текущая отменилась, то можно начинать остановку
|
|
|
);
|
|
|
// посылаем текущей операции сигнал остановки
|
|
|
current.Cancel();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public ExecutionState State {
|
|
|
get {
|
|
|
return m_stateMachine.State;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public Exception LastError {
|
|
|
get {
|
|
|
return m_lastError;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region IDisposable implementation
|
|
|
|
|
|
public void Dispose() {
|
|
|
IPromise pending;
|
|
|
lock (m_stateMachine) {
|
|
|
if (m_stateMachine.State == ExecutionState.Disposed)
|
|
|
return;
|
|
|
|
|
|
Move(Commands.Dispose);
|
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
|
pending = m_pending;
|
|
|
m_pending = null;
|
|
|
}
|
|
|
if (pending != null) {
|
|
|
pending.Cancel();
|
|
|
pending.Timeout(DisposeTimeout).On(
|
|
|
() => Dispose(true, null),
|
|
|
err => Dispose(true, err),
|
|
|
reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason))
|
|
|
);
|
|
|
} else {
|
|
|
Dispose(true, m_lastError);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
~RunnableComponent() {
|
|
|
Dispose(false, null);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
protected virtual void Dispose(bool disposing, Exception lastError) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|