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