// ==++== // // 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 { /// /// 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. /// #pragma warning(push) #pragma warning(disable: 4324) // structure was padded due to alignment specifier class ExternalStatistics { public: // // Public methods // /// /// Constructs the statistics object for an external context or alien thread. /// ExternalStatistics() : m_enqueuedTaskCounter(0), m_dequeuedTaskCounter(0), m_enqueuedTaskCheckpoint(0), m_dequeuedTaskCheckpoint(0), m_fIsActive(true) { } /// /// Increments the count of work coming in. /// void IncrementEnqueuedTaskCounter() { m_enqueuedTaskCounter++; } /// /// Increments the count of work being done. /// void IncrementDequeuedTaskCounter() { m_dequeuedTaskCounter++; } /// /// Increments the count of work being done. /// void IncrementDequeuedTaskCounter(unsigned int count) { m_dequeuedTaskCounter += count; } /// /// Resets the count of work coming in. /// /// /// 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. /// /// /// Previous value of the counter. /// 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; } /// /// Resets the count of work being done. /// /// /// Look at remarks for GetEnqueuedTaskCount. /// /// /// Previous value of the counter. /// 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; } /// /// 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. /// void MarkInactive() { m_fIsActive = FALSE; } /// /// Checks whether this statistics class expects any new updates. /// /// /// True if statistics is still active. /// 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 int m_listArrayIndex{}; private: // // Private data // template 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; }; #pragma warning(pop) /// /// Implements the base class for ConcRT external contexts. /// class ExternalContextBase : public ContextBase { public: // // Public methods // /// /// Constructs an external context. /// ExternalContextBase(SchedulerBase *pScheduler, bool explicitAttach); /// /// Destroys an external context. /// virtual ~ExternalContextBase(); /// /// 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. /// virtual void Block(); /// /// Unblocks the external context causing it to start running. /// virtual void Unblock(); /// /// Since there is no underlying virtual processor, the yield operation is a no-op for external contexts. /// virtual void Yield(); /// /// Since there is no underlying virtual processor, the yield operation is a no-op for external contexts. /// virtual void SpinYield() { Yield(); } /// /// See comments for Concurrency::Context::Oversubscribe. /// virtual void Oversubscribe(bool beginOversubscription); /// /// Allocates a block of memory of the size specified. /// /// /// Number of bytes to allocate. /// /// /// A pointer to newly allocated memory. /// virtual void* Alloc(size_t numBytes); /// /// Frees a block of memory previously allocated by the Alloc API. /// /// /// A pointer to an allocation previously allocated by Alloc. /// virtual void Free(void* pAllocation); /// /// Tells whether the context was explicitly attached to the scheduler at the time it was created /// bool WasExplicitlyAttached() const { return m_fExplicitlyAttached; } /// /// Returns an identifier to the virtual processor the context is currently executing on, if any. /// virtual unsigned int GetVirtualProcessorId() const { return UINT_MAX; } /// /// 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. /// void PrepareForUse(bool explicitAttach); /// /// Prepares an external context for the idle pool by releasing some resources. /// void RemoveFromUse(); /// /// Returns a handle to the underlying thread. /// HANDLE GetPhysicalContext() { return m_hPhysicalContext; } /// /// 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. /// SubAllocator* GetCurrentSubAllocator() { if (m_pSubAllocator == NULL) { m_pSubAllocator = SchedulerBase::GetSubAllocator(true); } return m_pSubAllocator; } /// /// Increments the count of work coming in. /// void IncrementEnqueuedTaskCounter() { m_pStats->IncrementEnqueuedTaskCounter(); } /// /// Increments the count of work being done. /// void IncrementDequeuedTaskCounter() { m_pStats->IncrementDequeuedTaskCounter(); } /// /// Increments the count of work being done. /// void IncrementDequeuedTaskCounter(unsigned int count) { m_pStats->IncrementDequeuedTaskCounter(count); } /// /// Orphan the statistics and let it know there will be no more updates. /// /// /// The statistics that were attached to this external context. /// ExternalStatistics * DetachStatistics() { ExternalStatistics * externalStatistics = m_pStats; m_pStats = NULL; externalStatistics->MarkInactive(); return externalStatistics; } /// /// Determines whether or not the context is synchronously blocked at this given time. /// /// /// Whether context is in synchronous block state. /// virtual bool IsSynchronouslyBlocked() const { return (m_contextSwitchingFence == 1); } #if _DEBUG // _DEBUG helper DWORD GetThreadId() const { return m_threadId; } #endif private: friend class SchedulerBase; template 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 // /// /// Performs cleanup of the external context /// void Cleanup(); /// /// Callback to indicate the exit of one of the external threads. /// static void CALLBACK ImplicitDetachHandler(PTP_CALLBACK_INSTANCE instance, PVOID parameter, PTP_WAIT waiter, TP_WAIT_RESULT waitResult); /// /// Same callback function as ImplicitDetachHandler but used on XP. /// static void CALLBACK ImplicitDetachHandlerXP(PVOID parameter, BOOLEAN is_timeout); /// /// Returns the type of context /// virtual ContextKind GetContextKind() const { return ExternalContext; } }; } // namespace details } // namespace Concurrency