view Implab/Components/RunnableComponent.cs @ 187:dd4a3590f9c6 ref20160224

Reworked cancelation handling, if the cancel handler isn't specified the OperationCanceledException will be handled by the error handler Any unhandled OperationCanceledException will cause the promise cancelation
author cin
date Tue, 19 Apr 2016 17:35:20 +0300
parents 75103928da09
children 40d7fed4a09e
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);
                    }
                );

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

        }

    }
}