// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// workqueue.cpp
//
// Work stealing queues pair implementation.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include "concrtinternal.h"
namespace Concurrency
{
namespace details
{
///
/// Constructs a new work queue.
///
WorkQueue::WorkQueue()
: m_structuredQueue(&m_lock)
, m_detachmentState(QUEUE_ATTACHED)
, m_pOwningContext(NULL)
, m_unstructuredQueue(&m_lock)
{
m_detachment.m_listArrayIndex = 0;
m_detachment.m_pObject = this;
m_id = SchedulerBase::GetNewWorkQueueId();
}
///
/// Steal a chore from the work stealing queue for unrealized parallelism.
///
///
/// Whether to steal the task at the bottom end of the work stealing queue even if it is an affinitized to a location
/// that has active searches. This is set to true on the final SFW pass to ensure a vproc does not deactivate while there
/// are chores higher up in the queue that are un-affinitized and therefore inaccessible via a mailbox.
///
_UnrealizedChore *WorkQueue::UnlockedSteal(bool fForceStealLocalized)
{
if (IsEmpty())
return NULL;
//
// Make certain we do not steal from a canceled context. The exception handling will properly unwind this. This avoids a degree on infighting
// on cancellation. When a subtree which has stolen chores is canceled, the stealing threads are canceled, unwind, and immediately become available
// for stealing. They can easily pick up a region of the tree where exception handling has not unwound to the cancellation point and the exception
// handlers wind up in a little battle with the stealing threads (the exception handlers shooting down contexts and the contexts stealing different
// pieces of the tree, wash, rinse, repeat). In order to avoid *some* of the contention, we simply will refuse to steal from any context which was
// shot down as part of a subtree cancellation.
//
// Further, as an optimization, we do temporarily suspend stealing from a context which has an inflight cancellation -- even if the stealing happens
// in a different piece of the subtree than being canceled. This allows us to avoid yet more infighting during a cancellation. It should be a rare
// enough event that the suspension of stealing from that context during the in flight cancel shouldn't hurt performance. Note that this is the difference
// between the pCheckContext->HasInlineCancellation() and a pCheckContext->IsEntireContextCanceled() below. The latter would only check fully canceled
// contexts. The former does the suspension on contexts with in flight cancellations.
//
// Allow stealing to occur from a blocked context as it could be relying on stolen chores to make further progress.
//
_UnrealizedChore *pResult = NULL;
ContextBase *pOwningContext = m_pOwningContext;
if (pOwningContext != NULL && pOwningContext->HasInlineCancellation() && !pOwningContext->IsSynchronouslyBlocked())
{
//
// There is a scenario that we need to be extraordinarily careful of here. Normally, we could just ignore the steal. Unfortunately,
// it's entirely possible that a detached queue was reused on a context (A) and then A became cancelled while we waited on a task
// collection that had chores in the detached region. This would lead to deadlock. We need to allow stealing from the region of
// the WSQ which was detached.
//
// m_unstructuredQueue may contains tasks from task collections that are not being canceled. However, we cannot quickly detect the difference. Thus
// before arbitrary stealing from m_unstructuredQueue, we need to finish cancellation of the task collections that need to be canceled, which is why
// pOwningContext->HasInlineCancellation() is true. Cancellation is over when pOwningContext->HasInlineCancellation() is false and then uninhibited
// stealing from m_unstructuredQueue may occur again. However, in the meantime, stealing threads may go idle because no work can be found and if
// m_pOwningContext exits w/o waiting, there may be work left to execute, but all vprocs are idle. This 'deadlock' is prevented by calling
// NotifyWorkSkipped() that assures an associated vproc will not go idle without verifying there is no work left by executing this function again.
//
if (m_unstructuredQueue.MarkedForDetachment())
pResult = (_UnrealizedChore *) m_unstructuredQueue.UnlockedSteal(fForceStealLocalized, true);
if (pResult != NULL)
pResult->_SetDetached(true);
else
static_cast(SchedulerBase::FastCurrentContext())->NotifyWorkSkipped();
}
else
{
pResult = (_UnrealizedChore *) m_structuredQueue.UnlockedSteal(fForceStealLocalized);
//
// Structured does not need to deal with detachment and cancellation references, simply return the value.
//
if (pResult != NULL)
return pResult;
pResult = (_UnrealizedChore *) m_unstructuredQueue.UnlockedSteal(fForceStealLocalized, false);
//
// If we stole and there is an owning context and it's not a detached steal (which always goes on the task collection list), keep the owning context
// alive until the wrapper can add the task to the appropriate cancellation list. Since we're holding the workqueue lock, we can safely access m_pOwningContext.
// NOTE: pResult is unstructured.
//
if (pResult != NULL)
{
if (m_pOwningContext != NULL
&& reinterpret_cast (pResult->_OwningCollection()->_OwningContext()) == m_pOwningContext)
{
m_pOwningContext->ReferenceForCancellation();
pResult->_SetDetached(false);
}
else
{
pResult->_SetDetached(true);
}
}
}
return pResult;
}
///
/// Attempts to steal an unrealized chore from the unstructured work stealing queue.
///
///
/// Whether to steal the task at the bottom end of the work stealing queue even if it is an affinitized to a location
/// that has active searches. This is set to true on the final SFW pass to ensure a vproc does not deactivate while there
/// are chores higher up in the queue that are un-affinitized and therefore inaccessible via a mailbox.
///
///
/// The try lock was successfully acquired.
///
///
/// An unrealized chore stolen from the work stealing queues or NULL if no such chore can be stolen
///
_UnrealizedChore *WorkQueue::TryToSteal(bool fForceStealLocalized, bool& fSuccessfullyAcquiredLock)
{
_UnrealizedChore *pResult = NULL;
if (m_lock._TryAcquire())
{
__try
{
pResult = UnlockedSteal(fForceStealLocalized);
}
__finally
{
m_lock._Release();
}
fSuccessfullyAcquiredLock = true;
}
else
fSuccessfullyAcquiredLock = false;
return pResult;
}
///
/// Steal a chore from both work stealing queues.
///
///
/// Whether to steal the task at the bottom end of the work stealing queue even if it is an affinitized to a location
/// that has active searches. This is set to true on the final SFW pass to ensure a vproc does not deactivate while there
/// are chores higher up in the queue that are un-affinitized and therefore inaccessible via a mailbox.
///
_UnrealizedChore* WorkQueue::Steal(bool fForceStealLocalized)
{
_CriticalNonReentrantLock::_Scoped_lock lockHolder(m_lock);
return UnlockedSteal(fForceStealLocalized);
}
///
/// Sweeps the unstructured work stealing queue for items matching a predicate and potentially removes them
/// based on the result of a callback.
///
///
/// The predicate for things to call pSweepFn on.
///
///
/// The data for the predicate callback
///
///
/// The sweep function
///
void WorkQueue::SweepUnstructured(WorkStealingQueue<_UnrealizedChore>::SweepPredicate pPredicate,
void *pData,
WorkStealingQueue<_UnrealizedChore>::SweepFunction pSweepFn
)
{
m_unstructuredQueue.Sweep(pPredicate, pData, pSweepFn);
}
///
/// Causes a detached work queue to release its reference on the passed-in schedule group and remove itself from that schedule group's
/// list of work queues at the next available safe point.
///
void WorkQueue::RetireAtSafePoint(ScheduleGroupSegmentBase *pSegment)
{
m_pDetachedSegment = pSegment;
m_detachmentSafePoint.InvokeAtNextSafePoint(reinterpret_cast(&WorkQueue::StaticRetire),
this,
pSegment->GetGroup()->GetScheduler());
}
///
/// Causes a detached work queue to redetach due to roll-back of retirement at the next available safe point.
///
void WorkQueue::RedetachFromScheduleGroupAtSafePoint(ScheduleGroupSegmentBase *pSegment)
{
m_pDetachedSegment = pSegment;
m_detachmentSafePoint.InvokeAtNextSafePoint(reinterpret_cast(&WorkQueue::StaticRedetachFromScheduleGroup),
this,
pSegment->GetGroup()->GetScheduler());
}
///
/// Retires the detached work queue.
///
void WorkQueue::StaticRetire(WorkQueue *pQueue)
{
pQueue->m_pDetachedSegment->RetireDetachedQueue(pQueue);
}
///
/// Places the work queue back in a detached state on roll back.
///
void WorkQueue::StaticRedetachFromScheduleGroup(WorkQueue *pQueue)
{
pQueue->m_pDetachedSegment->RedetachQueue(pQueue);
}
///
/// Reinitialize a work queue pulled from a free pool
///
void WorkQueue::Reinitialize()
{
// Grab steal locks, this will prevent other readers from grabbing this work
// queue while we are reinitializing (h=t=0 is non-atomic, is it worth it to fix?)
m_lock._Acquire();
m_id = SchedulerBase::GetNewWorkQueueId();
// Reinitialize the reused workqueue
m_structuredQueue.Reinitialize();
m_unstructuredQueue.Reinitialize();
m_detachmentState = QUEUE_ATTACHED;
m_pOwningContext = NULL;
// Release the work queue locks
m_lock._Release();
}
} // namespace details
} // namespace Concurrency