Mercurial > pub > ImplabNet
diff Implab/Components/RunnableComponent.cs @ 205:8200ab154c8a v2
Added ResetState to RunnableComponent to reset in case of failure
Added StateChanged event to IRunnable
Renamed Promise.SUCCESS -> Promise.Success
Added Promise.FromException
Renamed Bundle -> PromiseAll in PromiseExtensions
author | cin |
---|---|
date | Tue, 25 Oct 2016 17:40:33 +0300 |
parents | 4d9830a9bbb8 |
children | 7d07503621fe |
line wrap: on
line diff
--- a/Implab/Components/RunnableComponent.cs Tue Oct 18 17:49:54 2016 +0300 +++ b/Implab/Components/RunnableComponent.cs Tue Oct 25 17:40:33 2016 +0300 @@ -9,7 +9,8 @@ Start, Stop, Dispose, - Last = Dispose + Reset, + Last = Reset } class StateMachine { @@ -37,9 +38,11 @@ Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); - Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok); + Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok); + Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); + Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset); } static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { @@ -67,11 +70,26 @@ IPromise m_pending; Exception m_lastError; - readonly StateMachine m_stateMachine; - - protected RunnableComponent(bool initialized) { + readonly StateMachine m_stateMachine; + readonly bool m_reusable; + public event EventHandler<StateChangeEventArgs> StateChanged; + + /// <summary> + /// Initializes component state. + /// </summary> + /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> + /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param> + protected RunnableComponent(bool initialized, bool reusable) { m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); - DisposeTimeout = 10000; + m_reusable = reusable; + DisposeTimeout = 10000; + } + + /// <summary> + /// Initializes component state. The component created with this constructor is not reusable, i.e. it will be disposed after stop. + /// </summary> + /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> + protected RunnableComponent(bool initialized) : this(initialized, false) { } /// <summary> @@ -84,49 +102,119 @@ 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)); + throw new ObjectDisposedException(ToString()); + + throw new InvalidOperationException(String.Format("Command {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); + } + + bool MoveIfInState(Commands cmd, IPromise pending, Exception error, ExecutionState state) { + ExecutionState prev, current; + lock (m_stateMachine) { + if (m_stateMachine.State != state) + return false; + + prev = m_stateMachine.State; + if (!m_stateMachine.Move(cmd)) + ThrowInvalidCommand(cmd); + current = m_stateMachine.State; + + m_pending = pending; + m_lastError = error; + } + if (prev != current) + OnStateChanged(prev, current, error); + return true; } - void Move(Commands cmd) { - if (!m_stateMachine.Move(cmd)) - ThrowInvalidCommand(cmd); + bool MoveIfPending(Commands cmd, IPromise pending, Exception error, IPromise expected) { + ExecutionState prev, current; + lock (m_stateMachine) { + if (m_pending != expected) + return false; + prev = m_stateMachine.State; + if (!m_stateMachine.Move(cmd)) + ThrowInvalidCommand(cmd); + current = m_stateMachine.State; + m_pending = pending; + m_lastError = error; + } + if (prev != current) + OnStateChanged(prev, current, error); + return true; + } + + IPromise Move(Commands cmd, IPromise pending, Exception error) { + ExecutionState prev, current; + IPromise ret; + lock (m_stateMachine) { + prev = m_stateMachine.State; + if (!m_stateMachine.Move(cmd)) + ThrowInvalidCommand(cmd); + current = m_stateMachine.State; + + ret = m_pending; + m_pending = pending; + m_lastError = error; + + } + if(prev != current) + OnStateChanged(prev, current, error); + return ret; + } + + protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) { + var h = StateChanged; + if (h != null) + h(this, new StateChangeEventArgs { + State = current, + LastError = error + }); } /// <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; + protected bool Fail(Exception error) { + return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running); } - void Invoke(Commands cmd, Action action) { - lock (m_stateMachine) - Move(cmd); - + /// <summary> + /// Tries to reset <see cref="ExecutionState.Failed"/> state to <see cref="ExecutionState.Ready"/>. + /// </summary> + /// <returns>True if component is reset to <see cref="ExecutionState.Ready"/>, false if the componet wasn't + /// in <see cref="ExecutionState.Failed"/> state.</returns> + /// <remarks> + /// This method checks the current state of the component and if it's in <see cref="ExecutionState.Failed"/> + /// moves component to <see cref="ExecutionState.Initializing"/>. + /// The <see cref="OnResetState()"/> is called and if this method completes succesfully the component moved + /// to <see cref="ExecutionState.Ready"/> state, otherwise the component is moved to <see cref="ExecutionState.Failed"/> + /// state. If <see cref="OnResetState()"/> throws an exception it will be propagated by this method to the caller. + /// </remarks> + protected bool ResetState() { + if (!MoveIfInState(Commands.Reset, null, null, ExecutionState.Failed)) + return false; + try { - action(); - lock(m_stateMachine) - Move(Commands.Ok); - + OnResetState(); + Move(Commands.Ok, null, null); + return true; } catch (Exception err) { - lock (m_stateMachine) { - Move(Commands.Fail); - m_lastError = err; - } + Move(Commands.Fail, null, err); throw; - } + } + } + + /// <summary> + /// This method is called by <see cref="ResetState"/> to reinitialize component in the failed state. + /// </summary> + /// <remarks> + /// Default implementation throws <see cref="NotImplementedException"/> which will cause the component + /// fail to reset it's state and it left in <see cref="ExecutionState.Failed"/> state. + /// If this method doesn't throw exceptions the component is moved to <see cref="ExecutionState.Ready"/> state. + /// </remarks> + protected virtual void OnResetState() { + throw new NotImplementedException(); } IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) { @@ -135,40 +223,20 @@ 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(); + MoveIfPending(Commands.Fail, null, e, promise); + throw new PromiseTransientException(e); + }; - 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( + () => MoveIfPending(Commands.Ok, null, null, promise), + errorOrCancel, + errorOrCancel + ); - promise = task.Then( - () => { - lock(m_stateMachine) { - if (m_pending == promise) { - Move(Commands.Ok); - m_pending = null; - } - } - }, - errorOrCancel, - errorOrCancel - ); - - m_pending = promise; - } + prev = Move(cmd, promise, null); if (prev == null) task.Resolve(); @@ -181,8 +249,16 @@ #region IInitializable implementation - public void Init() { - Invoke(Commands.Init, OnInitialize); + public void Initialize() { + Move(Commands.Init, null, null); + + try { + OnInitialize(); + Move(Commands.Ok, null, null); + } catch (Exception err) { + Move(Commands.Fail, null, err); + throw; + } } protected virtual void OnInitialize() { @@ -197,15 +273,16 @@ } protected virtual IPromise OnStart() { - return Promise.SUCCESS; + return Promise.Success; } public IPromise Stop() { - return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose); + var pending = InvokeAsync(Commands.Stop, OnStop, StopPending); + return m_reusable ? pending : pending.Then(Dispose); } protected virtual IPromise OnStop() { - return Promise.SUCCESS; + return Promise.Success; } /// <summary> @@ -258,17 +335,14 @@ /// </para></remarks> public void Dispose() { IPromise pending; + lock (m_stateMachine) { if (m_stateMachine.State == ExecutionState.Disposed) return; - - Move(Commands.Dispose); + pending = Move(Commands.Dispose, null, null); + } - GC.SuppressFinalize(this); - - pending = m_pending; - m_pending = null; - } + GC.SuppressFinalize(this); if (pending != null) { pending.Cancel(); pending.Timeout(DisposeTimeout).On( @@ -277,7 +351,7 @@ reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason)) ); } else { - Dispose(true, m_lastError); + Dispose(true, null); } } @@ -287,6 +361,11 @@ #endregion + /// <summary> + /// Releases all resources used by the component, called automatically, override this method to implement your cleanup. + /// </summary> + /// <param name="disposing">true if this method is called during normal dispose process.</param> + /// <param name="lastError">The last error which occured during the component stop.</param> protected virtual void Dispose(bool disposing, Exception lastError) { }