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) {
 
         }