##// END OF EJS Templates
fixed promise chaining behavior, the error handler doesn't handle result or cancellation handlers exceptions these exceptions are propagated to the next handlers.
fixed promise chaining behavior, the error handler doesn't handle result or cancellation handlers exceptions these exceptions are propagated to the next handlers.

File last commit:

r196:40d7fed4a09e default
r196:40d7fed4a09e default
Show More
RunnableComponent.cs
264 lines | 8.4 KiB | text/x-csharp | CSharpLexer
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;
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);
};
promise = task.Then(
() => {
lock(m_stateMachine) {
if (m_pending == promise) {
Move(Commands.Ok);
m_pending = null;
}
}
},
errorOrCancel,
errorOrCancel
);
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) {
}
}
}