view Implab/AbstractEvent.cs @ 156:97fbbf816844 v2

Promises: SignalXXX methods merged into SignalHandler method. Components: RunnableComponent In progress
author cin
date Mon, 15 Feb 2016 04:22:15 +0300
parents e6d4b41f0101
children 5802131432e4
line wrap: on
line source

using System;
using Implab.Parallels;
using System.Threading;
using System.Reflection;

namespace Implab {
    public abstract class AbstractEvent<THandler> : ICancellationToken, ICancellable { 

        const int UNRESOLVED_SATE = 0;
        const int TRANSITIONAL_STATE = 1;
        protected const int SUCCEEDED_STATE = 2;
        protected const int REJECTED_STATE = 3;
        protected const int CANCELLED_STATE = 4;

        const int CANCEL_NOT_REQUESTED = 0;
        const int CANCEL_REQUESTING = 1;
        const int CANCEL_REQUESTED = 2;

        const int RESERVED_HANDLERS_COUNT = 4;

        int m_state;
        Exception m_error;
        int m_handlersCount;

        //readonly THandler[] m_handlers = new THandler[RESERVED_HANDLERS_COUNT];
        THandler[] m_handlers;
        MTQueue<THandler> m_extraHandlers;
        int m_handlerPointer = -1;
        int m_handlersCommited;

        int m_cancelRequest;
        Exception m_cancelationReason;
        MTQueue<Action<Exception>> m_cancelationHandlers;


        #region state managment
        bool BeginTransit() {
            return UNRESOLVED_SATE == Interlocked.CompareExchange(ref m_state, TRANSITIONAL_STATE, UNRESOLVED_SATE);
        }

        void CompleteTransit(int state) {
            if (TRANSITIONAL_STATE != Interlocked.CompareExchange(ref m_state, state, TRANSITIONAL_STATE))
                throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state");
        }

        void WaitTransition() {
            while (m_state == TRANSITIONAL_STATE) {
                Thread.MemoryBarrier();
            }
        }

        protected bool BeginSetResult() {
            if (!BeginTransit()) {
                WaitTransition();
                if (m_state != CANCELLED_STATE)
                    throw new InvalidOperationException("The promise is already resolved");
                return false;
            }
            return true;
        }

        protected void EndSetResult() {
            CompleteTransit(SUCCEEDED_STATE);
            Signal();
        }



        /// <summary>
        /// Выполняет обещание, сообщая об ошибке
        /// </summary>
        /// <remarks>
        /// Поскольку обещание должно работать в многопточной среде, при его выполнении сразу несколько потоков
        /// могу вернуть ошибку, при этом только первая будет использована в качестве результата, остальные
        /// будут проигнорированы.
        /// </remarks>
        /// <param name="error">Исключение возникшее при выполнении операции</param>
        /// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception>
        protected void SetError(Exception error) {
            if (BeginTransit()) {
                if (error is OperationCanceledException) {
                    m_error = error.InnerException;
                    CompleteTransit(CANCELLED_STATE);
                } else {
                    m_error = error is PromiseTransientException ? error.InnerException : error;
                    CompleteTransit(REJECTED_STATE);
                }
                Signal();
            } else {
                WaitTransition();
                if (m_state == SUCCEEDED_STATE)
                    throw new InvalidOperationException("The promise is already resolved");
            }
        }

        /// <summary>
        /// Отменяет операцию, если это возможно.
        /// </summary>
        /// <remarks>Для определения была ли операция отменена следует использовать свойство <see cref="IsCancelled"/>.</remarks>
        protected void SetCancelled(Exception reason) {
            if (BeginTransit()) {
                m_error = reason;
                CompleteTransit(CANCELLED_STATE);
                Signal();
            }
        }

        protected abstract void SignalHandler(THandler handler, int signal);

        void Signal() {
            var hp = m_handlerPointer;
            var slot = hp +1 ;
            while (slot < m_handlersCommited) {
                if (Interlocked.CompareExchange(ref m_handlerPointer, slot, hp) == hp) {
                    SignalHandler(m_handlers[slot], m_state);
                }
                hp = m_handlerPointer;
                slot = hp +1 ;
            }


            if (m_extraHandlers != null) {
                THandler handler;
                while (m_extraHandlers.TryDequeue(out handler))
                    SignalHandler(handler, m_state);
            }
        }

        #endregion

        protected abstract Signal GetResolveSignal();

