// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // UMSThreadProxy.cpp // // Proxy for a UMS thread. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #include "concrtinternal.h" namespace Concurrency { namespace details { /// /// Constructs a thread proxy. /// UMSThreadProxy::UMSThreadProxy(IThreadProxyFactory * pFactory, PUMS_COMPLETION_LIST pStartupList, unsigned int stackSize) : m_pFactory(pFactory) , m_pUMSContext(NULL) , m_hPhysicalContext(NULL) , m_hBlock(NULL) , m_blockingType(BlockingNone) , m_pRoot(NULL) , m_pLastRoot(NULL) , m_stackSize(stackSize) , m_threadPriority(THREAD_PRIORITY_NORMAL) , m_fIdlePooled(false) , m_yieldAction(ActionNone) , m_pTransmogrification(NULL) , m_activationCause(ActivationCauseNone) , m_fCanceled(FALSE) , m_criticalRegionCount(0) , m_hyperCriticalRegionCount(0) #if defined(_DEBUG) , m_lastRunPrepareTimeStamp(0) , m_UMSDebugBits(0) , m_fShutdownValidations(false) #endif // _DEBUG { // Since we treat thread proxy creation as non-fatal, this function must be exception safe with respect to // scheduler_worker_creation_error m_hBlock = CreateEventW(NULL, FALSE, FALSE, NULL); if (m_hBlock == NULL) throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); m_id = ResourceManager::GetThreadProxyId(); UMS_CREATE_THREAD_ATTRIBUTES umsAttributes; PPROC_THREAD_ATTRIBUTE_LIST pAttributeList; SIZE_T sizeAttributeList; if (!UMS::CreateUmsThreadContext(&m_pUMSContext)) throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); // VSO#459907 UMSBaseObject *pThis = this; if (!UMS::SetUmsThreadInformation(m_pUMSContext, UmsThreadUserContext, &pThis, sizeof(pThis))) throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); // VSO#459907 umsAttributes.UmsVersion = UMS_VERSION; umsAttributes.UmsContext = m_pUMSContext; umsAttributes.UmsCompletionList = pStartupList; UMS::InitializeProcThreadAttributeList(NULL, 1, 0, &sizeAttributeList); ASSERT(GetLastError() == ERROR_INSUFFICIENT_BUFFER); pAttributeList = reinterpret_cast(_concrt_new char[sizeAttributeList]); if (!UMS::InitializeProcThreadAttributeList(pAttributeList, 1, 0, &sizeAttributeList)) { throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); // VSO#459907 } if (!UMS::UpdateProcThreadAttribute(pAttributeList, 0, PROC_THREAD_ATTRIBUTE_UMS_THREAD, &umsAttributes, sizeof(UMS_CREATE_THREAD_ATTRIBUTES), NULL, NULL)) { throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); // VSO#459907 } m_hPhysicalContext = UMS::CreateRemoteThreadEx(GetCurrentProcess(), NULL, stackSize*KB, UMSThreadProxyMain, this, STACK_SIZE_PARAM_IS_A_RESERVATION, pAttributeList, &m_threadId); UMS::DeleteProcThreadAttributeList(pAttributeList); delete[] (reinterpret_cast(pAttributeList)); if (m_hPhysicalContext == NULL) { // Cleanup everything we've allocated because this exception may be caught by a higher // layer to provide resiliency against thread creation failures during thread proxy construction. UMS::DeleteUmsThreadContext(m_pUMSContext); CloseHandle(m_hBlock); throw scheduler_worker_creation_error(HRESULT_FROM_WIN32(GetLastError())); } } /// /// Destroys a thread proxy. /// UMSThreadProxy::~UMSThreadProxy() { ASSERT(m_hBlock != NULL); CloseHandle(m_hBlock); ASSERT(m_hPhysicalContext != NULL); CloseHandle(m_hPhysicalContext); ASSERT(m_pUMSContext != NULL); UMS::DeleteUmsThreadContext(m_pUMSContext); } /// /// Returns a process unique identifier for the thread proxy. /// unsigned int UMSThreadProxy::GetId() const { return m_id; } /// /// Thread start routine for proxies. /// /// /// Pointer to the thread proxy /// DWORD CALLBACK UMSThreadProxy::UMSThreadProxyMain(LPVOID lpParameter) { UMSThreadProxy* pThreadProxy = reinterpret_cast (lpParameter); pThreadProxy->ReadyForDispatch(); CONCRT_COREASSERT(pThreadProxy->GetCriticalRegionType() == OutsideCriticalRegion || pThreadProxy->m_pRoot->IsDeleting()); pThreadProxy->Dispatch(); ASSERT(pThreadProxy->m_fCanceled); // // The data structure needs to remain until we jump back to the primary executing it for the last time and the primary detects that // the thread has terminated. Otherwise, the primary will read an invalid data structure to determine what just terminated! // return 0; } /// /// Indicate that the thread proxy is ready for dispatch. /// void UMSThreadProxy::ReadyForDispatch() { // If the UT is started up on a dedicated primary then we need not be in a hypercritical // region. We enter a hypercritical region here to ensure that this would work if we decide // to move UT startup to vprocs. EnterHyperCriticalRegion(); m_yieldAction = ActionStartup; UMS::UmsThreadYield(this); // Critical region count will be cleared on dispatch. } /// /// Returns our understanding of a UMS context (a UMS thread proxy) from the UMS system's understanding (a UMS_CONTEXT) /// /// /// UMS context /// /// /// UMS thread proxy /// UMSThreadProxy *UMSThreadProxy::FromUMSContext(PUMS_CONTEXT pUMSContext) { if (pUMSContext == NULL) return NULL; UMSBaseObject *pObj = NULL; UMSThreadProxy *pProxy = NULL; ULONG returnLength; if (!UMS::QueryUmsThreadInformation(pUMSContext, UmsThreadUserContext, reinterpret_cast (&pObj), sizeof(pObj), &returnLength)) throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); ASSERT(returnLength == sizeof(pObj)); if (pObj != NULL && !pObj->IsPrimary()) pProxy = static_cast(pObj); return pProxy; } /// /// Returns our understanding of a UMS context (a UMS thread proxy) from the SLIST_ENTRY used to /// place it on an SLIST (e.g.: the transfer list). /// /// /// Pointer to the SList entry in the thread proxy /// /// /// UMS thread proxy /// UMSThreadProxy *UMSThreadProxy::FromListEntry(PSLIST_ENTRY pListEntry) { if (pListEntry == NULL) return NULL; return CONTAINING_RECORD(pListEntry, UMSThreadProxy, m_listEntry); } /// /// Switch from the current thread proxy to pProxy. /// /// /// The thread proxy to switch to. /// /// /// The reason for the switch. /// void UMSThreadProxy::InternalSwitchTo(UMSThreadProxy *pProxy, SwitchingProxyState switchState) { const YieldAction stateActions[] = { ActionSwitchToAndRetire /* Idle */, ActionSwitchTo /* Blocking */, ActionTransmogrifyAndSwitch /* Nesting */ }; // // Manipulations of m_yieldAction need to be guarded under a hyper-critical region. We cannot tolerate a page fault happening // which corrupts our state. // EnterHyperCriticalRegion(); // // On nesting, we have to transmogrify the currently running UMS thread to a "virtual"-thread so that it can continue // to run as an external context of a nested scheduler without having to be scheduled atop a virtual processor (something // outside our model). // if (switchState == Nesting) { CONCRT_COREASSERT(m_pTransmogrification == NULL); // // We cannot perform an allocation or thread creation here, so we ask the transmogrificator to do this for us. Oh fun. // RequestTransmogrification(); } RVPMTRACE(switchState == Idle ? MTRACE_EVT_SWITCHTO_IDLE : (switchState == Blocking ? MTRACE_EVT_SWITCHTO_BLOCKING : MTRACE_EVT_SWITCHTO_NESTING), this, m_pRoot, pProxy); CONCRT_COREASSERT((int)switchState >= 0 && (int)switchState <= 2); m_yieldAction = stateActions[switchState]; m_pNextProxy = pProxy; CONCRT_COREASSERT(m_yieldAction != ActionNone); CONCRT_COREASSERT(m_pRoot != NULL && m_pRoot == m_pLastRoot && m_pRoot->GetExecutingProxy() == this); UMS::UmsThreadYield(this); // // Idle threads have critical regions completely reset when they go on the idle pool (this is what allows us to have // "until the end of time" semantics on certain critical regions from the perspective of a thread going away). // if (switchState != Idle) ExitHyperCriticalRegion(); else { // // Note that there is now a code path in the primary where-by it must allocate a new (or get a pooled) proxy to burn on the way out to avoid // a Win7 bug. In this case, the primary must continue to execute the thread until thread exit which means it must be hyper-crit. In this circumstance, // m_fCanceled should already be set on the way out! // CONCRT_COREASSERT(m_hyperCriticalRegionCount == 0 || m_fCanceled); } } /// /// Called in order to perform a cooperative context switch out. After this call, the context which was running will be blocked /// until it is switched to or used to activate a virtual processor. /// void UMSThreadProxy::InternalSwitchOut(SwitchingProxyState switchState) { EnterHyperCriticalRegion(); if (switchState == Nesting) { CONCRT_COREASSERT(m_pTransmogrification == NULL); // // We cannot perform an allocation or thread creation here, so we ask the transmogrificator to do this for us. Oh fun. // RequestTransmogrification(); // // Indicate to the primary that it should reset itself after unblocking the transmogrifier // m_yieldAction = ActionTransmogrifyAndReset; m_pNextProxy = NULL; UMS::UmsThreadYield(this); } else { CONCRT_COREASSERT(switchState == Blocking); // // If we are currently in a transmogrified state, undo the transmogrification. Yielding back to the transmogrified primary will get rid of that // particular thread. // if (m_pTransmogrification != NULL) { m_pTransmogrification = NULL; UMS::UmsThreadYield(this); } else { m_yieldAction = ActionResetForSwitchOut; UMS::UmsThreadYield(this); } } ExitHyperCriticalRegion(); } /// /// Yield to the underlying Operting system /// void UMSThreadProxy::InternalYieldToSystem() { // This needs to be called by the thread that is currently executing CONCRT_COREASSERT(UMSThreadProxy::GetCurrent() == this); EnterHyperCriticalRegion(); m_yieldAction = ActionYieldToSystem; m_pNextProxy = NULL; CONCRT_COREASSERT(m_yieldAction != ActionNone); CONCRT_COREASSERT(m_pRoot != NULL && m_pRoot == m_pLastRoot && m_pRoot->GetExecutingProxy() == this); UMS::UmsThreadYield(this); ExitHyperCriticalRegion(); } /// /// Deactivate the current thread proxy. As this requires a message block set, it lives here /// rather than in the VPROOT. /// /// /// An indication of whether the awakening was due to an Activate call on the virtual processor root /// (true) or an RM cause (e.g.: completion notification -- false). /// bool UMSThreadProxy::Deactivate() { CONCRT_COREASSERT(GetCriticalRegionType() != OutsideCriticalRegion); CONCRT_COREASSERT(m_pRoot != NULL); // // Manipulations of m_yieldAction need to be guarded under a hyper-critical region. We cannot tolerate a page fault happening // which corrupts our state. // EnterHyperCriticalRegion(); m_yieldAction = ActionDeactivate; m_pNextProxy = NULL; UMS::UmsThreadYield(this); CONCRT_COREASSERT(m_activationCause != ActivationCauseNone); bool fActivated = (m_activationCause == ActivationCauseActivate); m_activationCause = ActivationCauseNone; ExitHyperCriticalRegion(); return fActivated; } /// /// The caller has exited the dispatch loop. Free the thread and deactivate. /// void UMSThreadProxy::FreeViaExit() { // // Manipulations of m_yieldAction need to be guarded under a hyper-critical region. We cannot tolerate a page fault happening // which corrupts our state. // EnterHyperCriticalRegion(); CONCRT_COREASSERT(!m_fIdlePooled); m_yieldAction = ActionFree; UMS::UmsThreadYield(this); // // We need not worry about releasing the critical region here. When it goes on the free list, it gets completely reinitialized. Either // that or we're on the way out (to get rid of the thread) and we only want to run this thread anyway. // } /// /// Sets the priority of the underlying thread. /// /// /// The new priority value for the thread. /// void UMSThreadProxy::SetPriority(int priority) { // // Right now (Win7), the priority set here only impacts running atop the real UT thread (not running the primary underneath). // This only happens on certain kernel transitions and on certain optimizations out of the kernel. Really, // the primary should be the only one setting priority. However, we do this to ensure that BOTH the primary thread // and the kernel portion of the UT have the same priority setting. // m_threadPriority = priority; if (SetThreadPriority(m_hPhysicalContext, m_threadPriority) == 0) throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError())); } /// /// Cancels the thread proxy causing the underlying thread to exit. /// void UMSThreadProxy::Cancel() { m_fCanceled = true; } /// /// Spins until we've registered blocking. /// void UMSThreadProxy::SpinUntilBlocked() { if (m_pRoot != NULL) { _SpinWaitBackoffNone spinWait(_Sleep0); do { spinWait._SpinOnce(); } while (m_pRoot != NULL); } } /// /// Spins until we've registered what blocking type the last block operation was and returns the type. /// /// /// Returns the blocking type. /// UMSThreadProxy::BlockingType UMSThreadProxy::SpinOnAndReturnBlockingType() { if (m_blockingType == BlockingNone) { _SpinWaitBackoffNone spinWait(_Sleep0); do { spinWait._SpinOnce(); } while (m_blockingType == BlockingNone); } return m_blockingType; } /// /// Request this proxy to be transmogrified. /// void UMSThreadProxy::RequestTransmogrification() { CONCRT_COREASSERT(m_pFactory != NULL); UMSFreeThreadProxyFactory * pFactory = static_cast(m_pFactory); Transmogrificator *pTransmogrificator = pFactory->GetTransmogrificator(); CONCRT_COREASSERT(pTransmogrificator != NULL); pTransmogrificator->PerformTransmogrification(this); } /// /// Notify that this proxy is ready to be transmogrified. /// void UMSThreadProxy::NotifyTransmogrification() { CONCRT_COREASSERT(m_pFactory != NULL); UMSFreeThreadProxyFactory * pFactory = static_cast(m_pFactory); Transmogrificator *pTransmogrificator = pFactory->GetTransmogrificator(); CONCRT_COREASSERT(pTransmogrificator != NULL); pTransmogrificator->UnblockTransmogrification(this); } /// /// Returns whether or not this thread is currently suspended. /// /// /// Returns true if the thread is suspended /// bool UMSThreadProxy::IsSuspended() { BOOLEAN fSuspended = FALSE; UMS::QueryUmsThreadInformation(m_pUMSContext, UmsThreadIsSuspended, &fSuspended, sizeof(fSuspended), NULL); return !!fSuspended; } /// /// Returns whether or not this thread is currently terminated. /// /// /// Returns true if the thread is terminated /// bool UMSThreadProxy::IsTerminated() { BOOLEAN fTerminated = FALSE; UMS::QueryUmsThreadInformation(m_pUMSContext, UmsThreadIsTerminated, &fTerminated, sizeof(fTerminated), NULL); return !!fTerminated; } /// /// Called in order to prepare this thread proxy to run atop a given virtual processor root. /// void UMSThreadProxy::PrepareToRun(UMSFreeVirtualProcessorRoot *pRoot) { #if defined(_DEBUG) m_lastRunPrepareTimeStamp = _ReadTimeStampCounter(); #endif // _DEBUG m_blockingType = BlockingNone; m_pLastRoot = pRoot; m_pRoot = pRoot; } } // namespace details } // namespace Concurrency