Mercurial > pub > ImplabNet
diff Implab/Components/RunnableComponent.cs @ 190:1c2a16d071a7 v2
Слияние с ref20160224
author | cin |
---|---|
date | Fri, 22 Apr 2016 13:08:08 +0300 |
parents | dd4a3590f9c6 |
children | 40d7fed4a09e |
line wrap: on
line diff
--- a/Implab/Components/RunnableComponent.cs Fri Feb 19 18:07:17 2016 +0300 +++ b/Implab/Components/RunnableComponent.cs Fri Apr 22 13:08:08 2016 +0300 @@ -1,24 +1,164 @@ using System; -using Implab.Parsing; namespace Implab.Components { - public class RunnableComponent : Disposable, IRunnable, IInitializable { - + 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 @@ -26,33 +166,92 @@ #region IRunnable implementation public IPromise Start() { - throw new NotImplementedException(); + return InvokeAsync(Commands.Start, OnStart, null); } protected virtual IPromise OnStart() { return Promise.SUCCESS; } - protected virtual void Run() { + public IPromise Stop() { + return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose); + } + + protected virtual IPromise OnStop() { + return Promise.SUCCESS; } - public IPromise Stop() { - throw new NotImplementedException(); + /// <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 { - throw new NotImplementedException(); + return m_stateMachine.State; } } public Exception LastError { get { - throw new NotImplementedException(); + 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) { + + } + } }