// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// UMSThreadScheduler.cpp
//
// Source file containing the implementation for a UMS thread based concrt scheduler
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#pragma once
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
DWORD UMSThreadScheduler::t_dwSchedulingContextIndex;
///
/// Creates a UMS thread based scheduler
///
UMSThreadScheduler::UMSThreadScheduler(_In_ const ::Concurrency::SchedulerPolicy& policy) :
SchedulerBase(policy),
m_pCompletionList(NULL)
{
}
///
/// Creates a UMS thread based scheduler
///
UMSThreadScheduler* UMSThreadScheduler::Create(_In_ const ::Concurrency::SchedulerPolicy& policy)
{
return _concrt_new UMSThreadScheduler(policy);
}
///
/// Creates a UMS thread based virtual processor.
///
VirtualProcessor* UMSThreadScheduler::CreateVirtualProcessor(SchedulingNode *pOwningNode, IVirtualProcessorRoot* pOwningRoot)
{
return _concrt_new UMSThreadVirtualProcessor(pOwningNode, pOwningRoot);
}
///
/// Creates a new thread internal context and returns it to the base scheduler.
///
InternalContextBase *UMSThreadScheduler::CreateInternalContext()
{
return _concrt_new UMSThreadInternalContext(this);
}
///
/// Destroys a UMS thread based scheduler
///
UMSThreadScheduler::~UMSThreadScheduler()
{
}
///
/// Notification after a virtual processor goes from INACTIVE to ACTIVE or ACTIVE to INACTIVE
/// For UMS we need to ensure that there is at least 1 active vproc.
///
///
/// True if a virtual processor is going from INACTIVE to ACTIVE, and false if it is going from ACTIVE to INACTIVE.
///
///
/// Active virtual processor count after the transition
///
void UMSThreadScheduler::VirtualProcessorActiveNotification(bool fActive, LONG activeCount)
{
// We need to maintain at least 1 active virtual processor as long
// as there is work. Since we cannot easily determine if there are blocked UMS context,
// do not allow active vproc count to go from 1 to 0.
if (activeCount == 0)
{
// If we are the last active virtual processor, we should be in a hyper cricital region
CONCRT_COREASSERT(!fActive);
StartupIdleVirtualProcessor(GetNextSchedulingRing()->GetAnonymousScheduleGroupSegment());
}
}
///
/// Called in order to move the completion list to the runnables lists.
///
///
/// Bias any awakening of virtual processors to this location.
///
///
/// Whether there was anything on the completion list when queried.
///
bool UMSThreadScheduler::MoveCompletionListToRunnables(location bias)
{
bool fHadItems = false;
//
// This *ABSOLUTELY* must be in a hyper-critical region. Deadlock can ensue if not. Anything outward from this
// must follow the set of rules governing a hyper-critical region.
//
ContextBase *pCurrentContext = SchedulerBase::FastCurrentContext();
if (pCurrentContext != NULL)
pCurrentContext->EnterHyperCriticalRegion();
IUMSUnblockNotification *pUnblock = m_pCompletionList->GetUnblockNotifications();
while (pUnblock != NULL)
{
fHadItems = true;
IUMSUnblockNotification *pNext = pUnblock->GetNextUnblockNotification();
UMSThreadInternalContext *pContext = static_cast (pUnblock->GetContext());
VCMTRACE(MTRACE_EVT_PULLEDFROMCOMPLETION, pContext, (pCurrentContext && !pCurrentContext->IsExternal()) ? static_cast(pCurrentContext)->m_pVirtualProcessor : NULL, pCurrentContext);
#if defined(_DEBUG)
pContext->SetDebugBits(CTX_DEBUGBIT_PULLEDFROMCOMPLETIONLIST);
#endif // _DEBUG
//
// In order to know what to do with this particular item, we need to know *why* it blocked. If the primary hasn't gotten to telling us that,
// we must spin.
//
UMSThreadInternalContext::BlockingType blockingType = pContext->SpinOnAndReturnBlockingType();
CONCRT_COREASSERT(blockingType != UMSThreadInternalContext::BlockingNone);
//
// Make a determination of where this item goes. There are several cases here:
//
// - It might have UMS blocked during a normal critical region (e.g.: the main dispatch loop blocked on the heap lock or some
// other similar object). If the context was inside a critical region, we have special determinations to make.
//
// - It might just be a runnable.
//
switch(blockingType)
{
case UMSThreadInternalContext::BlockingCritical:
//
// This is the single special context allowed to be "inside a critical region" on the virtual processor. Signal the virtual
// processor specially.
//
VCMTRACE(MTRACE_EVT_CRITICALNOTIFY, pContext, (pCurrentContext && !pCurrentContext->IsExternal()) ? static_cast(pCurrentContext)->m_pVirtualProcessor : NULL, pCurrentContext);
#if defined(_DEBUG)
pContext->SetDebugBits(CTX_DEBUGBIT_CRITICALNOTIFY);
#endif // _DEBUG
pContext->m_pLastVirtualProcessor->CriticalNotify();
break;
case UMSThreadInternalContext::BlockingNormal:
//
// If it's a normal runnable, it just goes on the runnables list. We pass along the bias to indicate which virtual processor
// (or owning node) we prefer to awaken due to the addition of runnables.
//
pContext->AddToRunnables(bias);
break;
}
pUnblock = pNext;
}
if (pCurrentContext != NULL)
pCurrentContext->ExitHyperCriticalRegion();
return fHadItems;
}
///
/// Static initialization common to UMS schedulers.
///
void UMSThreadScheduler::OneShotStaticConstruction()
{
t_dwSchedulingContextIndex = TlsAlloc();
if (t_dwSchedulingContextIndex == TLS_OUT_OF_INDEXES)
{
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
}
}
///
/// Static destruction common to UMS schedulers.
///
void UMSThreadScheduler::OneShotStaticDestruction()
{
TlsFree(t_dwSchedulingContextIndex);
t_dwSchedulingContextIndex = 0;
}
} // namespace details
} // namespace Concurrency