        #region synchronization traits
        protected void WaitResult(int timeout) {
            if (!(IsResolved || GetResolveSignal().Wait(timeout)))
                throw new TimeoutException();

            switch (m_state) {
                case SUCCEEDED_STATE:
                    return;
                case CANCELLED_STATE:
                    throw new OperationCanceledException();
                case REJECTED_STATE:
                    throw new TargetInvocationException(m_error);
                default:
                    throw new ApplicationException(String.Format("Invalid promise state {0}", m_state));
            }
        }
        #endregion

        #region handlers managment

        protected void AddHandler(THandler handler) {

            if (m_state > 1) {
                // the promise is in the resolved state, just invoke the handler
                SignalHandler(handler, m_state);
            } else {
                var slot = Interlocked.Increment(ref m_handlersCount) - 1;

                if (slot < RESERVED_HANDLERS_COUNT) {

                    if (slot == 0)
                        Interlocked.CompareExchange(ref m_handlers, new THandler[RESERVED_HANDLERS_COUNT], null);

                    m_handlers[slot] = handler;

                    while (slot != Interlocked.CompareExchange(ref m_handlersCommited, slot + 1, slot)) {
                    }

                    if (m_state > 1) {
                        do {
                            var hp = m_handlerPointer;
                            slot = hp + 1;
                            if (slot < m_handlersCommited) {
                                if (Interlocked.CompareExchange(ref m_handlerPointer, slot, hp) != hp)
                                    continue;
                                SignalHandler(m_handlers[slot], m_state);
                            }
                            break;
                        } while(true);
                    }
                } else {
                    if (slot == RESERVED_HANDLERS_COUNT) {
                        m_extraHandlers = new MTQueue<THandler>();
                    } else {
                        while (m_extraHandlers == null)
                            Thread.MemoryBarrier();
                    }

                    m_extraHandlers.Enqueue(handler);

                    if (m_state > 1 && m_extraHandlers.TryDequeue(out handler))
                    // if the promise have been resolved while we was adding the handler to the queue
                    // we can't guarantee that someone is still processing it
                    // therefore we need to fetch a handler from the queue and execute it
                    // note that fetched handler may be not the one that we have added
                    // even we can fetch no handlers at all :)
                        SignalHandler(handler, m_state);
                }
            }
        }

        #endregion

        #region IPromise implementation

        public bool IsResolved {
            get {
                Thread.MemoryBarrier();
                return m_state > 1;
            }
        }

        public bool IsCancelled {
            get {
                Thread.MemoryBarrier();
                return m_state == CANCELLED_STATE;
            }
        }

        #endregion

        public Exception Error {
            get {
                return m_error;
            }
        }

        public bool CancelOperationIfRequested() {
            if (IsCancellationRequested) {
                CancelOperation(CancellationReason);
                return true;
            }
            return false;
        }

        public virtual void CancelOperation(Exception reason) {
            SetCancelled(reason);
        }

        public void CancellationRequested(Action<Exception> handler) {
            Safe.ArgumentNotNull(handler, "handler");
            if (IsCancellationRequested)
                handler(CancellationReason);

            if (m_cancelationHandlers == null)
                Interlocked.CompareExchange(ref m_cancelationHandlers, new MTQueue<Action<Exception>>(), null);

            m_cancelationHandlers.Enqueue(handler);

            if (IsCancellationRequested && m_cancelationHandlers.TryDequeue(out handler))
                // TryDeque implies MemoryBarrier()
                handler(m_cancelationReason);
        }

        public bool IsCancellationRequested {
            get {
                do {
                    if (m_cancelRequest == CANCEL_NOT_REQUESTED)
                        return false;
                    if (m_cancelRequest == CANCEL_REQUESTED)
                        return true;
                    Thread.MemoryBarrier();
                } while(true);
            }
        }

        public Exception CancellationReason {
            get {
                do {
                    Thread.MemoryBarrier();
                } while(m_cancelRequest == CANCEL_REQUESTING);

                return m_cancelationReason;
            }
        }

        #region ICancellable implementation

        public void Cancel() {
            Cancel(null);
        }

        public void Cancel(Exception reason) {
            if (CANCEL_NOT_REQUESTED == Interlocked.CompareExchange(ref m_cancelRequest, CANCEL_REQUESTING, CANCEL_NOT_REQUESTED)) {
                m_cancelationReason = reason;
                m_cancelRequest = CANCEL_REQUESTED;
                if (m_cancelationHandlers != null) {
                    Action<Exception> handler;
                    while (m_cancelationHandlers.TryDequeue(out handler))
                        handler(m_cancelationReason);
                }
            }
        }

        #endregion
    }
}