// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// Transmogrificator.cpp
//
// A background thread which has the purpose of transmogrifying UTs to make them appear as NT threads to the layer
// above them. This is done via queued creation of a TransmogrifiedPrimary. The reason there is a background thread
// that does this is that we can only make a determination of when to transmogrify at a SwitchTo(..., Nesting) or subsequent
// SwitchOut in the RM. At this point, we **CANNOT** perform a heap allocation without hopelessly confusing the scheduler
// or deadlocking it. But we **MUST** not allow the running thread to continue without performing a heap allocation. The
// catch-22 is solved here by returning the virtual processor (allowing the SwitchTo to happen) and letting it run things
// which may hold the heap lock. The original UT (which certainly isn't holding any blasted locks at this stage) isn't run again
// until this thread can perform the allocation and get it set up.
//
// Note that this thread may **VERY WELL** cache transmogrified primaries in order to avoid having to do the thread creation and
// heap allocations on each nesting.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
///
/// Constructs a new Transmogrificator.
///
Transmogrificator::Transmogrificator()
: m_hWaitHandle(NULL)
, m_hUnblock(NULL)
, m_queuedProxyCount(0)
, m_cacheCount(0)
{
m_hUnblock = CreateEventW(NULL, FALSE, FALSE, NULL); // VSO#459907
if (m_hUnblock == NULL)
{
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
}
InitializeSListHead(&m_cachedProxies);
if (!RegisterWaitForSingleObject(&m_hWaitHandle,
m_hUnblock,
Transmogrificator::TransmogrificationHandler,
this, INFINITE, WT_EXECUTEDEFAULT))
{
throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
}
}
///
/// Destructs the Transmogrificator.
///
Transmogrificator::~Transmogrificator()
{
ASSERT(m_queuedProxyCount == 0);
//
// Get rid of everything in the cache.
//
PSLIST_ENTRY pLE = InterlockedFlushSList(&m_cachedProxies);
while (pLE != NULL)
{
PSLIST_ENTRY pNext = pLE->Next;
CachedTransmogrifiedPrimary *pCachedPrimary = CONTAINING_RECORD(pLE, CachedTransmogrifiedPrimary, m_cacheEntry);
pCachedPrimary->Shutdown();
pLE = pNext;
}
// Cancels the wait and ensure that all callbacks have indeed completed.
if (m_hWaitHandle != NULL)
{
UnregisterWaitEx(m_hWaitHandle, INVALID_HANDLE_VALUE);
}
CloseHandle(m_hUnblock);
}
///
/// Callback handler for m_hUnblock
///
void CALLBACK Transmogrificator::TransmogrificationHandler(PVOID parameter, BOOLEAN)
{
Transmogrificator * pTransmogrificator = reinterpret_cast(parameter);
pTransmogrificator->BeginTransmogrifying();
}
///
/// Performs a transmogrification of pProxy. The Transmogrified primary which is created will not start until
/// UnblockTransmogrification is called.
///
///
/// The thread proxy which is being transmogrified.
///
void Transmogrificator::PerformTransmogrification(UMSThreadProxy *pProxy)
{
//
// This **CANNOT** do anything that cannot be done in a HyperCritical region or on an arbitrary primary! Try to grab one off the cache
// before we defer to the transmogrificator's thread.
//
PSLIST_ENTRY pEntry = InterlockedPopEntrySList(&m_cachedProxies);
if (pEntry != NULL)
{
InterlockedDecrement(&m_cacheCount);
//
// This does not need a fence as there's no race since we cannot unblock it until after this call returns.
//
pProxy->m_pTransmogrification = CONTAINING_RECORD(pEntry, CachedTransmogrifiedPrimary, m_cacheEntry);
}
else
{
//
// There's nothing on the cache. We cannot perform *ANY* allocation or creation of threads here. We must go back to the transmogrificator's
// thread.
//
m_queuedProxies.AddTail(&(pProxy->m_transmogrificatorPendingQueue));
if (InterlockedIncrement(&m_queuedProxyCount) == 1)
SetEvent(m_hUnblock);
}
}
///
/// Unblocks the transmogrification which was created in PerformTransmogrification. Note that PerformTransmogrification
/// must be called first!
///
///
/// The thread proxy whose transmogrification is being unblocked.
///
void Transmogrificator::UnblockTransmogrification(UMSThreadProxy *pProxy)
{
//
// CAS in a magic constant indicating an immediate unblock. If we pull out the primary, we unblock it. If the other side pulls out
// they unblock.
//
TransmogrifiedPrimary *pTransmogrifiedPrimary = reinterpret_cast(
InterlockedCompareExchangePointer((volatile PVOID *)&pProxy->m_pTransmogrification, (PVOID)TRANSMOGRIFICATION_UNBLOCKED, (PVOID)NULL)
);
if (pTransmogrifiedPrimary != NULL)
pTransmogrifiedPrimary->QueueToCompletion(pProxy);
}
///
/// The thread function which awakens when necessary to bind proxies which wish to be transmogrified with new
/// TransmogrifiedPrimary objects.
///
void Transmogrificator::BeginTransmogrifying()
{
do
{
ListEntry *pLE = m_queuedProxies.RemoveHead();
UMSThreadProxy *pProxy = CONTAINING_RECORD(pLE, UMSThreadProxy, m_transmogrificatorPendingQueue);
TransmogrifiedPrimary *pTransmogrifiedPrimary = _concrt_new CachedTransmogrifiedPrimary(this);
if ((PVOID)InterlockedExchangePointer((volatile PVOID *)&pProxy->m_pTransmogrification, pTransmogrifiedPrimary) == (PVOID)TRANSMOGRIFICATION_UNBLOCKED)
pTransmogrifiedPrimary->QueueToCompletion(pProxy);
} while (InterlockedDecrement(&m_queuedProxyCount) > 0);
}
///
/// Called in order to return a cached transmogrified primary to the transmogrificator.
///
void Transmogrificator::ReturnToCache(CachedTransmogrifiedPrimary *pTransmogrifiedPrimary)
{
if (m_cacheCount >= TRANSMOGRIFICATOR_CACHE_DEPTH)
pTransmogrifiedPrimary->Shutdown();
else
{
InterlockedIncrement(&m_cacheCount);
InterlockedPushEntrySList(&m_cachedProxies, &(pTransmogrifiedPrimary->m_cacheEntry));
}
}
} // namespace details
} // namespace Concurrency