windows-nt/Source/XPSP1/NT/admin/wmi/wbem/winmgmt/wbemcomn/tss.cpp
2020-09-26 16:20:57 +08:00

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);
*/
}