RunnableComponent.cs
296 lines
| 10.1 KiB
| text/x-csharp
|
CSharpLexer
cin
|
r156 | using System; | ||
namespace Implab.Components { | ||||
cin
|
r185 | public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable { | ||
cin
|
r184 | 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]; | ||||
cin
|
r185 | 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); | ||||
cin
|
r184 | |||
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); | ||||
cin
|
r185 | |||
Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); | ||||
cin
|
r184 | } | ||
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); | ||||
cin
|
r203 | DisposeTimeout = 10000; | ||
cin
|
r184 | } | ||
cin
|
r203 | /// <summary> | ||
/// Gets or sets the timeout to wait for the pending operation to complete. If the pending operation doesn't finish than the component will be disposed anyway. | ||||
/// </summary> | ||||
protected int DisposeTimeout { | ||||
get; | ||||
set; | ||||
cin
|
r185 | } | ||
cin
|
r184 | void ThrowInvalidCommand(Commands cmd) { | ||
cin
|
r185 | if (m_stateMachine.State == ExecutionState.Disposed) | ||
throw new ObjectDisposedException(ToString()); | ||||
cin
|
r184 | throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); | ||
} | ||||
cin
|
r185 | void Move(Commands cmd) { | ||
if (!m_stateMachine.Move(cmd)) | ||||
ThrowInvalidCommand(cmd); | ||||
cin
|
r184 | } | ||
cin
|
r203 | /// <summary> | ||
/// Moves the component from running to failed state. | ||||
/// </summary> | ||||
/// <param name="error">The exception which is describing the error.</param> | ||||
/// <returns>Returns true if the component is set to the failed state, false - otherwise. | ||||
/// This method works only for the running state, in any other state it will return false.</returns> | ||||
protected bool Fail(Exception error) { | ||||
lock (m_stateMachine) { | ||||
if(m_stateMachine.State == ExecutionState.Running) { | ||||
m_stateMachine.Move(Commands.Fail); | ||||
m_lastError = error; | ||||
return true; | ||||
} | ||||
} | ||||
return false; | ||||
} | ||||
cin
|
r185 | void Invoke(Commands cmd, Action action) { | ||
lock (m_stateMachine) | ||||
Move(cmd); | ||||
cin
|
r184 | try { | ||
action(); | ||||
cin
|
r185 | lock(m_stateMachine) | ||
Move(Commands.Ok); | ||||
cin
|
r184 | } catch (Exception err) { | ||
cin
|
r185 | lock (m_stateMachine) { | ||
Move(Commands.Fail); | ||||
m_lastError = err; | ||||
} | ||||
cin
|
r184 | throw; | ||
} | ||||
} | ||||
cin
|
r185 | IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) { | ||
IPromise promise = null; | ||||
IPromise prev; | ||||
cin
|
r184 | |||
cin
|
r185 | var task = new ActionChainTask(action, null, null, true); | ||
lock (m_stateMachine) { | ||||
Move(cmd); | ||||
prev = m_pending; | ||||
cin
|
r184 | |||
cin
|
r196 | Action<Exception> errorOrCancel = e => { | ||
if (e == null) | ||||
e = new OperationCanceledException(); | ||||
lock (m_stateMachine) { | ||||
if (m_pending == promise) { | ||||
Move(Commands.Fail); | ||||
m_pending = null; | ||||
m_lastError = e; | ||||
} | ||||
} | ||||
throw new PromiseTransientException(e); | ||||
}; | ||||
cin
|
r185 | promise = task.Then( | ||
() => { | ||||
lock(m_stateMachine) { | ||||
if (m_pending == promise) { | ||||
Move(Commands.Ok); | ||||
m_pending = null; | ||||
} | ||||
cin
|
r184 | } | ||
cin
|
r196 | }, | ||
errorOrCancel, | ||||
errorOrCancel | ||||
cin
|
r185 | ); | ||
cin
|
r157 | |||
cin
|
r185 | m_pending = promise; | ||
} | ||||
cin
|
r157 | |||
cin
|
r185 | if (prev == null) | ||
task.Resolve(); | ||||
else | ||||
chain(prev, task); | ||||
cin
|
r156 | |||
cin
|
r185 | return promise; | ||
cin
|
r156 | } | ||
cin
|
r184 | |||
cin
|
r156 | #region IInitializable implementation | ||
public void Init() { | ||||
cin
|
r184 | Invoke(Commands.Init, OnInitialize); | ||
} | ||||
protected virtual void OnInitialize() { | ||||
cin
|
r156 | } | ||
#endregion | ||||
#region IRunnable implementation | ||||
public IPromise Start() { | ||||
cin
|
r185 | return InvokeAsync(Commands.Start, OnStart, null); | ||
cin
|
r156 | } | ||
protected virtual IPromise OnStart() { | ||||
return Promise.SUCCESS; | ||||
} | ||||
cin
|
r185 | public IPromise Stop() { | ||
return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose); | ||||
} | ||||
protected virtual IPromise OnStop() { | ||||
return Promise.SUCCESS; | ||||
cin
|
r156 | } | ||
cin
|
r185 | /// <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 { | ||||
cin
|
r187 | // связваем текущую операцию с операцией остановки | ||
current.On( | ||||
stop.Resolve, // если текущая операция заверщилась, то можно начинать остановку | ||||
stop.Reject, // если текущая операция дала ошибку - то все плохо, нельзя продолжать | ||||
e => stop.Resolve() // если текущая отменилась, то можно начинать остановку | ||||
); | ||||
// посылаем текущей операции сигнал остановки | ||||
cin
|
r185 | current.Cancel(); | ||
} | ||||
cin
|
r156 | } | ||
public ExecutionState State { | ||||
get { | ||||
cin
|
r203 | return m_stateMachine.State; | ||
cin
|
r156 | } | ||
} | ||||
public Exception LastError { | ||||
get { | ||||
cin
|
r185 | return m_lastError; | ||
cin
|
r156 | } | ||
} | ||||
#endregion | ||||
cin
|
r185 | |||
#region IDisposable implementation | ||||
cin
|
r203 | /// <summary> | ||
/// Releases all resource used by the <see cref="Implab.Components.RunnableComponent"/> object. | ||||
/// </summary> | ||||
/// <remarks> | ||||
/// <para>Will not try to stop the component, it will just release all resources. | ||||
/// To cleanup the component gracefully use <see cref="Stop()"/> method.</para> | ||||
/// <para> | ||||
/// In normal cases the <see cref="Dispose()"/> method shouldn't be called, the call to the <see cref="Stop()"/> | ||||
/// method is sufficient to cleanup the component. Call <see cref="Dispose()"/> only to cleanup after errors, | ||||
/// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may | ||||
/// lead to the data loss by the component. | ||||
/// </para></remarks> | ||||
cin
|
r185 | 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) { | ||||
} | ||||
cin
|
r156 | } | ||
} | ||||