RunnableComponent.cs
255 lines
| 7.8 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
|
r185 | protected virtual int DisposeTimeout { | ||
get { | ||||
return 10000; | ||||
} | ||||
} | ||||
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
|
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
|
r185 | promise = task.Then( | ||
() => { | ||||
lock(m_stateMachine) { | ||||
if (m_pending == promise) { | ||||
Move(Commands.Ok); | ||||
m_pending = null; | ||||
} | ||||
cin
|
r184 | } | ||
cin
|
r185 | }, e => { | ||
lock(m_stateMachine) { | ||||
if (m_pending == promise) { | ||||
Move(Commands.Fail); | ||||
m_pending = null; | ||||
m_lastError = e; | ||||
} | ||||
} | ||||
throw new PromiseTransientException(e); | ||||
}, | ||||
r => { | ||||
cin
|
r186 | // handle cancellation as exception | ||
cin
|
r185 | throw new OperationCanceledException("The operation has been cancelled", r); | ||
cin
|
r184 | } | ||
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
|
r186 | current.On(stop.Resolve, stop.Reject, e => stop.Resolve()); | ||
cin
|
r185 | current.Cancel(); | ||
} | ||||
cin
|
r156 | } | ||
public ExecutionState State { | ||||
get { | ||||
cin
|
r185 | return m_stateMachine.State; | ||
cin
|
r156 | } | ||
} | ||||
public Exception LastError { | ||||
get { | ||||
cin
|
r185 | return m_lastError; | ||
cin
|
r156 | } | ||
} | ||||
#endregion | ||||
cin
|
r185 | |||
#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) { | ||||
} | ||||
cin
|
r156 | } | ||
} | ||||