/*++ Copyright (C) 1996-2001 Microsoft Corporation Module Name: TSS.CPP Abstract: This file implements the classes used by the Timer Subsystem. History: 26-Nov-96 raymcc Draft 28-Dec-96 a-richm Alpha PDK Release 12-Apr-97 a-levn Extensive changes --*/ #include "precomp.h" #include "tss.h" #include #include #include CInstructionQueue::CInstructionQueue() : m_pQueue(NULL), m_csQueue(), m_bBreak(FALSE) { // Create the event which will be signaled whenever a new instruction // is added to the head of the queue // ================================================================== m_hNewHead = CreateEvent(NULL, FALSE, // automatic reset FALSE, // non-signalled NULL); if (NULL == m_hNewHead) throw CX_MemoryException(); // checked in esssink.cpp } CInstructionQueue::~CInstructionQueue() { CInCritSec ics(&m_csQueue); // work inside critical section while(m_pQueue) { CQueueEl* pCurrent = m_pQueue; m_pQueue = m_pQueue->m_pNext; delete pCurrent; } CloseHandle(m_hNewHead); } void CInstructionQueue::TouchHead() { SetEvent(m_hNewHead); } HRESULT CInstructionQueue::Enqueue(CWbemTime When, ADDREF CTimerInstruction* pInst) { CInCritSec ics(&m_csQueue); // work inside critical section // Create the link-list element for the object // =========================================== CQueueEl* pNew = new CQueueEl(pInst, When); if(!pNew) return WBEM_E_OUT_OF_MEMORY; // Find the right place to insert this instruction // =============================================== CQueueEl* pCurrent = m_pQueue; CQueueEl* pLast = NULL; while(pCurrent && When >= pCurrent->m_When) { pLast = pCurrent; pCurrent = pCurrent->m_pNext; } // Insert it // ========= if(pLast) { // Inserting in the middle // ======================= pLast->m_pNext = pNew; pNew->m_pNext = pCurrent; } else { // Inserting at the head // ===================== pNew->m_pNext = m_pQueue; m_pQueue = pNew; TouchHead(); } return S_OK; } HRESULT CInstructionQueue::Dequeue(OUT RELEASE_ME CTimerInstruction*& pInst, OUT CWbemTime& When) { CInCritSec ics(&m_csQueue); // all work in critical section if(m_pQueue == NULL) return S_FALSE; pInst = m_pQueue->m_pInst; When = m_pQueue->m_When; // Null out the instruction in the queue so it would not be deleted // ================================================================ m_pQueue->m_pInst = NULL; // Delete the head from the queue // ============================== CQueueEl* pNewHead = m_pQueue->m_pNext; delete m_pQueue; m_pQueue = pNewHead; return S_OK; } HRESULT CInstructionQueue::Remove(IN CInstructionTest* pPred, OUT RELEASE_ME CTimerInstruction** ppInst) { if(ppInst) *ppInst = NULL; CTimerInstruction* pToMark = NULL; BOOL bFound = FALSE; { CInCritSec ics(&m_csQueue); // all work in critical section CQueueEl* pCurrent = m_pQueue; CQueueEl* pLast = NULL; while(pCurrent) { if((*pPred)(pCurrent->m_pInst)) { // Accepted. Remove // ================ bFound = TRUE; CQueueEl* pNext; if(pLast) { // removing from the middle // ======================== pLast->m_pNext = pCurrent->m_pNext; pNext = pLast->m_pNext; } else { // Removing from the head // ====================== m_pQueue = pCurrent->m_pNext; pNext = m_pQueue; TouchHead(); } if(pToMark) { // This is not entirely clean. This function was originally // written to remove one instruction, but then converted to // remove all matching ones. The **ppInst and pToMark // business is only applicable to the one instruction case. // It would be cleaner to split this function up into two, // but that's too risky at this point. // ======================================================== pToMark->Release(); } pToMark = pCurrent->m_pInst; pToMark->AddRef(); delete pCurrent; pCurrent = pNext; } else { pLast = pCurrent; pCurrent = pCurrent->m_pNext; } } } // out of critical section // Preserve the instruction to be returned, if required // ==================================================== if(ppInst != NULL) { // Release whatever may be in there // ================================ if(*ppInst) (*ppInst)->Release(); // Store the instruction being deleted there // ========================================= *ppInst = pToMark; } else if(pToMark) { pToMark->MarkForRemoval(); pToMark->Release(); } if(!bFound) return S_FALSE; return S_OK; } HRESULT CInstructionQueue::Change(CTimerInstruction* pInst, CWbemTime When) { CInCritSec ics(&m_csQueue); // all work in critical section CIdentityTest Test(pInst); CTimerInstruction* pObtained; if(Remove(&Test, &pObtained) == S_OK) { // pObtained == pInst, of course // ============================= // Got it. Enqueue with new time // ============================= HRESULT hres = S_OK; if(When.IsFinite()) hres = Enqueue(When, pInst); pObtained->Release(); return hres; } else { // This instruction is no longer there return S_FALSE; } } BOOL CInstructionQueue::IsEmpty() { return (m_pQueue == NULL); } CWbemInterval CInstructionQueue::TimeToWait() { // ================================================ // Assumes that we are inside the critical section! // ================================================ if(m_pQueue == NULL) { return CWbemInterval::GetInfinity(); } else { return CWbemTime::GetCurrentTime().RemainsUntil(m_pQueue->m_When); } } void CInstructionQueue::BreakWait() { m_bBreak = TRUE; SetEvent(m_hNewHead); } HRESULT CInstructionQueue::WaitAndPeek( OUT RELEASE_ME CTimerInstruction*& pInst, OUT CWbemTime& When) { EnterCriticalSection(&m_csQueue); CWbemInterval ToWait = TimeToWait(); // Wait that long. The wait may be interrupted and shortened by // insertion of new instructions // ============================================================ while(!ToWait.IsZero()) { LeaveCriticalSection(&m_csQueue); // If ToWait is infinite, wait for 30 seconds instead // ================================================== DWORD dwMilli; if(ToWait.IsFinite()) dwMilli = ToWait.GetMilliseconds(); else dwMilli = 30000; DWORD dwRes = WbemWaitForSingleObject(m_hNewHead, dwMilli); if(m_bBreak) return S_FALSE; if (dwRes == -1 || (dwRes == WAIT_TIMEOUT && !ToWait.IsFinite())) { if (dwRes == -1) { ERRORTRACE((LOG_WBEMCORE, "WaitForMultipleObjects failed. LastError = %X.\n", GetLastError())); ::Sleep(0); } // We timed out on the 30 second wait --- time to quit for lack // of work // ============================================================ return WBEM_S_TIMEDOUT; } EnterCriticalSection(&m_csQueue); ToWait = TimeToWait(); } // still in critical section pInst = m_pQueue->m_pInst; When = m_pQueue->m_When; pInst->AddRef(); LeaveCriticalSection(&m_csQueue); return S_OK; } long CInstructionQueue::GetNumInstructions() { EnterCriticalSection(&m_csQueue); long lCount = 0; CQueueEl* pCurrent = m_pQueue; while(pCurrent) { lCount++; pCurrent = pCurrent->m_pNext; } LeaveCriticalSection(&m_csQueue); return lCount; } CTimerGenerator::CTimerGenerator() : CHaltable(), m_fExitNow(FALSE), m_hSchedulerThread(NULL) { // throws because of CHaltable } void CTimerGenerator::EnsureRunning() { CInCritSec ics(&m_cs); if(m_hSchedulerThread) return; // Create scheduler thread. // ======================== NotifyStartingThread(); DWORD dwThreadId; m_hSchedulerThread = CreateThread( NULL, // pointer to thread security attributes 0, // initial thread stack size, in bytes (LPTHREAD_START_ROUTINE)SchedulerThread, // pointer to thread function (CTimerGenerator*)this, // argument for new thread 0, // creation flags &dwThreadId // pointer to returned thread identifier ); } HRESULT CTimerGenerator::Shutdown() { if(m_hSchedulerThread) { // Set the flag indicating that the scheduler should stop m_fExitNow = 1; // Resume the scheduler if halted. ResumeAll(); // Wake up scheduler. It will stop immediately because of the flag. m_Queue.BreakWait(); // Wait for scheduler thread to exit. WbemWaitForSingleObject(m_hSchedulerThread, INFINITE); CloseHandle(m_hSchedulerThread); m_hSchedulerThread = NULL; return S_OK; } else return S_FALSE; } CTimerGenerator::~CTimerGenerator() { Shutdown(); } HRESULT CTimerGenerator::Set(ADDREF CTimerInstruction *pInst, CWbemTime NextFiring) { if (isValid() == false) return WBEM_E_OUT_OF_MEMORY; CInCritSec ics(&m_cs); // // 0 for NextFiring indicates that the instruction has not been fired or // scheduled before, and should therefore be asked when its first firing // time should be // if(NextFiring.IsZero()) { NextFiring = pInst->GetFirstFiringTime(); } // // Infinite firing time indicates that this istruction can never fire // if(!NextFiring.IsFinite()) return S_FALSE; // // Real instruction --- enqueue // HRESULT hres = m_Queue.Enqueue(NextFiring, pInst); // // Ensure time generator thread is running, as it shuts down when there are // no instructions on the queue // EnsureRunning(); return hres; } HRESULT CTimerGenerator::Remove(CInstructionTest* pPred) { CInCritSec ics(&m_cs); HRESULT hres = m_Queue.Remove(pPred); if(FAILED(hres)) return hres; return S_OK; } DWORD CTimerGenerator::SchedulerThread(LPVOID pArg) { InitializeCom(); CTimerGenerator * pGen = (CTimerGenerator *) pArg; try { while(1) { // Wait until we are resumed. In non-paused state, returns immediately. // ==================================================================== pGen->WaitForResumption(); // Wait for the next instruction on the queue to mature // ==================================================== CTimerInstruction* pInst; CWbemTime WhenToFire; HRESULT hres = pGen->m_Queue.WaitAndPeek(pInst, WhenToFire); if(hres == S_FALSE) { // End of the game: destructor called BreakDequeue // =============================================== break; } else if(hres == WBEM_S_TIMEDOUT) { // The thread is exiting for lack of work // ====================================== CInCritSec ics(&pGen->m_cs); // Check if there is any work // ========================== if(pGen->m_Queue.IsEmpty()) { // That's it --- exit // ================== CloseHandle( pGen->m_hSchedulerThread ); pGen->m_hSchedulerThread = NULL; break; } else { // Work was added before we entered CS // =================================== continue; } } // Make sure we haven't been halted while sitting here // =================================================== if(pGen->IsHalted()) { // try again later. pInst->Release(); continue; } // Figure out how many times this instruction has "fired" // ====================================================== long lMissedFiringCount = 0; CWbemTime NextFiring = pInst->GetNextFiringTime(WhenToFire, &lMissedFiringCount); // Notify accordingly // ================== pInst->Fire(lMissedFiringCount+1, NextFiring); // Requeue the instruction // ======================= if(pGen->m_Queue.Change(pInst, NextFiring) != S_OK) { //Error!!! } pInst->Release(); } } catch( CX_MemoryException ) { } pGen->NotifyStoppingThread(); CoUninitialize(); return 0; } class CFreeUnusedLibrariesInstruction : public CTimerInstruction { protected: long m_lRef; CWbemInterval m_Delay; public: CFreeUnusedLibrariesInstruction() : m_lRef(0) { m_Delay.SetMilliseconds(660000); } virtual void AddRef() { InterlockedIncrement(&m_lRef);} virtual void Release() {if(0 == InterlockedDecrement(&m_lRef)) delete this;} virtual int GetInstructionType() {return INSTTYPE_FREE_LIB;} public: virtual CWbemTime GetNextFiringTime(CWbemTime LastFiringTime, OUT long* plFiringCount) const { *plFiringCount = 1; return CWbemTime::GetInfinity(); } virtual CWbemTime GetFirstFiringTime() const { return CWbemTime::GetCurrentTime() + m_Delay; } virtual HRESULT Fire(long lNumTimes, CWbemTime NextFiringTime) { DEBUGTRACE((LOG_WBEMCORE, "Calling CoFreeUnusedLibraries...\n")); CoFreeUnusedLibraries(); return S_OK; } }; void CTimerGenerator::ScheduleFreeUnusedLibraries() { // Inform our EXE that now and in 11 minutes would be a good time to call // CoFreeUnusedLibraries // ====================================================================== HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, __TEXT("WINMGMT_PROVIDER_CANSHUTDOWN")); if (hEvent) { SetEvent(hEvent); CloseHandle(hEvent); } }