windows-nt/Source/XPSP1/NT/printscan/print/spooler/inetpp2/server/cachemgr.cxx
2020-09-26 16:20:57 +08:00

934 lines
22 KiB
C++

/*****************************************************************************\
* MODULE: cachemgr.cxx
*
* The module contains routines to implement the caching algorithm for
* the printers provider
*
* Purpose:
*
* Description:
*
* The Caching algorithm operates based on a state machine. There are five
* states in the cache:
*
* CACHE_STATE_INIT
* CACHE_STATE_ACCESSED_VALID,
* CACHE_STATE_DATA_VALID,
* CACHE_STATE_ACCESSED_VALID_AGAIN,
* CACHE_STATE_NOT_ACCESSED_VALID_AGAIN
*
* CACHE_STATE_INIT is the initial state. Once the first cache hit
* comes in, the cahce manager calles FetchData to fetch the data
* and goes into CACHE_STATE_ACCESSED_VALID state.
*
* In CACHE_STATE_ACCESSED_VALID state, the cache manager waits
* for the minimum cache timeout and go to CACHE_STATE_DATA_VALID
*
* In CACHE_STATE_DATA_VALID state, the cache manager waits for
* another cache hit withing half of the last fetch time.
*
* If there is a hit during this waiting, the cache manager will go
* for another fetch and go to CACHE_STATE_ACCESSED_VALID state.
*
* If there is no hit during the waiting period, the cache manager
* waits for another timeout or another cache hit.
*
* If there is no access during the waiting, the cache manager
* invalidates the cache. Otherwise, the cache manager go out and
* do anther data fetch and then go to CACHE_STATE_ACCESSED_VALID.
*
*
*
* Copyright (C) 1998-1999 Microsoft Corporation
*
* History:
* 10/16/98 weihaic Created
*
\*****************************************************************************/
#include "precomp.h"
#include "priv.h"
extern BOOL _ppinfo_net_get_info(
IN PCINETMONPORT pIniPort,
OUT PPRINTER_INFO_2 *ppInfo,
OUT LPDWORD lpBufAllocated,
IN ALLOCATORFN pAllocator);
extern BOOL ppjob_EnumForCache(
IN PCINETMONPORT pIniPort,
OUT LPPPJOB_ENUM *ppje);
// cdwMaxCacheValidTime is the maximum expire time for the current cache
// Idealy, we should put a large number to increase the effiency of the cache
// so we choose 30 seconds (30*1000) as the final number. For testing purpose,
// we put 15 seconds to increase the hit of the fetch data code
//
// weihaic 10/23/98
//
const DWORD cdwMaxCacheValidTime = 30*1000; // The cache content will expire after 30 seconds.
const DWORD cdwMinCacheValidTime = 2*1000; // The cache content will be valid for at least 2 seconds
CacheMgr::CacheMgr ():
m_dwState (CACHE_STATE_INIT),
m_pIniPort (NULL),
m_pData (NULL),
m_hDataReadyEvent (NULL),
m_hHitEvent (NULL),
m_hInvalidateCacheEvent (NULL),
m_hThread (NULL),
m_bCacheStopped (FALSE),
m_bAccessed (FALSE),
m_bInvalidateFlag (FALSE),
m_bValid (FALSE)
{
if ((m_hDataReadyEvent = CreateEvent (NULL, TRUE, FALSE, NULL)) &&
(m_hHitEvent = CreateEvent (NULL, FALSE, FALSE, NULL)) &&
(m_hInvalidateCacheEvent = CreateEvent (NULL, FALSE, FALSE, NULL)) )
m_bValid = TRUE;
}
CacheMgr::~CacheMgr ()
{
}
VOID
CacheMgr::Shutdown ()
{
CacheRead.Lock ();
// No more read is possible
m_bCacheStopped = TRUE;
if (m_hThread) {
SetEvent (m_hInvalidateCacheEvent);
// Wait for another thread to exit
WaitForSingleObject (m_hThread, INFINITE);
}
if (m_hDataReadyEvent) {
CloseHandle (m_hDataReadyEvent);
}
if (m_hHitEvent) {
CloseHandle (m_hHitEvent);
}
if (m_hInvalidateCacheEvent) {
CloseHandle (m_hInvalidateCacheEvent);
}
m_bValid = FALSE;
CacheRead.Unlock ();
delete this;
}
BOOL
CacheMgr::SetupAsyncFetch (
PCINETMONPORT pIniPort)
{
BOOL bRet = FALSE;
PTHREADCONTEXT pThreadData = new THREADCONTEXT;
if (pThreadData) {
pThreadData->pIniPort = pIniPort;
pThreadData->pCache = this;
#ifdef WINNT32
pThreadData->pSidToken = new CSid;
if (pThreadData->pSidToken && pThreadData->pSidToken->bValid()) {
if (m_hThread = CreateThread (NULL, COMMITTED_STACK_SIZE, (LPTHREAD_START_ROUTINE)CacheMgr::WorkingThread,
(PVOID) pThreadData, 0, NULL)) {
bRet = TRUE;
}
}
#else
// This parameter can not be deleted since CreateThread() in Win9X requires
// a non-NULL pointer to a DWORD as the last parameter.
DWORD dwThreadId;
if (m_hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)CacheMgr::WorkingThread,
(PVOID) pThreadData, 0, &dwThreadId)) {
bRet = TRUE;
}
#endif
if (!bRet) {
#ifdef WINNT32
if (pThreadData->pSidToken) {
delete pThreadData->pSidToken;
}
#endif
delete (pThreadData);
}
}
return bRet;
}
VOID
CacheMgr::TransitState (
PCINETMONPORT pIniPort)
{
PVOID pData = NULL;
DWORD dwFetchTime = 0;
CACHESTATE dwState, dwOldState;
BOOL bNewData;
HANDLE hHandles[2];
hHandles [0] = m_hHitEvent;
hHandles [1] = m_hInvalidateCacheEvent;
dwState = m_dwState;
do {
DBGMSGT (DBG_CACHE_TRACE, ( TEXT ("TransitState: current state %d"), m_dwState));
dwState = m_dwState;
bNewData = FALSE;
m_bAccessed = FALSE;
switch (dwState) {
case CACHE_STATE_INIT:
// Clean the data ready event
ResetEvent (m_hDataReadyEvent);
if (GetFetchTime (pIniPort, &dwFetchTime, &pData)) {
bNewData = TRUE;
dwState = CACHE_STATE_ACCESSED_VALID;
}
else {
//Invalid Cache Content
DBGMSGT (DBG_CACHE_ERROR, ( TEXT ("TransitState: FatalError %d"), GetLastError));
// Invalidate the cache content, so that when the next get comes, it will
// get the NULL pointer.
pData = NULL;
bNewData = TRUE;
dwState = CACHE_STATE_ACCESSED_VALID;
}
break;
case CACHE_STATE_ACCESSED_VALID:
WaitForSingleObject (m_hInvalidateCacheEvent, cdwMinCacheValidTime);
dwState = CACHE_STATE_DATA_VALID;
break;
case CACHE_STATE_DATA_VALID:
WaitForSingleObject (m_hInvalidateCacheEvent, dwFetchTime / 2);
if (m_bAccessed) {
// The cache has been accessed during the waiting time
dwState = CACHE_STATE_ACCESSED_VALID_AGAIN;
}
else {
// The cache has not been accessed during the waiting time
dwState = CACHE_STATE_NOT_ACCESSED_VALID_AGAIN;
}
break;
case CACHE_STATE_ACCESSED_VALID_AGAIN:
if (GetFetchTime (pIniPort, &dwFetchTime, &pData)) {
bNewData = TRUE;
dwState = CACHE_STATE_ACCESSED_VALID;
}
else {
//Invalid Cache Content
DBGMSGT (DBG_CACHE_ERROR, ( TEXT ("TransitState: FatalError %d"), GetLastError));
// Invalidate the cache content, so that when the next access to cache comes,
// it will get a NULL pointer.
pData = NULL;
bNewData = TRUE;
dwState = CACHE_STATE_ACCESSED_VALID;
}
break;
case CACHE_STATE_NOT_ACCESSED_VALID_AGAIN:
// This has to be a long wait so that the cache will be valid for an access long after
// the last fetch happeen
ResetEvent (m_hHitEvent);
switch (WaitForMultipleObjects (2, hHandles, FALSE, cdwMaxCacheValidTime ))
{
case WAIT_TIMEOUT:
dwState = CACHE_STATE_INIT;
break;
case WAIT_OBJECT_0:
//Accessed
if (GetFetchTime (pIniPort, &dwFetchTime, &pData)) {
bNewData = TRUE;
dwState = CACHE_STATE_ACCESSED_VALID;
}
else {
//Invalid Cache Content
DBGMSGT (DBG_CACHE_ERROR, ( TEXT ("TransitState: FatalError %d"), GetLastError));
// Invalidate the cache content, so that when the next access to cache comes,
// it will get a NULL pointer.
pData = NULL;
bNewData = TRUE;
dwState = CACHE_STATE_ACCESSED_VALID;
}
break;
case WAIT_OBJECT_0 + 1:
dwState = CACHE_STATE_INIT;
break;
default:
// ERROR
DBGMSGT (DBG_CACHE_ERROR, ( TEXT ("TransitState: WaitForSingleObject FatalError %d"), GetLastError));
// Invalidate the cache
dwState = CACHE_STATE_INIT;
break;
}
break;
default:
DBGMSGT (DBG_CACHE_ERROR, (TEXT ("AsyncFech: wrong state %d"), m_dwState));
// Invalidate the cache
dwState = CACHE_STATE_INIT;
}
if (m_bCacheStopped) {
// Cache is being stopped. Cleanup everything this thread generates
if (bNewData) {
FreeBuffer (pIniPort, pData);
}
// Since the caching thread is going to abort, so we need to raise
// this flag so that the waiting thread can go on.
SetEvent (m_hDataReadyEvent);
break;
}
dwOldState = m_dwState;
SetState (pIniPort, dwState, bNewData, pData);
if (dwOldState == CACHE_STATE_INIT) {
SetEvent (m_hDataReadyEvent);
}
if (m_bInvalidateFlag) {
// Another thread called invalidate thread and hope get rid of the thread
m_dwState = CACHE_STATE_INIT;
}
} while ( m_dwState != CACHE_STATE_INIT );
// Terminate the async fetch thread
HANDLE hThread = m_hThread;
m_hThread = NULL;
CloseHandle (hThread);
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("CacheMgr::TransitState: Async thread quit")));
return;
}
VOID
CacheMgr::SetState (
PCINETMONPORT pIniPort,
CACHESTATE dwState,
BOOL bNewData,
PVOID pNewData)
{
PVOID pOldData = NULL;
m_bAccessed = 0;
if (bNewData) {
CacheData.Lock ();
pOldData = m_pData;
m_pData = pNewData;
CacheData.Unlock ();
}
// This line must be here, since otherwise, the state is updated to the new one
// but the data are not
//
m_dwState = dwState;
if (pOldData) {
FreeBuffer (pIniPort, pOldData);
}
}
VOID
CacheMgr::WorkingThread (
PTHREADCONTEXT pThreadData)
{
CacheMgr *pThis = pThreadData->pCache;
PCINETMONPORT pIniPort = pThreadData->pIniPort;
#ifdef WINNT32
pThreadData->pSidToken->SetCurrentSid ();
delete pThreadData->pSidToken;
pThreadData->pSidToken = NULL;
#endif
delete pThreadData;
pThis->TransitState (pIniPort);
}
BOOL
CacheMgr::GetFetchTime (
PCINETMONPORT pIniPort,
LPDWORD pdwTime,
PVOID *ppData)
{
BOOL bRet = FALSE;
DWORD dwT0, dwT1;
dwT0 = GetTickCount();
if (FetchData (pIniPort, ppData)) {
bRet = TRUE;
}
dwT1 = GetTickCount();
*pdwTime = GetTimeDiff (dwT0, dwT1);
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("GetFetchTime: returns %d, timediff=%d ms"),
bRet, GetTimeDiff (dwT0, dwT1)));
return bRet;
}
PVOID
CacheMgr::BeginReadCache (
PCINETMONPORT pIniPort)
{
if (m_bValid) {
CacheRead.Lock ();
#ifdef WINNT32
CUserData CurUser;
while (TRUE) {
if (m_dwState == CACHE_STATE_INIT) {
m_CurUser = CurUser;
if (!m_hThread) {
// If there is no thread running, we need to reset the dataready event
ResetEvent (m_hDataReadyEvent);
}
if (m_hThread // There is already a thread running
|| SetupAsyncFetch (pIniPort)) {
// We must leave the critical section since it might take forever to
// get the information at the first time
CacheRead.Unlock ();
WaitForSingleObject (m_hDataReadyEvent, INFINITE);
CacheRead.Lock ();
}
break;
}
else {
if (m_CurUser == CurUser) {
if (m_dwState == CACHE_STATE_NOT_ACCESSED_VALID_AGAIN) {
SetEvent (m_hHitEvent);
}
break;
}
else {
// We must call the internal version of InvalidateCache since
// we have to leave the critical section when waiting for the termination
// of the working thread.
//
_InvalidateCache ();
// Now, the state becomes CACHE_STATE_INIT
}
}
}
#else
// Win9X case, no security check
while (TRUE) {
if (m_dwState == CACHE_STATE_INIT) {
if (!m_hThread) {
// If there is no thread running, we need to reset the dataready event
ResetEvent (m_hDataReadyEvent);
}
if (m_hThread // There is already a thread running
|| SetupAsyncFetch (pIniPort)) {
// We must leave the critical section since it might take forever to
// get the information at the first time
CacheRead.Unlock ();
WaitForSingleObject (m_hDataReadyEvent, INFINITE);
CacheRead.Lock ();
}
break;
}
else {
if (m_dwState == CACHE_STATE_NOT_ACCESSED_VALID_AGAIN) {
SetEvent (m_hHitEvent);
}
break;
}
}
#endif
m_bAccessed = TRUE;
BOOL bRet = CacheData.Lock ();
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("CacheData.Lock() = %d"), bRet));
return m_pData;
}
else
return NULL;
}
VOID
CacheMgr::EndReadCache (VOID)
{
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("CacheMgr::EndReadCache: Entered")));
if (m_bValid) {
BOOL bRet = CacheData.Unlock ();
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("CacheData.Unlock() = %d"), bRet));
CacheRead.Unlock ();
}
}
VOID
CacheMgr::_InvalidateCache ()
{
if (!m_bInvalidateFlag) {
m_bInvalidateFlag = TRUE;
SetEvent (m_hInvalidateCacheEvent);
}
if (m_hThread) {
CacheRead.Unlock ();
// We must leave the critical section since we don't know how long it will take for thread to exit
// Wait for another thread to exit
WaitForSingleObject (m_hThread, INFINITE);
CacheRead.Lock ();
}
// Clean up the event
ResetEvent (m_hInvalidateCacheEvent);
m_bInvalidateFlag = FALSE;
DBGMSGT (DBG_CACHE_TRACE, ( TEXT ("CacheMgr::InvalidateCache dwState=%d"), m_dwState));
}
VOID
CacheMgr::InvalidateCache ()
{
CacheRead.Lock ();
_InvalidateCache ();
CacheRead.Unlock ();
}
#if (defined(WINNT32))
VOID
CacheMgr::InvalidateCacheForUser(
CLogonUserData *pUser )
/*++
Routine Description:
This routine checks to see whether the given user is currently controlling the cache
thread. If they are, the thread is terminated.
Arguments:
pUser - A pointer to the user.
Return Value:
None.
--*/
{
CacheRead.Lock();
if (m_CurUser == *(CUserData *)pUser) // Comparison is valid after caste
_InvalidateCache ();
CacheRead.Unlock();
}
#endif
inline DWORD
CacheMgr::GetTimeDiff (
DWORD t0,
DWORD t1)
{
if (t1 > t0) {
return t1 - t0;
}
else {
return DWORD(-1) - t0 + t1;
}
}
LPVOID CacheMgr::Allocator(
DWORD cb)
{
return new CHAR[cb];
}
GetPrinterCache::GetPrinterCache(
PCINETMONPORT pIniPort):
m_pIniPort (pIniPort)
{
}
GetPrinterCache::~GetPrinterCache (
VOID)
{
if (m_pData) {
FreeBuffer (m_pIniPort, m_pData);
}
}
BOOL
GetPrinterCache::FetchData (
PCINETMONPORT pIniPort,
PVOID *ppData)
{
BOOL bRet = FALSE;
PGETPRINTER_CACHEDATA pCacheData = NULL;
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("GetPrinterCache::FetchData begins")));
#ifdef DEBUG
if (0) {
// Simulate the hanging of the current thread
// For debugging purpose.
MessageBox (NULL, TEXT ("GetPrinterCache::FetchData called. Press OK to continue."), TEXT ("ALERT"), MB_OK);
}
#endif
if (pCacheData = new GETPRINTER_CACHEDATA) {
semEnterCrit ();
pCacheData->bRet = _ppinfo_net_get_info(pIniPort,
& (pCacheData->pInfo) ,
& (pCacheData->cbSize),
Allocator);
semLeaveCrit ();
pCacheData->dwLastError = GetLastError ();
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("GetPrinterCache::FetchData bRet=%d, err=%d\n"),
pCacheData->bRet, pCacheData->dwLastError ));
//
// We must return TRUE, otherwise PPGetPrinter won't get the correct last error
//
bRet = TRUE; //pCacheData->cbSize != 0;
}
if (!bRet) {
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("GetPrinterCache::FetchData failed, call FreeBuffer (%x, %x)"), pIniPort, pCacheData));
FreeBuffer (pIniPort, pCacheData);
}
else {
*ppData = pCacheData;
}
return bRet;
}
BOOL
GetPrinterCache::FreeBuffer (
PCINETMONPORT pIniPort,
PVOID pData)
{
PGETPRINTER_CACHEDATA pCacheData = (PGETPRINTER_CACHEDATA) pData;
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("FreeBuffer(%x, %x) called"), pIniPort, pCacheData));
if (pCacheData) {
if (pCacheData->pInfo) {
delete [] (PCHAR)(pCacheData->pInfo);
}
delete pCacheData;
}
return TRUE;
}
BOOL
GetPrinterCache::BeginReadCache (
PPRINTER_INFO_2 *ppInfo)
{
BOOL bRet = FALSE;
if (m_bValid) {
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("GetPrinterCache::BeginReadCache: Entered")));
PGETPRINTER_CACHEDATA pData = (PGETPRINTER_CACHEDATA) CacheMgr::BeginReadCache (m_pIniPort);
if (pData) {
if (pData->bRet) {
*ppInfo = pData->pInfo;
bRet = TRUE;
}
else {
SetLastError (pData->dwLastError);
}
}
if (!bRet && GetLastError () == ERROR_SUCCESS) {
SetLastError (ERROR_CAN_NOT_COMPLETE);
}
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("GetPrinterCache::BeginReadCache: pData = %x, return = %d, lasterror = %d"),
pData, bRet, GetLastError ()));
}
else {
SetLastError (ERROR_CAN_NOT_COMPLETE);
}
return bRet;
}
EnumJobsCache::EnumJobsCache(
PCINETMONPORT pIniPort):
m_pIniPort (pIniPort)
{
}
EnumJobsCache::~EnumJobsCache (
VOID)
{
if (m_pData) {
FreeBuffer (m_pIniPort, m_pData);
}
}
BOOL
EnumJobsCache::FetchData (
PCINETMONPORT pIniPort,
PVOID *ppData)
{
BOOL bRet = FALSE;
HANDLE hPort = (HANDLE)pIniPort;
PENUMJOBS_CACHEDATA pCacheData = NULL;
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("EnumJobsCache::FetchData begins")));
if (pCacheData = new ENUMJOBS_CACHEDATA) {
pCacheData->pje = NULL;
semEnterCrit ();
pCacheData->bRet = ppjob_EnumForCache(pIniPort, & (pCacheData->pje));
pCacheData->dwLastError = GetLastError ();
DBGMSGT (DBG_CACHE_TRACE, (TEXT ("EnumJobsCache::FetchData bRet=%d, err=%d\n"),
pCacheData->bRet, pCacheData->dwLastError ));
if (pCacheData->bRet) {
if (pCacheData->pje) {
pCacheData->cbSize = pCacheData->pje->cbSize;
}
else {
pCacheData->cbSize = 0;
}
}
semLeaveCrit ();
bRet = TRUE;
}
if (bRet) {
*ppData = pCacheData;
}
return bRet;
}
BOOL
EnumJobsCache::FreeBuffer (
PCINETMONPORT pIniPort,
PVOID pData)
{
PENUMJOBS_CACHEDATA pCacheData = (PENUMJOBS_CACHEDATA) pData;
if (pCacheData) {
if (pCacheData->pje) {
// memFree has access to the global link list, so it is neccesary
// to run it under critical secion.
//
semEnterCrit ();
memFree(pCacheData->pje, memGetSize(pCacheData->pje));
semLeaveCrit ();
}
delete pCacheData;
}
return TRUE;
}
BOOL
EnumJobsCache::BeginReadCache (
LPPPJOB_ENUM *ppje)
{
BOOL bRet = FALSE;
if (m_bValid) {
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("EnumJobsCache::BeginReadCache: Entered")));
PENUMJOBS_CACHEDATA pData = (PENUMJOBS_CACHEDATA) CacheMgr::BeginReadCache (m_pIniPort);
if (pData) {
if (pData->bRet) {
*ppje = pData->pje;
bRet = TRUE;
}
else {
SetLastError (pData->dwLastError);
}
}
if (!bRet && GetLastError () == ERROR_SUCCESS) {
SetLastError (ERROR_CAN_NOT_COMPLETE);
}
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("EnumJobsCache::BeginReadCache: pData = %x, return = %d, lasterror = %d"),
pData, bRet, GetLastError ()));
}
else {
SetLastError (ERROR_CAN_NOT_COMPLETE);
}
return bRet;
}
VOID
EnumJobsCache::EndReadCache (
VOID)
{
DBGMSGT (DBG_CACHE_TRACE,
(TEXT ("EnumJobsCache::EndReadCache: Entered")));
CacheMgr::EndReadCache ();
}
/*********************************************************************************
** End of File (cachemgr.cxx)
*********************************************************************************/