612 lines
15 KiB
C++
612 lines
15 KiB
C++
|
/*++
|
||
|
|
||
|
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);
|
||
|
|
||
|
}
|
||
|
|
||
|
CInstructionQueue::~CInstructionQueue()
|
||
|
{
|
||
|
CInCritSec ics(&m_csQueue); // work inside critical section
|
||
|
|
||
|
while(m_pQueue)
|
||
|
{
|
||
|
CQueueEl* pNext = m_pQueue->m_pNext;
|
||
|
delete m_pQueue;
|
||
|
m_pQueue = pNext;
|
||
|
}
|
||
|
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)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
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() {m_lRef++;}
|
||
|
virtual void Release() {if(--m_lRef == 0) 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"));
|
||
|
SetEvent(hEvent);
|
||
|
CloseHandle(hEvent);
|
||
|
/*
|
||
|
CoFreeUnusedLibraries();
|
||
|
CFreeUnusedLibrariesInstruction* pInst =
|
||
|
new CFreeUnusedLibrariesInstruction;
|
||
|
Set(pInst);
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
|