// ==++==
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// ExternalContextBase.h
//
// Header file containing the metaphor for an external execution context.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#pragma once

namespace Concurrency
{
namespace details
{
    /// <summary>
    ///     Provides a storage area for external contexts bound to this scheduler or alien threads (threads not
    ///     associated with any scheduler or those associated with scheduler other than this one) where they can put
    ///     the statistical data necessary to track the rate of work.
    /// </summary>

    class ExternalStatistics
    {
    public:
        //
        // Public methods
        //

        /// <summary>
        ///     Constructs the statistics object for an external context or alien thread.
        /// </summary>
        ExternalStatistics() : m_enqueuedTaskCounter(0), m_dequeuedTaskCounter(0), m_enqueuedTaskCheckpoint(0), m_dequeuedTaskCheckpoint(0), m_fIsActive(true)
        {
        }

        /// <summary>
        ///     Increments the count of work coming in.
        /// </summary>
        void IncrementEnqueuedTaskCounter()
        {
            m_enqueuedTaskCounter++;
        }

        /// <summary>
        ///     Increments the count of work being done.
        /// </summary>
        void IncrementDequeuedTaskCounter()
        {
            m_dequeuedTaskCounter++;
        }

        /// <summary>
        ///     Increments the count of work being done.
        /// </summary>
        void IncrementDequeuedTaskCounter(unsigned int count)
        {
            m_dequeuedTaskCounter += count;
        }

        /// <summary>
        ///     Resets the count of work coming in.
        /// </summary>
        /// <remarks>
        ///     This function will reset the state so that the next time it is called, it reports only the
        ///     units of work that came in since the last time. One obvious solution is to reset the
        ///     counter, but that introduces a race with a thread that tries to increment. Instead,
        ///     we update the trailing counter to match the current count. This way the difference
        ///     between the two is always the number of work coming in. By keeping these numbers unsigned
        ///     we make use of "modulo 2" behavior of unsigned ints and avoid overflow problems.
        ///
        ///     NOTE: There is a highly rare condition present in this code. If, for some reason,
        //      statistics calls were infrequent enough that UINT_MAX units of work were enqueued
        ///     between two calls we will wrap around and consequently think that no work came in at all.
        /// </remarks>
        /// <returns>
        ///     Previous value of the counter.
        /// </returns>
        unsigned int GetEnqueuedTaskCount()
        {
            unsigned int currentValue = m_enqueuedTaskCounter;
            unsigned int retVal = currentValue - m_enqueuedTaskCheckpoint;

            // Update the checkpoint value with the current value
            m_enqueuedTaskCheckpoint = currentValue;

            ASSERT(retVal < INT_MAX);
            return retVal;
        }

        /// <summary>
        ///     Resets the count of work being done.
        /// </summary>
        /// <remarks>
        ///     Look at remarks for GetEnqueuedTaskCount.
        /// </remarks>
        /// <returns>
        ///     Previous value of the counter.
        /// </returns>
        unsigned int GetDequeuedTaskCount()
        {
            unsigned int currentValue = m_dequeuedTaskCounter;
            unsigned int retVal = currentValue - m_dequeuedTaskCheckpoint;

            // Update the checkpoint value with the current value
            m_dequeuedTaskCheckpoint = currentValue;

            ASSERT(retVal < INT_MAX);
            return retVal;
        }

        /// <summary>
        ///     Marks this statistics as not active anymore. This means that external context
        ///     has gone away and it will no longer update the statistical information. However,
        ///     we can't remove statistics right away because they might not have been collected yet.
        ///     So, we mark it as inactive and we wait for the next collection to take place before
        ///     permanently retiring this statistics.
        /// </summary>
        void MarkInactive()
        {
            m_fIsActive = FALSE;
        }

        /// <summary>
        ///     Checks whether this statistics class expects any new updates.
        /// </summary>
        /// <returns>
        ///     True if statistics is still active.
        /// </returns>
        bool IsActive()
        {
            // By the memory ordering rules the only way that m_fIsActive would be marked as false
            // is if external context is being destroyed, which means there is no work coming in or
            // out of this external context. The task counts are final and there is no race between
            // task counts and active bit.
            return (m_fIsActive || (m_enqueuedTaskCounter != m_enqueuedTaskCheckpoint) || (m_dequeuedTaskCounter != m_dequeuedTaskCheckpoint));
        }

        // A field that is necessary to store the statistics data structure in a ListArray<ExternalStatistics>
        int m_listArrayIndex;

    private:
        //
        // Private data
        //

        template <class T> friend class ListArray;

        // Intrusive links for list array.
        SLIST_ENTRY m_listArrayFreeLink;

        // Statistics data counters
        unsigned int m_enqueuedTaskCounter;
        unsigned int m_dequeuedTaskCounter;

        // Statistics data checkpoints
        unsigned int m_enqueuedTaskCheckpoint;
        unsigned int m_dequeuedTaskCheckpoint;

        // Whether this statistics is actively worked on
        volatile BOOL m_fIsActive;
    };

    /// <summary>
    /// Implements the base class for ConcRT external contexts.
    /// </summary>
    class ExternalContextBase : public ContextBase
    {
    public:

        //
        // Public methods
        //

        /// <summary>
        ///     Constructs an external context.
        /// </summary>
        ExternalContextBase(SchedulerBase *pScheduler, bool explicitAttach);

        /// <summary>
        ///     Destroys an external context.
        /// </summary>
        virtual ~ExternalContextBase();

        /// <summary>
        ///     Causes the external context to block. Since external contexts do not execute on virtual processors,
        ///     the context does not switch to another one. Instead, it stops executing until it is unblocked.
        /// </summary>
        virtual void Block();

