// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// TransmogrifiedPrimary.cpp
//
// A very special primary thread whose sole purpose is to "virtually" transmogrify a UMS thread into an NT thread
// for the purposes of scheduler nesting.
//
// Specifically, this thread servers the following functions:
// 1. Run thread proxies to their thread main
// 2. Retire thread proxies
// 3. Allow thread proxies to run external to vproc (in the case of nesting scheduler).
// 4. Poll for thread proxies that are suspended
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
///
/// Destroy a UMSBaseObject
///
UMSBaseObject::~UMSBaseObject()
{
}
///
/// Construct a transmogrified primary
///
TransmogrifiedPrimary::TransmogrifiedPrimary() :
m_pBoundProxy(NULL),
m_hCompletionListEvent(NULL),
m_hBlock(NULL),
m_hRetire(NULL),
m_queueCount(0)
{
Initialize();
}
///
/// Initialize the transmogrified primary.
///
void TransmogrifiedPrimary::Initialize()
{
if (!UMS::CreateUmsCompletionList(&m_pCompletionList))
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
if (!UMS::GetUmsCompletionListEvent(m_pCompletionList, &m_hCompletionListEvent))
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
m_hRetire = CreateEventW(NULL, FALSE, FALSE, NULL);
if (m_hRetire == NULL)
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
m_hBlock = CreateEventW(NULL, FALSE, FALSE, NULL);
if (m_hBlock == NULL)
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
m_hPrimary = LoadLibraryAndCreateThread(NULL,
0,
PrimaryMain,
this,
0,
&m_primaryId);
if (m_hPrimary == NULL)
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
}
///
/// Destroys the transmogrified primary.
///
TransmogrifiedPrimary::~TransmogrifiedPrimary()
{
CloseHandle(m_hPrimary);
if (m_hRetire != NULL)
{
CloseHandle(m_hRetire);
}
if (m_hBlock != NULL)
{
CloseHandle(m_hBlock);
}
if (m_hCompletionListEvent != NULL)
{
CloseHandle(m_hCompletionListEvent);
}
UMS::DeleteUmsCompletionList(m_pCompletionList);
}
///
/// Queues a thread to execute to completion and asynchronously returns.
///
///
/// The proxy to queue to completion.
///
void TransmogrifiedPrimary::QueueToCompletion(UMSThreadProxy *pProxy)
{
// We need to use a hypercritical lock here since
// this is called from the primary as well.
UMSThreadProxy *pCurrentProxy = UMSThreadProxy::GetCurrent();
if (pCurrentProxy != NULL)
{
pCurrentProxy->EnterHyperCriticalRegion();
}
m_queuedExecutions.Enqueue(pProxy);
if (pCurrentProxy != NULL)
{
pCurrentProxy->ExitHyperCriticalRegion();
}
if (InterlockedIncrement(&m_queueCount) == 1)
{
Unblock();
}
}
///
/// The UMS primary function. This is invoked when the primary switches into UMS scheduling mode or whenever a given
/// context blocks or yields.
///
///
/// The reason for the UMS invocation.
///
///
/// The activation payload (depends on reason)
///
///
/// The context (the primary pointer)
///
void NTAPI TransmogrifiedPrimary::PrimaryInvocation(UMS_SCHEDULER_REASON reason, ULONG_PTR activationPayload, PVOID pData)
{
(activationPayload);
TransmogrifiedPrimary *pRoot = NULL;
PUMS_CONTEXT pPrimaryContext = UMS::GetCurrentUmsThread();
if (reason != UmsSchedulerStartup)
{
//
// activationPayload and pData might be NULL (blocking), so we're left with storing the TransmogrifiedPrimary in either
// TLS or the UMS context (the primary does have one). At present, it's in the UMS context.
//
if (!UMS::QueryUmsThreadInformation(pPrimaryContext, UmsThreadUserContext, &pRoot, sizeof(pRoot), NULL))
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
}
else
{
pRoot = reinterpret_cast(pData);
if (!UMS::SetUmsThreadInformation(pPrimaryContext, UmsThreadUserContext, &pRoot, sizeof(pRoot)))
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
}
UMSThreadProxy *pProxy = NULL;
switch(reason)
{
case UmsSchedulerStartup:
{
pProxy = pRoot->WaitForWork();
if (pProxy == NULL)
{
//
// No work was found. We are done
//
return;
}
pRoot->Execute(pProxy);
CONCRT_COREASSERT(false);
break;
}
case UmsSchedulerThreadBlocked:
{
pProxy = pRoot->HandleBlocking();
if (pProxy == NULL)
{
//
// No work was found. We are done
//
return;
}
pRoot->Execute(pProxy);
CONCRT_COREASSERT(false);
break;
}
case UmsSchedulerThreadYield:
{
pProxy = pRoot->HandleYielding();
if (pProxy == NULL)
{
//
// No work was found. We are done.
//
return;
}
pRoot->Execute(pProxy);
CONCRT_COREASSERT(false);
break;
}
default:
CONCRT_COREASSERT(false);
break;
}
}
///
/// Handle blocking for a UT on this primary
///
UMSThreadProxy * TransmogrifiedPrimary::HandleBlocking()
{
UMSThreadProxy *pProxy = m_pBoundProxy;
CONCRT_COREASSERT(pProxy != NULL);
//
// Wait for the blocked thread to complete
//
WaitForBlockedThread(pProxy);
//
// If the thread terminated, either someone did an ExitThread or the thread we meant to run to completion did. If so, block.
//
if (pProxy->IsTerminated())
{
//
// This is the **FIRST** place it's safe to delete the proxy and move on.
//
delete pProxy;
m_pBoundProxy = NULL;
//
// Search for new work
//
return SearchForWork();
}
else
{
//
// proxy has not run to completion yet.
//
return pProxy;
}
}
///
/// Handle yielding for a UT on this primary
///
UMSThreadProxy * TransmogrifiedPrimary::HandleYielding()
{
UMSThreadProxy *pProxy = m_pBoundProxy;
CONCRT_COREASSERT(pProxy != NULL);
switch(pProxy->m_yieldAction)
{
case UMSThreadProxy::ActionStartup:
{
//
// UT startup
//
UMSFreeThreadProxy * pStartedProxy = static_cast(pProxy);
pStartedProxy->m_yieldAction = UMSThreadProxy::ActionNone;
SetEvent(pStartedProxy->m_hBlock);
break;
}
default:
{
//
// When the thread explicity yields, it's blocked as far as we're concerned and someone else can run it. This would be the case
// on an exit from nesting.
//
pProxy->NotifyBlocked(false);
break;
}
};
m_pBoundProxy = NULL;
return SearchForWork();
}
///
/// Execute the given proxy on this primary
///
///
/// The proxy to execute
///
void TransmogrifiedPrimary::Execute(UMSThreadProxy *pProxy)
{
CONCRT_COREASSERT(pProxy != NULL);
m_pBoundProxy = pProxy;
int retryCount = 0;
for(;;)
{
UMS::ExecuteUmsThread(pProxy->GetUMSContext());
CONCRT_COREASSERT(!pProxy->IsTerminated());
Sleep(0);
// Poll at regular intervals
if (++retryCount == 100)
{
m_poller.DoPolling();
retryCount = 0;
}
}
}
///
/// Search for work queued in the case of multiple binding
///
UMSThreadProxy * TransmogrifiedPrimary::SearchForWork()
{
CONCRT_COREASSERT(m_pBoundProxy == NULL);
//
// This decrement is for the PREVIOUSLY executed work item.
//
_InterlockedDecrement(&m_queueCount);
m_pBoundProxy = m_queuedExecutions.Dequeue();
if (m_pBoundProxy != NULL)
{
return m_pBoundProxy;
}
CompletedTransmogrification();
//
// Dequeue new work and bind it to the primary
//
return WaitForWork();
}
///
/// Wait for a proxy to appear on the completion list
///
UMSThreadProxy * TransmogrifiedPrimary::WaitForBlockedThread(UMSThreadProxy * pProxy)
{
//
// While waiting on the completion list we need to poll proxies for execution, if any.
// This is required because the current proxy could be blocked for a resource that is
// held by a UT that is suspended (and needs to be polled for subsequent execution).
//
const int maxCount = 2;
HANDLE hObjects[maxCount];
int count = 0;
hObjects[count++] = m_poller.GetEvent();
hObjects[count++] = m_hCompletionListEvent;
CONCRT_COREASSERT(count == maxCount);
DWORD timeout = INFINITE;
for(;;)
{
DWORD result = WaitForMultipleObjectsEx(count, hObjects, FALSE, timeout, FALSE);
DWORD index = (result == WAIT_TIMEOUT) ? 0 : (result - WAIT_OBJECT_0);
if (index == 0)
{
bool done = m_poller.DoPolling();
//
// Poll every interval
//
timeout = done ? INFINITE : UMSBackgroundPoller::PollInterval();
}
else
{
CONCRT_COREASSERT(index == 1);
// Proxy came back on the completion list
PUMS_CONTEXT pUMSContext = NULL;
if (!UMS::DequeueUmsCompletionListItems(m_pCompletionList, 0, &pUMSContext))
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
//
// The completed thread should be the one we are running
//
UMSThreadProxy *pCompletedProxy = UMSThreadProxy::FromUMSContext(pUMSContext);
CONCRT_COREASSERT(pCompletedProxy == pProxy && UMS::GetNextUmsListItem(pUMSContext) == NULL);
return pCompletedProxy;
}
}
}
///
/// Wait for new work
///
UMSThreadProxy * TransmogrifiedPrimary::WaitForWork()
{
//
// There are 3 possibilities here
// 1. A proxy needs to be polled for execution
// 2. A proxy needs to be transmogrified/retired/run to thread main
// 3. This background thread needs to be retired
//
const int maxCount = 3;
HANDLE hObjects[maxCount];
int count = 0;
hObjects[count++] = m_poller.GetEvent();
hObjects[count++] = m_hBlock;
hObjects[count++] = m_hRetire;
CONCRT_COREASSERT(count == maxCount);
DWORD timeout = INFINITE;
for(;;)
{
DWORD result = WaitForMultipleObjectsEx(count, hObjects, FALSE, timeout, FALSE);
DWORD index = (result == WAIT_TIMEOUT) ? 0 : (result - WAIT_OBJECT_0);
if (index == 0)
{
bool done = m_poller.DoPolling();
//
// Poll every interval
//
timeout = done ? INFINITE : UMSBackgroundPoller::PollInterval();
}
else if (index == 1)
{
//
// Dequeue new work and bind it to the primary. It is possible
// that we already picked up the entry that signalled the event.
//
m_pBoundProxy = m_queuedExecutions.Dequeue();
if (m_pBoundProxy != NULL)
{
return m_pBoundProxy;
}
}
else
{
//
// Canceled
//
CONCRT_COREASSERT(index == 2);
CONCRT_COREASSERT(m_queueCount == 0);
CONCRT_COREASSERT(timeout == INFINITE);
return NULL;
}
}
}
///
/// The primary thread for this transmogrified primary.
///
///
/// The TransmogrifiedPrimary that this thread manages.
///
DWORD CALLBACK TransmogrifiedPrimary::PrimaryMain(LPVOID pContext)
{
TransmogrifiedPrimary *pPrimary = reinterpret_cast(pContext);
UMS_SCHEDULER_STARTUP_INFO si;
si.UmsVersion = UMS_VERSION;
si.CompletionList = pPrimary->m_pCompletionList;
si.SchedulerProc = (PUMS_SCHEDULER_ENTRY_POINT) &PrimaryInvocation;
si.SchedulerParam = pContext;
if (!UMS::EnterUmsSchedulingMode(&si))
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
delete pPrimary;
FreeLibraryAndDestroyThread(0);
return 0;
}
///
/// Construct a new cached transmogrified primary. The primary will be placed back upon the cache of the specified transmogrificator
/// at the end of each transmogrification.
///
CachedTransmogrifiedPrimary::CachedTransmogrifiedPrimary(Transmogrificator *pTransmogrificator) :
m_pTransmogrificator(pTransmogrificator)
{
}
///
/// Called when a transmogrification is complete.
///
void CachedTransmogrifiedPrimary::CompletedTransmogrification()
{
m_pTransmogrificator->ReturnToCache(this);
}
} // namespace details
} // namespace Concurrency