view Implab/Components/RunnableComponent.cs @ 186:75103928da09 ref20160224

working on cancelation and error handling
author cin
date Tue, 19 Apr 2016 00:50:14 +0300
parents 822aab37b107
children dd4a3590f9c6
line wrap: on
line source

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;

                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);
                    },
                    r => {
                        // handle cancellation as exception
                        throw new OperationCanceledException("The operation has been cancelled", r);
                    }
                );

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

        }

    }
}