        /// <summary>
        ///     Unblocks the external context causing it to start running.
        /// </summary>
        virtual void Unblock();

        /// <summary>
        ///     Since there is no underlying virtual processor, the yield operation is a no-op for external contexts.
        /// </summary>
        virtual void Yield();

        /// <summary>
        ///     Since there is no underlying virtual processor, the yield operation is a no-op for external contexts.
        /// </summary>
        virtual void SpinYield()
        {
            Yield();
        }

        /// <summary>
        ///     See comments for Concurrency::Context::Oversubscribe.
        /// </summary>
        virtual void Oversubscribe(bool beginOversubscription);

        /// <summary>
        ///     Allocates a block of memory of the size specified.
        /// </summary>
        /// <param name="numBytes">
        ///     Number of bytes to allocate.
        /// </param>
        /// <returns>
        ///     A pointer to newly allocated memory.
        /// </returns>
        virtual void* Alloc(size_t numBytes);

        /// <summary>
        ///     Frees a block of memory previously allocated by the Alloc API.
        /// </summary>
        /// <param name="pAllocation">
        ///     A pointer to an allocation previously allocated by Alloc.
        /// </param>
        virtual void Free(void* pAllocation);

        /// <summary>
        ///     Tells whether the context was explicitly attached to the scheduler at the time it was created
        /// </summary>
        bool WasExplicitlyAttached() const { return m_fExplicitlyAttached; }

        /// <summary>
        ///     Returns an identifier to the virtual processor the context is currently executing on, if any.
        /// </summary>
        virtual unsigned int GetVirtualProcessorId() const { return UINT_MAX; }

        /// <summary>
        ///     Initializes fields that need re-initialization when an external context is reused. This is called
        ///     in the constructor and when an external context is taken off the idle pool for reuse.
        /// </summary>
        void PrepareForUse(bool explicitAttach);

        /// <summary>
        ///     Prepares an external context for the idle pool by releasing some resources.
        /// </summary>
        void RemoveFromUse();

        /// <summary>
        ///     Returns a handle to the underlying thread.
        /// </summary>
        HANDLE GetPhysicalContext() { return m_hPhysicalContext; }

        /// <summary>
        ///     Returns a pointer to the suballocator for this external context. Note that the RM call to get an
        ///     allocator can return NULL, since the RM only hands out a fixed number of allocators for external
        ///     contexts.
        /// </summary>
        SubAllocator* GetCurrentSubAllocator()
        {
            if (m_pSubAllocator == NULL)
            {
                m_pSubAllocator = SchedulerBase::GetSubAllocator(true);
            }
            return m_pSubAllocator;
        }

        /// <summary>
        ///     Increments the count of work coming in.
        /// </summary>
        void IncrementEnqueuedTaskCounter()
        {
            m_pStats->IncrementEnqueuedTaskCounter();
        }

        /// <summary>
        ///     Increments the count of work being done.
        /// </summary>
        void IncrementDequeuedTaskCounter()
        {
            m_pStats->IncrementDequeuedTaskCounter();
        }

        /// <summary>
        ///     Increments the count of work being done.
        /// </summary>
        void IncrementDequeuedTaskCounter(unsigned int count)
        {
            m_pStats->IncrementDequeuedTaskCounter(count);
        }

        /// <summary>
        ///     Orphan the statistics and let it know there will be no more updates.
        /// </summary>
        /// <returns>
        ///     The statistics that were attached to this external context.
        /// </returns>
        ExternalStatistics * DetachStatistics()
        {
            ExternalStatistics * externalStatistics = m_pStats;
            m_pStats = NULL;
            externalStatistics->MarkInactive();

            return externalStatistics;
        }

        /// <summary>
        ///     Determines whether or not the context is synchronously blocked at this given time.
        /// </summary>
        /// <returns>
        ///     Whether context is in synchronous block state.
        /// </returns>
        virtual bool IsSynchronouslyBlocked() const
        {
            return (m_contextSwitchingFence == 1);
        }

#if _DEBUG
        // _DEBUG helper
        DWORD GetThreadId() const { return m_threadId; }
#endif

    private:
        friend class SchedulerBase;
        template<class T> friend void _InternalDeleteHelper(T*);

        //
        // Private data
        //

        // Specifies whether the context was created as a result of an explicit or implicit attach.
        bool m_fExplicitlyAttached;

        // Statistical information for this external context.
        ExternalStatistics * m_pStats;

        // A pointer to the suballocator for this context.
        SubAllocator * m_pSubAllocator;

        // Handle to the underlying thread.
        HANDLE m_hPhysicalContext;

        // Handle to the event used for blocking.
        HANDLE m_hBlock;

        // Wait handle for thread exit event (used on XP)
        HANDLE m_hWaitHandle;

        //
        // Private methods
        //

        /// <summary>
        ///     Performs cleanup of the external context
        /// </summary>
        void Cleanup();

        /// <summary>
        ///     Callback to indicate the exit of one of the external threads.
        /// </summary>
        static void CALLBACK ImplicitDetachHandler(PTP_CALLBACK_INSTANCE instance, PVOID parameter, PTP_WAIT waiter, TP_WAIT_RESULT waitResult);

        /// <summary>
        ///     Same callback function as ImplicitDetachHandler but used on XP.
        /// </summary>
        static void CALLBACK ImplicitDetachHandlerXP(PVOID parameter, BOOLEAN is_timeout);

        /// <summary>
        ///     Returns the type of context
        /// </summary>
        virtual ContextKind GetContextKind() const { return ExternalContext; }
    };
} // namespace details
} // namespace Concurrency
