// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// FreeThreadProxy.cpp
//
// Part of the ConcRT Resource Manager -- this source file contains the internal definition for the free thread
// proxy.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
///
/// Called in order to perform a cooperative context switch between one context and another. After this call, pContext will
/// be running atop the virtual processor root and the context which was running will not. What happens to the context that
/// was running depends on the value of the reason argument.
///
///
/// The context to cooperatively switch to.
///
///
/// Indicates the state of the thread proxy that is executing the switch. This can determine ownership of the underlying thread
/// proxy and context.
///
void FreeThreadProxy::SwitchTo(Concurrency::IExecutionContext *pContext, SwitchingProxyState switchState)
{
if (pContext == NULL)
throw std::invalid_argument("pContext");
// Find out if this context already has a thread proxy, if not, we have to request one from the factory.
FreeThreadProxy * pProxy = static_cast (pContext->GetProxy());
if (pProxy == NULL)
{
// Find a thread proxy from the pool that corresponds to the stack size and priority we need. Since this
// is a context in the same scheduler as the current context's scheduler, we can use existing values of
// stack size and priority.
pProxy = static_cast (m_pRoot->GetSchedulerProxy()->GetNewThreadProxy(pContext));
}
FreeVirtualProcessorRoot *pRoot = static_cast(m_pRoot);
m_pRoot = NULL;
if (switchState == Blocking)
{
ASSERT(m_fBlocked == FALSE);
InterlockedExchange(&m_fBlocked, TRUE);
}
// The 'next' thread proxy must be affinitized to a copy of the 'this' proxy's vproc root VPRoot1, snapped BEFORE the blocked flag
// is set. Not doing this could result in vproc root orphanage. See VirtualProcessorRoot::Affinitize for details.
pRoot->Affinitize(pProxy);
switch (switchState)
{
case Blocking:
//
// Signal the other thread proxy and block until switched to, or until a virtual processor is activated with
// the context running on this thread proxy.
//
platform::__SignalObjectAndWait(pProxy->m_hBlock, m_hBlock, INFINITE, TRUE);
ASSERT(m_fBlocked == TRUE);
InterlockedExchange(&m_fBlocked, FALSE);
break;
case Nesting:
//
// Signal the other thread proxy that now owns this virtual processor, but do not block. The current thread proxy
// is about to move to a nested scheduler.
//
ASSERT(pProxy->m_pRoot != NULL);
ASSERT(pProxy->m_pContext != NULL);
pProxy->ResumeExecution();
break;
case Idle:
//
// Return without blocking, indicating to the caller that the scheduler should yield this thread proxy
// back to the RM, by exiting the contexts dispatch loop.
//
ASSERT(pProxy->m_pRoot != NULL);
ASSERT(pProxy->m_pContext != NULL);
pProxy->ResumeExecution();
break;
default:
ASSERT(false);
break;
}
}
///
/// Called in order to disassociate the currently executing context from its virtual processor root, and reinitialize the root
/// for future use.
///
///
/// Indicates the state of the thread proxy that is executing the switch. This can determine ownership of the underlying thread
/// proxy and context.
///
void FreeThreadProxy::SwitchOut(SwitchingProxyState switchState)
{
if ((switchState == Idle) || (m_pRoot == NULL && switchState != Blocking))
throw std::invalid_argument("switchState");
ASSERT(m_fBlocked == 0);
//
// If a virtual processor root is removed on the thread running atop it, the virtual processor root's removal will NULL out this field indicating
// that we are now a free thread. If there is a virtual processor root, the scheduler still wants to keep the vproc root around and we must
// correspondingly act as both a switch out and a deactivate.
//
if (m_pRoot != NULL)
{
FreeVirtualProcessorRoot * pRoot = static_cast(m_pRoot);
if (switchState == Nesting)
{
// IThreadProxy::SwitchOut can be called with Nesting, if the context tried to InternalContextBase::SwitchTo(NULL, Nesting). Ensure the
// root is set to NULL here so the right thing happens with this context/proxy rejoins the scheduler by calling IThreadProxy::SwitchOut(Blocking).
m_pRoot = NULL;
}
(static_cast(pRoot))->ResetOnIdle(switchState);
// If we're nesting, we should return without blocking with the root unchanged. If not, we should have been affinitized to a different root.
ASSERT(m_pRoot != NULL || switchState == Nesting);
}
else
{
// There are currently only two cases where the m_pRoot field is expected to be NULL.
// - A virtual processor is being retired and the caller invokes SwitchOut to block the thread proxy.
// (root was set to NULL in FreeVirtualProcessorRoot::DeleteThis)
// - A thread proxy that previously switched to a different, nested scheduler, is now joining its original scheduler again.
// (root was set to NULL in FreeThreadProxy::SwitchOut or FreeThreadProxy::SwitchTo)
SuspendExecution();
}
}
///
/// Called right after obtaining a thread proxy from the factory. Associates the thread proxy with the execution
/// context it is about to run.
///
void FreeThreadProxy::AssociateExecutionContext(Concurrency::IExecutionContext * pContext)
{
m_pContext = pContext;
pContext->SetProxy(this);
}
///
/// Returns a thread proxy to the factory when it is no longer in use.
///
void FreeThreadProxy::ReturnIdleProxy()
{
_CONCRT_ASSERT(m_pFactory != NULL);
m_pContext = NULL;
m_pFactory->ReclaimProxy(this);
}
///
/// The main dispatch loop for the free thread proxy.
///
void FreeThreadProxy::Dispatch()
{
// Send the default dispatch state into Dispatch.
DispatchState dispatchState;
if (!m_fCanceled)
{
platform::__TlsSetValue(m_pFactory->GetExecutionResourceTls(), (LPVOID) (((size_t) this) | TlsResourceInProxy));
}
while (!m_fCanceled)
{
_CONCRT_ASSERT(m_pContext != NULL);
_CONCRT_ASSERT(m_pRoot != NULL);
// Call the dispatch loop of the registered context.
m_pContext->SetProxy(this);
m_pContext->Dispatch(&dispatchState);
//
// The dispatch loop returns when the scheduler that the proxy was given to, has decided to return it back to the RM.
// It should be returned to the free proxy factory, so that it can be handed out to a different virtual processor root
// (bound to a different context).
//
// Before doing so, however, we restore the virtual processor to its original state so that it can be activated again. Note
// that if the virtual processor deleted on the way out, m_pRoot is already NULL. This is only thread which does this and
// we are on the same thread. There is no race.
//
FreeVirtualProcessorRoot *pRoot = static_cast(m_pRoot);
m_pContext = NULL;
m_pRoot = NULL;
// Return to the idle pool in the RM. If the pool is full, the proxy will be canceled.
ReturnIdleProxy();
if (pRoot != NULL)
{
pRoot->ResetOnIdle(Blocking);
}
else
{
SuspendExecution();
}
}
}
} // namespace details
} // namespace Concurrency