// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// VirtualProcessor.h
//
// Source file containing the VirtualProcessor declaration.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#pragma once
namespace Concurrency
{
namespace details
{
//
// TRANSITION: This is temporary until priority based livelock prevention is real.
//
struct BoostedObject
{
enum BoostType
{
BoostTypeScheduleGroupSegment,
BoostTypeVirtualProcessor
};
enum BoostState
{
BoostStateDisallowed,
BoostStateUnboosted,
BoostStateBoosted
};
BoostType m_type;
BoostedObject *m_pNext;
BoostedObject *m_pPrev;
LONG m_boostState;
bool IsScheduleGroupSegment() const
{
return m_type == BoostTypeScheduleGroupSegment;
}
bool IsVirtualProcessor() const
{
return m_type == BoostTypeVirtualProcessor;
}
};
//
// virtualized hardware thread
//
///
/// A virtual processor is an abstraction of a hardware thread. However, there may very well be more than one
/// virtual processor per hardware thread. The SchedulerPolicy key TargetOversubscriptionFactor determines
/// the number of virtual processor per hardware thread, scheduler wide.
///
///
/// Virtual processors may be created and destroyed at any time, since resource management (RM) may give or take
/// away hardware threads. But as such batches of TargetOversubscriptionFactor virtual processors are created or
/// destroyed simultaneously.
///
class VirtualProcessor
{
public:
///
/// Indicates the type of availability of a virtual processor.
///
enum AvailabilityType
{
// The virtual processor is claimed and running -- it is not available for claiming
AvailabilityClaimed = 0x0,
// The virtual processor was marked available and is/will go inactive with nothing running atop it
AvailabilityInactive = 0x01,
// The virtual processor is available with an idle/sleeping (or soon to be) thread running atop it.
AvailabilityIdle = 0x2,
// The virtual processor was marked available and is/will deactivate; however -- it is pending a thread. It should
// not be awoken unless a thread is ready for it (e.g.: not for LWTs / WSQ items)
AvailabilityInactivePendingThread = 0x4,
// The virtual processor is available with an idle/sleeping (or soon to be) thread running atop it; however -- it is pending a thread.
// This is a state exclusive to a scheduler with a specific scheduling context (e.g.: UMS)
AvailabilityIdlePendingThread = 0x8,
// Mask for any type of availability (e.g.: if trying to find/claim a vproc)
AvailabilityAny = 0xf
};
///
/// Represents a claim of exclusive ownership on a virtual processor.
///
class ClaimTicket
{
public:
///
/// Constructs a new empty ticket.
///
ClaimTicket() :
m_type(AvailabilityClaimed)
{
}
///
/// Exercises the ticket. Starts up a new context on the virtual processor or activates the idle one as appropriate.
///
bool Exercise(ScheduleGroupSegmentBase *pSegment = NULL)
{
ASSERT(m_type != AvailabilityClaimed);
bool fResult = m_pVirtualProcessor->ExerciseClaim(m_type, pSegment);
//
// Ensure that a second call to exercise the same ticket asserts.
//
m_type = AvailabilityClaimed;
return fResult;
}
///
/// Exercises the ticket with a particular context. This is only valid if ExerciseWakesExisting() returns false. Callers should have
/// sought virtual processors with specific claim rights.
///
bool ExerciseWith(InternalContextBase *pContext);
///
/// Informs the caller whether the claim simply wakes an existing thread.
///
bool ExerciseWakesExisting() const
{
return m_type == AvailabilityIdle || m_type == AvailabilityIdlePendingThread;
}
private:
friend class VirtualProcessor;
ClaimTicket(AvailabilityType type, VirtualProcessor *pVirtualProcessor)
{
InitializeTicket(type, pVirtualProcessor);
}
void InitializeTicket(AvailabilityType type, VirtualProcessor *pVirtualProcessor)
{
m_type = type;
m_pVirtualProcessor = pVirtualProcessor;
}
AvailabilityType m_type;
VirtualProcessor *m_pVirtualProcessor{};
};
///
/// Constructs a virtual processor.
///
VirtualProcessor();
///
/// Destroys a virtual processor
///
virtual ~VirtualProcessor();
///
/// Returns a scheduler unique identifier for the virtual processor.
///
unsigned int GetId() const { return m_id; }
///
/// Returns the execution resource id associated with this virtual processor.
///
unsigned int GetExecutionResourceId() const { return m_resourceId; }
///
/// Returns the mask id for this virtual processor.
///
unsigned int GetMaskId() const { return m_maskId; }
///
/// Updates tracking on the virtual processor whether it is executing affine work and/or local work.
///
///
/// An indication of whether the virtual processor is executing work which has affinity to it or not.
///
///
/// An indication of whether the virtual processor is executing work which is local to it or not.
///
void UpdateWorkState(bool fAffine, bool fLocal);
///
/// Performs a quick check as to whether work which is affine to the virtual processor has arrived. This allows short circuiting of
/// expensive searches for affine work in cases where a user does not use any location objects.
///
///
/// An indication of whether or not work affine to the virtual processor has arrived since UpdateWorkState was called to indicate that
/// the virtual processor began working on non-affine work.
///
bool CheckAffinityNotification();
///
/// Returns an indication of whether the virtual processor is (or was last) executing affine work.
///
///
/// An indication of whether the virtual processor is (or was last) executing affine work.
///
bool ExecutingAffine()
{
return m_fAffine;
}
///
/// Attempts to claim exclusive ownership of the virtual processor by resetting the available flag.
///
///
/// If true is returned, this will contain the claim ticket used to activate the virtual processor.
///
///
/// If specified, indicates the type of availability that we can claim. If the caller is only interested in inactive virtual processors,
/// for instance, AvailabilityInactive can be passed.
///
///
/// True if it was able to claim the virtual processor, false otherwise.
///
bool ClaimExclusiveOwnership(ClaimTicket &ticket, ULONG type = AvailabilityAny, bool updateCounters = true /* GET RID OF THIS */);
///
/// Returns the state of the virtual processor's availability prior to the last claim.
///
AvailabilityType ClaimantType() const
{
ASSERT(m_availabilityType == AvailabilityClaimed);
return m_claimantType;
}
///
/// Returns whether the last claimant claimed us while inactive.
///
bool ClaimantWasInactive() const
{
return ((ClaimantType() & (AvailabilityInactive | AvailabilityInactivePendingThread)) != 0);
}
///
/// Makes a virtual processor available for scheduling work.
///
void MakeAvailableForIdle()
{
MakeAvailable(AvailabilityIdle);
}
///
/// Makes a virtual processor available for scheduling work.
///
void MakeAvailableForInactive()
{
MakeAvailable(AvailabilityInactive);
}
///
/// Makes a virtual processor available pending a thread.
///
void MakeAvailablePendingThread()
{
MakeAvailable(AvailabilityInactivePendingThread);
}
///
/// Returns a pointer to the internal context that is executing on this virtual processor.
///
IExecutionContext * GetExecutingContext() { return m_pExecutingContext; }
///
/// Returns a pointer to the owning node for the virtual processor.
///
SchedulingNode * GetOwningNode() { return m_pOwningNode; }
///
/// Returns a pointer to the owning ring for the virtual processor.
///
SchedulingRing * GetOwningRing() { return m_pOwningRing; }
///
/// Returns a pointer to the owning root for the virtual processor.
///
IVirtualProcessorRoot * GetOwningRoot() { return m_pOwningRoot; }
///
/// Returns a pointer to the suballocator for the virtual processor.
///
SubAllocator * GetCurrentSubAllocator();
///
/// Returns true if the virtual processor is marked as available, false otherwise.
///
bool IsAvailable() { return (m_availabilityType != AvailabilityClaimed); }
///
/// Returns the type of availability of the virtual processor.
///
AvailabilityType TypeOfAvailability() { return m_availabilityType; }
///
/// Returns true if the virtual processor is marked for retirement, false otherwise.
///
bool IsMarkedForRetirement() { return m_fMarkedForRetirement; }
///
/// Activates a virtual processor with the context provided.
///
void Activate(IExecutionContext *pContext);
///
/// Temporarily deactivates a virtual processor.
///
///
/// An indication of which side the awakening occurred from (true -- we activated it, false -- the RM awoke it).
///
bool Deactivate(IExecutionContext *pContext);
///
/// Invokes the underlying virtual processor root to ensure all tasks are visible.
///
void EnsureAllTasksVisible(IExecutionContext * pContext);
///
/// Returns the default destination of a SwitchTo(NULL). There is none on a default virtual processor.
///
virtual IExecutionContext *GetDefaultDestination()
{
return NULL;
}
///
/// Performs a search for work for the given virtual processor.
///
bool SearchForWork(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass)
{
return m_searchCtx.Search(pWorkItem, pOriginSegment, fLastPass);
}
///
/// Performs a search for work for the given virtual processor only allowing certain types of work to be found.
///
bool SearchForWork(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass, ULONG allowableTypes)
{
return m_searchCtx.Search(pWorkItem, pOriginSegment, fLastPass, allowableTypes);
}
///
/// Performs a yielding search for work for the given virtual processor.
///
bool SearchForWorkInYield(WorkItem *pWork, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass)
{
return m_searchCtx.YieldingSearch(pWork, pOriginSegment, fLastPass);
}
///
/// Performs a yielding search for work for the given virtual processor only allowing certain types of work to be found.
///
bool SearchForWorkInYield(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass, ULONG allowableTypes)
{
return m_searchCtx.YieldingSearch(pWorkItem, pOriginSegment, fLastPass, allowableTypes);
}
///
/// Stub called in SFW before we search for runnable contexts.
///
///
/// A context which should be run.
///
virtual InternalContextBase *PreRunnableSearch()
{
return NULL;
}
///
/// Called when the context running atop this virtual processor has reached a safe point.
///
///
/// An indication of whether the caller should perform a commit.
///
bool SafePoint();
///
/// Gets a location object which represents the virtual processor.
///
const location& GetLocation() const
{
return m_location;
}
//
// TRANSITION: This goes with real priority.
//
void MarkGrabbedPriority()
{
m_fShortAffine = true;
}
///
/// Marks the segment as serviced at a particular time mark.
///
void ServiceMark(ULONGLONG markTime)
{
//
// Avoid cache contention on this by only writing the service time every so often. We only care about this on granularities of something
// like 1/2 seconds anyway -- it's effectively the priority boost time granularity that we care about.
//
if (TimeSinceServicing(markTime) > 100)
m_lastServiceTime = markTime;
}
///
/// Returns the time delta between the last service time and the passed service time.
///
ULONG TimeSinceServicing(ULONGLONG markTime)
{
return (ULONG)(markTime - m_lastServiceTime);
}
///
/// Returns a segment from its internal list entry.
///
static VirtualProcessor* FromBoostEntry(BoostedObject *pEntry)
{
return CONTAINING_RECORD(pEntry, VirtualProcessor, m_priorityServiceLink);
}
protected:
//
// protected data
//
// Indicates whether vproc is available to perform work.
volatile AvailabilityType m_availabilityType{};
// Indicates how the current claimant acquired the vproc.
AvailabilityType m_claimantType{};
// Local caching of realized chores/contexts
StructuredWorkStealingQueue m_localRunnableContexts;
// The search context which keeps track of where this virtual processor is in a search-for-work regardless of algorithm.
WorkSearchContext m_searchCtx;
// Owning scheduling node
SchedulingNode *m_pOwningNode{};
// Owning ring - ring associated with the owning node
SchedulingRing *m_pOwningRing{};
// Owning virtual processor root
IVirtualProcessorRoot *m_pOwningRoot{};
// Sub allocator
SubAllocator *m_pSubAllocator{};
// Tracks the types of work that the virtual processor is currently operating on.
bool m_fAffine{};
bool m_fLocal{};
// Once m_fAffine is clear, we need to post ONE notification to make sure that an affine search happens AFTER
// m_fAffine is set. This tracks the need for the ONE search.
bool m_fShortAffine{};
// The index that this Virtual Processor is at in its list array
int m_listArrayIndex{};
// Statistics data counters
unsigned int m_enqueuedTaskCounter{};
unsigned int m_dequeuedTaskCounter{};
// Statistics data checkpoints
unsigned int m_enqueuedTaskCheckpoint{};
unsigned int m_dequeuedTaskCheckpoint{};
// The context pushed to startup this virtual processor.
InternalContextBase *m_pPushContext{};
// Internal context that is affinitized to and executing on this virtual processor
// This is always an InternalContextBase except on UMS in very special circumstances.
IExecutionContext * volatile m_pExecutingContext{};
// The context which made the virtual processor available.
IExecutionContext * m_pAvailableContext{};
_HyperNonReentrantLock m_lock;
// Unique identifier for vprocs within a scheduler.
unsigned int m_id{};
// The execution resource id for this virtual processor.
unsigned int m_resourceId{};
// The mask id assigned by the scheduler to this virtual processor.
unsigned int m_maskId{};
// Stashed location for this virtual processor.
location m_location;
// The resource mask for this virtual processor.
QuickBitSet m_resourceMask;
// Flag specifying whether this is a virtual processor created as a result of a call to Oversubscribe.
bool m_fOversubscribed{};
// Flag that is set when the virtual processor should remove itself from the scheduler at a yield point,
// i.e, either when the context executing on it calls Block or Yield, or when it is in the dispatch loop
// looking for work.
bool m_fMarkedForRetirement{};
// Whether this virtual processor has reached a safe point in the code
// Used to demark when all virtual processors have reached safe points and a list array deletion
// can occur
bool m_fReachedSafePoint{};
///
/// Causes the virtual processor to remove itself from the scheduler. This is used either when oversubscription
/// ends or when the resource manager asks the vproc to retire.
///
virtual void Retire();
// The internal context that caused this virtual processor to be created, if this is an oversubscribed vproc.
InternalContextBase * m_pOversubscribingContext{};
///
/// Affinitizes an internal context to the virtual processor.
///
///
/// The internal context to affinitize.
///
_Post_satisfies_(this->m_pExecutingContext == pContext)
virtual void Affinitize(InternalContextBase *pContext);
///
/// Returns a type-cast of pContext to an InternalContextBase or NULL if it is not.
///
virtual InternalContextBase *ToInternalContext(IExecutionContext *pContext);
///
/// Initializes the virtual processor. This API is called by the constructor, and when a virtual processor is to
/// be re-initialized, when it is pulled of the free pool in the list array.
///
///
/// The owning schedule node for this virtual processor
///
///
/// The owning IVirtualProcessorRoot
///
virtual void Initialize(SchedulingNode *pOwningNode, IVirtualProcessorRoot *pOwningRoot);
///
/// Makes a virtual processor available for scheduling work.
///
///
/// Indicates whether the routine should update active state for the vproc based upon type or not.
///
void MakeAvailable(AvailabilityType type, bool fCanChangeActiveState = true);
private:
friend class SchedulerBase;
friend class ContextBase;
friend class InternalContextBase;
friend class ThreadInternalContext;
friend class ScheduleGroup;
friend class FairScheduleGroup;
friend class CacheLocalScheduleGroup;
friend class SchedulingNode;
friend class WorkSearchContext;
friend class ClaimTicket;
template friend class ListArray;
template friend class List;
// Links for throttling
VirtualProcessor *m_pNext{};
VirtualProcessor *m_pPrev{};
// Intrusive links for list array.
SLIST_ENTRY m_listArrayFreeLink{};
// The safe point marker.
SafePointMarker m_safePointMarker;
// The last time this virtual processor's LRC was serviced.
ULONGLONG m_lastServiceTime{};
//
// TRANSITION: This is a temporary patch on livelock until we can tie into priority for livelock prevention.
//
bool m_prioritized{}; // Under m_priorityServiceLink's lock.
BoostedObject m_priorityServiceLink{};
///
/// Exercises a claim over the virtual processor.
///
bool ExerciseClaim(AvailabilityType type, ScheduleGroupSegmentBase *pSegment, InternalContextBase *pContext = NULL);
///
/// Start a worker context executing on this.virtual processor.
///
virtual bool StartupWorkerContext(ScheduleGroupSegmentBase* pSegment, InternalContextBase *pContext = NULL);
///
/// Oversubscribes the virtual processor by creating a new virtual processor root affinitized to the same
/// execution resource as that of the current root
///
///
/// A virtual processor that oversubscribes this one.
///
virtual VirtualProcessor * Oversubscribe();
///
/// Marks the virtual processor such that it removes itself from the scheduler, once the context it is executing
/// reaches a safe yield point. Alternatively, if the context has not started executing yet, it can be retired immediately.
///
void MarkForRetirement();
///
/// Steals a context from the local runnables queue of this virtual processor.
///
InternalContextBase *StealLocalRunnableContext()
{
InternalContextBase *pContext = NULL;
if (!m_localRunnableContexts.Empty())
{
pContext = m_localRunnableContexts.Steal();
}
if (pContext != NULL)
{
#if defined(_DEBUG)
::Concurrency::details::SetContextDebugBits(pContext, CTX_DEBUGBIT_STOLENFROMLOCALRUNNABLECONTEXTS);
#endif // _DEBUG
}
return pContext;
}
///
/// Pops a runnable context from the local runnables queue of the vproc, if it can find one.
///
InternalContextBase *GetLocalRunnableContext()
{
if (m_localRunnableContexts.Count() > 0) // Is this check worthwhile? Yes, I believe. We'd take a fence to check otherwise.
{
InternalContextBase *pContext = m_localRunnableContexts.Pop();
#if defined(_DEBUG)
::Concurrency::details::SetContextDebugBits(pContext, CTX_DEBUGBIT_POPPEDFROMLOCALRUNNABLECONTEXTS);
#endif // _DEBUG
return pContext;
}
return NULL;
}
///
/// Resets the count of work coming in.
///
///
/// This function is using modulo 2 behavior of unsigned ints to avoid overflow and
/// reset problems. For more detail look at ExternalStatistics class in externalcontextbase.h.
///
///
/// 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.
///
///
/// This function is using modulo 2 behavior of unsigned ints to avoid overflow and
/// reset problems. For more detail look at the ExternalStatistics class in externalcontextbase.h.
///
///
/// 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;
}
///
/// Send a virtual processor ETW event
///
void TraceVirtualProcessorEvent(ConcRT_EventType eventType, UCHAR level, DWORD schedulerId, DWORD vprocId)
{
if (g_TraceInfo._IsEnabled(level, VirtualProcessorEventFlag))
ThrowVirtualProcessorEvent(eventType, level, schedulerId, vprocId);
}
///
/// Send a virtual processor ETW event
///
static void ThrowVirtualProcessorEvent(ConcRT_EventType eventType, UCHAR level, DWORD schedulerId, DWORD vprocId);
};
} // namespace details
} // namespace Concurrency