// ==++==
//
// 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