|
|
/*++
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 <cominit.h>
#include <stdio.h>
#include <wbemutil.h>
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); } }
|