// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// Chores.cpp
//
// Miscellaneous implementations of things related to individuals chores
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
///
/// Sets the attachment state of the chore at the time of stealing.
///
void _UnrealizedChore::_SetDetached(bool _FDetached)
{
_M_fDetached = _FDetached;
}
///
/// To free memory allocated with _InternalAlloc.
///
void _UnrealizedChore::_InternalFree(_UnrealizedChore * _PChore)
{
ASSERT(_PChore->_M_fRuntimeOwnsLifetime);
delete _PChore;
}
///
/// Place associated task collection in a safe state.
///
void _UnrealizedChore::_CheckTaskCollection()
{
//
// If _M_pTaskCollection is non-NULL, the chore is still scheduled to a task collection. This is only happening
// from a handle destructor and we have blown back through a stack based handle while it's still scheduled. We must
// wait. The semantic we choose is that this means cancellation too.
//
Concurrency::details::_TaskCollectionBase *pBase = _M_pTaskCollection;
if (pBase != NULL)
{
bool fThrow = false;
if (_M_pChoreFunction == &_UnrealizedChore::_StructuredChoreWrapper)
{
_StructuredTaskCollection *pTaskCollection = static_cast<_StructuredTaskCollection *>(pBase);
fThrow = !pTaskCollection->_TaskCleanup();
}
else
{
_TaskCollection *pTaskCollection = static_cast<_TaskCollection *>(pBase);
fThrow = !pTaskCollection->_TaskCleanup(true);
}
if (fThrow)
throw missing_wait();
}
}
///
/// Prepares for execution as a stolen chore.
///
void _UnrealizedChore::_PrepareSteal(ContextBase *pContext)
{
if (_M_pChoreFunction == &_UnrealizedChore::_StructuredChoreWrapper)
{
_PrepareStealStructured(pContext);
}
else
{
_PrepareStealUnstructured(pContext);
}
}
///
/// Called when a stolen chore from a given cancellation token is canceled.
///
void _UnrealizedChore::_CancelViaToken(ContextBase *pContext)
{
pContext->CancelEntireContext();
pContext->CancelStealers(NULL);
}
///
/// Prepares for execution as a stolen chore.
///
void _UnrealizedChore::_PrepareStealStructured(ContextBase *pBaseContext)
{
InternalContextBase *pContext = static_cast (pBaseContext);
if (pContext->GetRootCollection() == NULL)
{
_StructuredTaskCollection *pTaskCollection = static_cast<_StructuredTaskCollection *> (_M_pTaskCollection);
ContextBase *pOriginContext = reinterpret_cast (pTaskCollection->_M_pOwningContext);
pContext->SetRootCollection(pTaskCollection);
pOriginContext->AddStealer(pContext, false);
}
}
///
/// Wrapper around execution of a structured chore that performs appropriate notification
/// and exception handling semantics.
///
__declspec(noinline)
void __cdecl _UnrealizedChore::_StructuredChoreWrapper(_UnrealizedChore * pChore)
{
InternalContextBase *pContext = static_cast (SchedulerBase::FastCurrentContext());
// The context could be canceled if it was already prepared for steal (this happens during a block unblock race)
ASSERT(pContext != NULL && (!pContext->HasInlineCancellation() || pContext->GetRootCollection() != NULL));
_StructuredTaskCollection *pTaskCollection = static_cast<_StructuredTaskCollection *> (pChore->_M_pTaskCollection);
ContextBase *pOriginContext = reinterpret_cast (pTaskCollection->_M_pOwningContext);
pChore->_PrepareStealStructured(pContext);
//
// This allows cancellation of stolen chores based on a cancellation token between the declaration of a stg and its inlining.
//
_CancellationTokenState *pTokenState = pTaskCollection->_GetTokenState();
_CancellationTokenRegistration *pRegistration = NULL;
if (_CancellationTokenState::_IsValid(pTokenState))
{
pRegistration = pTokenState->_RegisterCallback(reinterpret_cast(&_UnrealizedChore::_CancelViaToken), (ContextBase *)pContext);
}
try
{
//
// We need to consider this a possible interruption point. It's entirely possible that we stole and raced with a
// cancellation thread. The collection was canceled after we stole(e.g.: removed from the WSQ) but before we added ourselves
// to the stealing chain list above. In this case, the entire context will wait until completion (bad). Immediately
// after we go on the list (a memory barrier) we need to check the collection cancellation flag. If the collection is going away,
// we need to get out *NOW* otherwise the entire subtree executes.
//
if (pTaskCollection->_IsAbnormalExit())
throw _Interruption_exception();
pChore->m_pFunction(pChore);
}
catch(const _Interruption_exception &)
{
//
// If someone manually threw the _Interruption_exception exception, we will have a cancel count but not a canceled context. This
// means we need to apply the cancel one level up. Normally, the act of throwing would do that via being caught in the
// wait, but this is special "marshaling" for _Interruption_exception.
//
if (pContext->HasInlineCancellation() && !pContext->IsEntireContextCanceled())
pTaskCollection->_Cancel();
}
catch(...)
{
//
// Track the exception that was thrown here. _RaisedException makes the decision on what
// exceptions to keep and what to discard. The flags it sets will indicate to the thread calling ::Wait that it must rethrow.
//
pTaskCollection->_RaisedException();
pTaskCollection->_Cancel();
}
pOriginContext->RemoveStealer(pContext);
ASSERT(pContext->GetGoverningTokenState() == NULL);
//
// This allows cancellation of stolen chores based on a cancellation token between the declaration of a stg and its inlining.
//
if (pRegistration != NULL)
{
ASSERT(pTokenState != NULL);
pTokenState->_DeregisterCallback(pRegistration);
pRegistration->_Release();
}
pContext->ClearCancel();
pContext->SetRootCollection(NULL);
pChore->_M_pTaskCollection = NULL;
pTaskCollection->_CountUp();
}
///
/// Prepares for execution as a stolen chore.
///
void _UnrealizedChore::_PrepareStealUnstructured(ContextBase *pBaseContext)
{
InternalContextBase *pContext = static_cast (pBaseContext);
if (pContext->GetRootCollection() == NULL)
{
_TaskCollection* pTaskCollection = static_cast<_TaskCollection *> (_M_pTaskCollection);
ContextBase *pOriginContext = reinterpret_cast (pTaskCollection->_M_pOwningContext);
pContext->SetRootCollection(pTaskCollection);
//
// pOriginContext is only safe to touch if the act of stealing from a non-detached context put a hold on that context
// to block deletion until we are chained for cancellation.
//
SafeRWList *pList = reinterpret_cast *> (pTaskCollection->_M_stealTracker);
ASSERT(sizeof(pTaskCollection->_M_stealTracker) >= sizeof(*pList));
if (_M_fDetached)
{
//
// We cannot touch the owning context -- it was detached as of the steal. The chain goes onto the task collection.
//
pContext->NotifyTaskCollectionChainedStealer();
pList->AddTail(&(pContext->m_stealChain));
}
else
{
pList->AcquireWrite();
pTaskCollection->_M_activeStealersForCancellation++;
pList->ReleaseWrite();
pOriginContext->AddStealer(pContext, true);
}
}
}
///
/// Wrapper around execution of an unstructured chore that performs appropriate notification
/// and exception handling semantics.
///
__declspec(noinline)
void __cdecl _UnrealizedChore::_UnstructuredChoreWrapper(_UnrealizedChore * pChore)
{
InternalContextBase *pContext = static_cast (SchedulerBase::FastCurrentContext());
// The context could be canceled if it was already prepared for steal (this happens during a block unblock race)
ASSERT(pContext != NULL && (!pContext->HasInlineCancellation() || pContext->GetRootCollection() != NULL));
_TaskCollection* pTaskCollection = static_cast<_TaskCollection *> (pChore->_M_pTaskCollection);
//
// pOriginContext is only safe to touch if the act of stealing from a non-detached context put a hold on that context
// to block deletion until we are chained for cancellation.
//
ContextBase *pOriginContext = reinterpret_cast (pTaskCollection->_M_pOwningContext);
SafeRWList *pList = reinterpret_cast *> (pTaskCollection->_M_stealTracker);
pChore->_PrepareStealUnstructured(pContext);
_CancellationTokenState *pTokenState = pTaskCollection->_GetTokenState();
_CancellationTokenRegistration *pRegistration = NULL;
if (_CancellationTokenState::_IsValid(pTokenState))
{
pRegistration = pTokenState->_RegisterCallback(reinterpret_cast(&_UnrealizedChore::_CancelViaToken), (ContextBase *)pContext);
}
//
// Waiting on the indirect alias may throw (e.g.: the entire context may have been canceled). If it
// throws, we need to deal with appropriate marshaling.
//
try
{
//
// Set up an indirect alias for this task collection. Any usage of the original task collection
// within this stolen chore will automatically redirect through the indirect alias. This allows
// preservation of single-threaded semantics within the task collection while allowing it to be "accessed"
// from stolen chores (multiple threads).
//
// This stack based collection will wait on stolen chores at destruction time. In the event the collection is not
// used during the steal, this doesn't do much.
//
_TaskCollection indirectAlias(pTaskCollection, false);
pContext->SetIndirectAlias(&indirectAlias);
try
{
//
// We need to consider this a possible interruption point. It's entirely possible that we stole and raced with a
// cancellation thread. The collection was canceled after we stole(e.g.: removed from the WSQ) but before we added ourselves
// to the stealing chain list above. In this case, the entire context will wait until completion (bad). Immediately
// after we go on the list (a memory barrier), we need to check the collection cancellation flag. If the collection is going away,
// we need to get out *NOW* otherwise the entire subtree executes.
//
if (pTaskCollection->_M_pOriginalCollection->_M_exitCode != 0 ||
(_CancellationTokenState::_IsValid(pTokenState) && pTokenState->_IsCanceled()) ||
(pTaskCollection->_M_executionStatus != TASKCOLLECTION_EXECUTION_STATUS_CLEAR &&
pTaskCollection->_M_executionStatus != TASKCOLLECTION_EXECUTION_STATUS_INLINE &&
pTaskCollection->_M_executionStatus != TASKCOLLECTION_EXECUTION_STATUS_INLINE_WAIT_WITH_OVERFLOW_STACK))
throw _Interruption_exception();
pChore->m_pFunction(pChore);
}
catch(const _Interruption_exception &)
{
//
// If someone manually threw _Interruption_exception, we will have a cancel count but not a canceled context. This
// means we need to apply the cancel one level up. Normally, the act of throwing would do that via being caught in the
// wait, but this is special "marshaling" for _Interruption_exception.
//
if (pContext->HasInlineCancellation() && !pContext->IsEntireContextCanceled())
pTaskCollection->_Cancel();
}
catch(...)
{
//
// Track the exception that was thrown here and subsequently cancel all work. _RaisedException makes the decision on what
// exceptions to keep and what to discard. The flags it sets will indicate to the thread calling ::Wait that it must rethrow.
//
pTaskCollection->_RaisedException();
pTaskCollection->_Cancel();
}
indirectAlias._Wait();
}
catch(const _Interruption_exception &)
{
//
// If someone manually threw _Interruption_exception out of a task on the indirect alias, the same thing applies as to
// a directly stolen chore (above).
//
if (pContext->HasInlineCancellation() && !pContext->IsEntireContextCanceled())
pTaskCollection->_Cancel();
}
catch(...)
{
//
// Track the exception that was thrown here and subsequently cancel all work. _RaisedException makes the decision on what
// exceptions to keep and what to discard. The flags it sets will indicate to the thread calling ::Wait that it must rethrow.
//
pTaskCollection->_RaisedException();
pTaskCollection->_Cancel();
}
pContext->SetIndirectAlias(NULL);
ASSERT(pContext->GetGoverningTokenState() == NULL);
if ( !pChore->_M_fDetached)
{
//
// pOriginContext may die at any point (detachment). When it does, it will transfer the stolen chore trace from the context to the
// given task collection (us) under lock. We can, therefore, take this lock and check if we are still okay to check the context.
//
pList->AcquireWrite();
if (pContext->IsContextChainedStealer())
pOriginContext->RemoveStealer(pContext);
else
pList->UnlockedRemove(&(pContext->m_stealChain));
pTaskCollection->_M_activeStealersForCancellation--;
pList->ReleaseWrite();
}
else
{
pList->Remove(&(pContext->m_stealChain));
}
if (pRegistration != NULL)
{
pTokenState->_DeregisterCallback(pRegistration);
pRegistration->_Release();
}
pContext->ClearCancel();
pContext->ClearAliasTable();
pContext->SetRootCollection(NULL);
pChore->_M_pTaskCollection = NULL;
pTaskCollection->_NotifyCompletedChoreAndFree(pChore);
}
} // namespace details
} // namespace Concurrency