// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // ExternalContextBase.cpp // // Source file containing the metaphor for an external execution ContextBase/stack/thread. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #include "concrtinternal.h" #pragma warning (disable : 4702) namespace Concurrency { namespace details { /// /// Constructs an external context. /// /// /// The scheduler the context will belong to. /// /// /// Whether or not this is an explicit attach. An explicit attach occurs as a result of calling a scheduler /// creation API, or the scheduler attach API. The scheduler will not detach implicitly for explicitly /// attached threads, on thread exit. /// ExternalContextBase::ExternalContextBase(SchedulerBase *pScheduler, bool explicitAttach) : ContextBase(pScheduler, true), m_pSubAllocator(NULL), m_hPhysicalContext(NULL) { // Create an auto-reset event that is initially not signaled. m_hBlock = platform::__CreateAutoResetEvent(); // VSO#459907 // External contexts are all grouped together in the 'anonymous' schedule group. m_pSegment = m_pScheduler->GetAnonymousScheduleGroupSegment(); // Create external context statistics as a place where this external context we are about to create // will store all the statistical data. m_pStats = _concrt_new ExternalStatistics(); // VSO#459907 m_pScheduler->AddExternalStatistics(m_pStats); // Initialize data that is reset each time the external context is reused. PrepareForUse(explicitAttach); } #if !defined(_ONECORE) /// /// Callback to indicate the exit of one of the external threads. This function /// is invoked on the wait thread. It is assumed that this function is short and quick. /// void CALLBACK ExternalContextBase::ImplicitDetachHandler(PTP_CALLBACK_INSTANCE instance, PVOID parameter, PTP_WAIT waiter, TP_WAIT_RESULT waitResult) { ExternalContextBase * pContext = reinterpret_cast(parameter); ASSERT(waitResult == WAIT_OBJECT_0); pContext->m_pScheduler->DetachExternalContext(pContext, false); // This is non-blocking UnRegisterAsyncWaitAndUnloadLibrary(instance, waiter); } #endif // !defined(_ONECORE) /// /// Same callback function as ImplicitDetachHandler but used on XP. /// void CALLBACK ExternalContextBase::ImplicitDetachHandlerXP(PVOID parameter, BOOLEAN is_timeout) { ExternalContextBase * pContext = reinterpret_cast(parameter); // This is non-blocking platform::__UnregisterWait(pContext->m_hWaitHandle); ASSERT(!is_timeout); pContext->m_pScheduler->DetachExternalContext(pContext, false); } /// /// Initializes fields that need re-initialization when an external context is recycled. This is called /// in the constructor and when an external context is taken off the idle pool for reuse. /// /// /// Whether or not this is an explicit attach. An explicit attach occurs as a result of calling a scheduler /// creation API, or the scheduler attach API. The scheduler will not detach implicitly for explicitly /// attached threads, on thread exit. /// void ExternalContextBase::PrepareForUse(bool explicitAttach) { // Even in the case of a nested external context being initialized, we expect the TLS slot to be clear. ASSERT(SchedulerBase::FastCurrentContext() == NULL); m_fExplicitlyAttached = explicitAttach; m_threadId = GetCurrentThreadId(); if (!explicitAttach) { // We only need to capture the current thread's handle for an implicit attach, so that we can register the // handle for exit tracking, in order that references may be released on thread exit. if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &m_hPhysicalContext, 0, FALSE, DUPLICATE_SAME_ACCESS)) { throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); } #if !defined(_ONECORE) // Request a thread pool thread to wait for this thread exit. if ((m_hWaitHandle = RegisterAsyncWaitAndLoadLibrary(m_hPhysicalContext, ExternalContextBase::ImplicitDetachHandler, this)) == nullptr) { throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); } #else // ^^^ !defined(_ONECORE) / defined(_ONECORE) vvv m_hWaitHandle = platform::__RegisterWaitForSingleObject(m_hPhysicalContext, ExternalContextBase::ImplicitDetachHandlerXP, this); #endif // ^^^ defined(_ONECORE) ^^^ } } /// /// Causes the external context to block. Since external contexts do not execute on virtual processors, /// the context does not switch to another one. Instead, it stops executing until it is unblocked. /// void ExternalContextBase::Block() { ASSERT(this == SchedulerBase::FastCurrentContext()); TraceContextEvent(CONCRT_EVENT_BLOCK, TRACE_LEVEL_INFORMATION, m_pScheduler->Id(), m_id); if (InterlockedIncrement(&m_contextSwitchingFence) == 1) { WaitForSingleObjectEx(m_hBlock, INFINITE, FALSE); } else { // Skip the block, since an unblock has already been encountered. } } /// /// Unblocks the external context causing it to start running. /// void ExternalContextBase::Unblock() { if (this != SchedulerBase::FastCurrentContext()) { TraceContextEvent(CONCRT_EVENT_UNBLOCK, TRACE_LEVEL_INFORMATION, m_pScheduler->Id(), m_id); LONG newValue = InterlockedDecrement(&m_contextSwitchingFence); if (newValue == 0) { SetEvent(m_hBlock); } else { if ((newValue < -1) || (newValue > 0)) { // Should not be able to get m_contextSwitchingFence above 0. ASSERT(newValue < -1); throw context_unblock_unbalanced(); } } } else { throw context_self_unblock(); } } /// /// Just a thread yield on the current processor. /// void ExternalContextBase::Yield() { TraceContextEvent(CONCRT_EVENT_YIELD, TRACE_LEVEL_INFORMATION, m_pScheduler->Id(), m_id); platform::__SwitchToThread(); } /// /// See comments for Concurrency::Context::Oversubscribe. /// External contexts do not support oversubscription. However, we keep track of calls and throw exceptions /// when appropriate. /// void ExternalContextBase::Oversubscribe(bool beginOversubscription) { if (beginOversubscription) { ++m_oversubscribeCount; } else { if (m_oversubscribeCount == 0) { throw invalid_oversubscribe_operation(); } --m_oversubscribeCount; } } /// /// Allocates a block of memory of the size specified. /// /// /// Number of bytes to allocate. /// /// /// A pointer to newly allocated memory. /// void* ExternalContextBase::Alloc(size_t numBytes) { void* pAllocation = NULL; ASSERT(SchedulerBase::FastCurrentContext() == this); // Find the suballocator for this external context if there is one. Note that if we are unable to get an allocator now, // we may be able to get one for a later Alloc or Free call (if a different external context released its allocator to // the free pool). SubAllocator* pAllocator = GetCurrentSubAllocator(); if (pAllocator != NULL) { pAllocation = pAllocator->Alloc(numBytes); } else { // Allocate from the CRT heap. At the point this allocation is freed, if the context has a suballocator, it will be // freed to the suballocator of the context. pAllocation = SubAllocator::StaticAlloc(numBytes); } return pAllocation; } /// /// Frees a block of memory previously allocated by the Alloc API. /// /// /// A pointer to an allocation previously allocated by Alloc. /// void ExternalContextBase::Free(void* pAllocation) { ASSERT(SchedulerBase::FastCurrentContext() == this); ASSERT(pAllocation != NULL); // Find the suballocator for this external context if there is one. Note that if we are unable to get an allocator now, // we may be able to get one for a later Alloc or Free call (if a different external context released its allocator to // the free pool). SubAllocator* pAllocator = GetCurrentSubAllocator(); if (pAllocator != NULL) { pAllocator->Free(pAllocation); } else { // Free to the CRT heap. SubAllocator::StaticFree(pAllocation); } } /// /// Prepares an external context for the idle pool by releasing some resources. /// void ExternalContextBase::RemoveFromUse() { ReleaseWorkQueue(); CONCRT_COREASSERT(GetCriticalRegionType() == OutsideCriticalRegion); if (m_hPhysicalContext != NULL) { CloseHandle(m_hPhysicalContext); m_hPhysicalContext = NULL; } } /// /// Destroys an external thread based context. /// ExternalContextBase::~ExternalContextBase() { // This takes care of calling the cleanup routine for ContextBase. Cleanup(); } /// /// Performs cleanup of the external context /// void ExternalContextBase::Cleanup() { ContextBase::Cleanup(); // // m_pGroup is an anonymous schedule group that is destroyed at scheduler shutdown, so don't release here. // if (m_hPhysicalContext != NULL) { CloseHandle(m_hPhysicalContext); m_hPhysicalContext = NULL; } if (m_hBlock) { CloseHandle(m_hBlock); } if (m_pSubAllocator != NULL) { SchedulerBase::ReturnSubAllocator(m_pSubAllocator); } // Mark the scheduler's list of non-internal contexts (external or non-bound to context) for removal. We // can't remove this item yet because statistics might not have had a chance to aggregate this information yet. DetachStatistics(); } } // namespace details } // namespace Concurrency