|
|
/*=========================================================================*\
Module: idlethrd.cpp
Copyright Microsoft Corporation 1998, All Rights Reserved.
Author: zyang
Description: Idle thread implementation
\*=========================================================================*/
#include <windows.h>
#include <limits.h>
#include <caldbg.h>
#include <ex\exmem.h>
#include <ex\autoptr.h>
#include <ex\idlethrd.h>
#pragma warning(disable:4127) // conditional expression is constant
#pragma warning(disable:4244) // possible loss of data
class CIdleThread;
// Globals
//
CIdleThread * g_pIdleThread = NULL;
// Debugging -----------------------------------------------------------------
//
DEFINE_TRACE(IdleThrd); #define IdleThrdTrace DO_TRACE(IdleThrd)
enum { EVENT_SHUTDOWN = WAIT_OBJECT_0, EVENT_REGISTER = WAIT_OBJECT_0 + 1 };
// class CIdleThread
//
// This is the idle thread implementation. it accept clients callback
// registration, and call back to the client when timeout.
//
// Instead of periodically wake up the thread, we maitain the minimal
// time to wait, so that we wake only when it is necessary.
//
// As there could be a huge number of the registrations, so we maintain
// a heap ordered by their next timeout value. so that we don't have to
// iterate through all the registrations each time.
//
// When the callback is registered, DwWait will be called to get
// initial timeout. Client can return zero thus cause the Execute be called
// immediately.
//
class CIdleThread { private:
struct REGISTRATION { __int64 m_i64Wakeup; // Time to wake up
auto_ref_ptr<IIdleThreadCallBack> m_pCallBack; // Call back object
};
struct IDLETHREADTASKITEM { BOOL m_fRegister; // TRUE - register,
// FALSE - unregister
auto_ref_ptr<IIdleThreadCallBack> m_pCallBack; // Call back object
};
// Default starting chunk size (in number of registrations)
//
enum { #ifdef DBG
CHUNKCOUNT_START = 2 // must be 2 or greater, as our first reg starts at index 1
#else
CHUNKCOUNT_START = 8192 / sizeof (REGISTRATION) #endif
};
HANDLE m_hIdleThread; HANDLE m_hevShutDown; // signaled to inform the idle
// thread to shutdown
HANDLE m_hevRegister; // signaled when new registration
// comes.
CRITICAL_SECTION m_csRegister; // Used to serialize registration operations
ULONG m_cSize; // Length of the current priority queue
ULONG m_cAllocated; // Size of the physical array allocated
REGISTRATION * m_pData; // The registration priority queue.
// prioritized on the wake up time.
LONG m_lSleep; // Time to sleep before wakeup.
// Negative or 0 means wakeup immediately.
// To sleep forever, use LONG_MAX instead
// of INFINITE because INFINITE (as a LONG)
// is a *negative* number (i.e. it would be
// interpreted as wake up immediately).
IDLETHREADTASKITEM * m_pTask; // Array of reg/unregs to be processed
ULONG m_cTask; // number of reg/unregs to be processed
ULONG m_cTaskAllocated; // size of the array
BOOL FStartIdleThread();
static DWORD __stdcall DwIdleThreadProc(PVOID pvThreadData);
inline VOID HeapAdd (); inline VOID HeapDelete (ULONG ulIndex);
//$HACK
// In order to avoid calling SetIndex on the deleted object
// in Exchange, we pass in a flag to indicate whether this
// Exchange() call is to delete a node, if so, then we should
// not call SetIndex on the node that is at the end of the queue
// (which is to be deleted)
//$HACK
inline VOID Exchange (ULONG ulIndex1, ULONG ulIndex2, BOOL fDelete = FALSE); inline VOID Heapify (ULONG ulIndex);
VOID EnterReg() { EnterCriticalSection (&m_csRegister); } VOID LeaveReg() { LeaveCriticalSection (&m_csRegister); }
// non-implemented
//
CIdleThread( const CIdleThread& ); CIdleThread& operator=( const CIdleThread& );
public: CIdleThread () : m_cSize (0), m_cAllocated (0), m_lSleep (LONG_MAX), m_pData (NULL), m_pTask (NULL), m_cTaskAllocated (0), m_cTask (0), m_hIdleThread (NULL), m_hevShutDown (NULL), m_hevRegister (NULL) { INIT_TRACE (IdleThrd); InitializeCriticalSection (&m_csRegister); }
~CIdleThread();
BOOL FAddNewTask (IIdleThreadCallBack * pCallBack, BOOL fRegister); };
// CIdleThread::DwIdleThreadProc
// This is idle thread implementation.
//
DWORD __stdcall CIdleThread::DwIdleThreadProc(PVOID pvThreadData) { // Get the CIdlThread object
//
CIdleThread * pit = reinterpret_cast<CIdleThread *>( pvThreadData ); HANDLE rgh[2]; FILETIME ftNow; DWORD dw;
// This thread wait for two events:
// shutdown event, and
// register event.
//
rgh[0] = pit->m_hevShutDown; rgh[1] = pit->m_hevRegister;
// This thread maintains a mininum timeout it could wait.
// and would wake up when it tiemouts
do { DWORD dwRet;
dwRet = WaitForMultipleObjects(2, // two events
rgh, // event handles
FALSE, // return if any event signaled
pit->m_lSleep);// timeout in milliseconds.
// If our shutdown event handle was signalled, suicide.
// (OR if the event object is gonzo....)
//
switch (dwRet) { case WAIT_TIMEOUT:
//$REVIEW
// How accurate do we use the time? is a snapshot like this enough?
// or we may need to this inside the loop
//
GetSystemTimeAsFileTime( &ftNow );
// Now that Unregister is supported, we need to check the size of
// the heap before we call back, as it's possible the call back
// has been unregistered.
//
while (pit->m_cSize && (pit->m_pData[1].m_i64Wakeup <= *(__int64 *)(&ftNow))) { // Call back to client
// Unregister if client required
//
Assert (pit->m_pData[1].m_pCallBack->UlIndex() == 1); if (!pit->m_pData[1].m_pCallBack->FExecute()) { pit->m_pData[1].m_pCallBack.clear();
//$HACK
// In order to avoid calling SetIndex on the deleted object
// in Exchange, we pass in a flag to indicate whether this
// Exchange() call is to delete a node, if so, then we should
// not call SetIndex on the node that is at the end of the queue
// (which is to be deleted)
//$HACK
pit->Exchange (1, pit->m_cSize, TRUE); pit->m_cSize--; if (!pit->m_cSize) break; } else { // Get the next wakeup time
// 1 millisecond = 10,000 of 100-nanoseconds
//
pit->m_pData[1].m_i64Wakeup = *(__int64 *)(&ftNow) + static_cast<__int64>(pit->m_pData[1].m_pCallBack->DwWait()) * 10000; }
// Get the next value
//
pit->Heapify(1); }
// Compute how long to wait before the next timeout
//
if (!pit->m_cSize) pit->m_lSleep = LONG_MAX; else { pit->m_lSleep = (pit->m_pData[1].m_i64Wakeup - *(__int64 *)(&ftNow)) / 10000; if (pit->m_lSleep < 0) { IdleThrdTrace ("Dav: Idle: zero or negative sleep: idle too active?"); pit->m_lSleep = 0; } }
IdleThrdTrace ("Dav: Idle: next idle action in:\n" "- milliseconds: %ld\n" "- seconds: %ld\n", pit->m_lSleep, pit->m_lSleep / 1000); break;
case EVENT_REGISTER: { ULONG ul; ULONG ulNew;
// Register the callback and obtain the initial timeout setting
//$REVIEW
// How accurate do we use the time? is a snapshot like this enough?
// or we may need to this inside the loop
//
GetSystemTimeAsFileTime( &ftNow );
// Make sure no one would add a new reg when we process the
// new regs
//
pit->EnterReg();
// It's possbile we've processed all the new regs in the
// last time we were signaled
//
if (pit->m_cTask) { // Expand the queue to the maximum possible required length
//
if (pit->m_cSize + pit->m_cTask >= pit->m_cAllocated) { REGISTRATION * pData = NULL; ULONG cNewSize = 0;
if (!pit->m_pData) { // Initial size of the priority queue
//$Note: we need at least one more slot for exchange
//
cNewSize = max(pit->m_cTask + 1, CHUNKCOUNT_START);
pData = static_cast<REGISTRATION *>(ExAlloc ( cNewSize * sizeof (REGISTRATION)));
} else { // Double the size, to get "logarithmic allocation behavior"
//
cNewSize = (pit->m_cSize + pit->m_cTask) * 2;
// Realloc the array
// If the realloc fails, the original remain unchanged
//
pData = static_cast<REGISTRATION *>(ExRealloc (pit->m_pData, cNewSize * sizeof(REGISTRATION))); }
// It's possible that allocation failed
//
if (!pData) { //$REVIEW: Anything else can we do other than a debugtrace ?
//
IdleThrdTrace ("Cannot allocate more space\n"); pit->LeaveReg(); break; }
// Initialize
//
ZeroMemory (pData + pit->m_cSize + 1, sizeof(REGISTRATION) * (cNewSize - pit->m_cSize - 1));
// Update information
//
pit->m_pData = pData; pit->m_cAllocated = cNewSize;
IdleThrdTrace ("priority queue size = %d\n", pit->m_cAllocated); }
for (ul=0; ul < pit->m_cTask; ul++) { if (pit->m_pTask[ul].m_fRegister) { // New position of the reg
//
ulNew = pit->m_cSize + 1;
IdleThrdTrace ("Dav: Idle: add new reg %x\n", pit->m_pTask[ul].m_pCallBack.get());
dw = pit->m_pTask[ul].m_pCallBack->DwWait(); pit->m_pData[ulNew].m_pCallBack.take_ownership (pit->m_pTask[ul].m_pCallBack.relinquish());
// dw is give in milliseconds, FILETIME unit is 100-nanoseconds.
//
pit->m_pData[ulNew].m_i64Wakeup = *(__int64 *)(&ftNow) + static_cast<__int64>(dw) * 10000;
// Update the index
//
pit->m_pData[ulNew].m_pCallBack->SetIndex(ulNew);
// Add to the heap, m_cSize is updated inside
//
pit->HeapAdd(); } else { Assert (pit->m_pTask[ul].m_pCallBack->UlIndex() <= pit->m_cSize);
IdleThrdTrace ("Dav: Idle: delete reg %x\n", pit->m_pTask[ul].m_pCallBack.get());
// Delete from the priority queue, m_cSize is updated inside
// it also release our ref on the deleted object
//
pit->HeapDelete (pit->m_pTask[ul].m_pCallBack->UlIndex());
pit->m_pTask[ul].m_pCallBack.clear(); } }
// Now that all task item are processed, reset
//
pit->m_cTask = 0; }
// Done with the task array
//
pit->LeaveReg();
// Compute the mininum time to wait
//
if (pit->m_cSize) { pit->m_lSleep = (pit->m_pData[1].m_i64Wakeup - *(__int64 *)(&ftNow)) / 10000; if (pit->m_lSleep < 0) { IdleThrdTrace ("Dav: Idle: zero or negative sleep: " "idle too active?"); pit->m_lSleep = 0; } } else pit->m_lSleep = LONG_MAX;
break; }
default: // Either shutdown event is signaled or other failure
//
#ifdef DBG
if (dwRet != EVENT_SHUTDOWN) { IdleThrdTrace ("Dav: Idle: thread quit because of failure\n"); if (WAIT_FAILED == dwRet) { IdleThrdTrace ("Dav: Idle: last error = %d\n", GetLastError()); } } #endif
for (UINT i = 1; i <= pit->m_cSize; i++) { // Tell clients that the idle thread is being shutdown
//
Assert (pit->m_pData[i].m_pCallBack->UlIndex() == i); pit->m_pData[i].m_pCallBack->Shutdown (); pit->m_pData[i].m_pCallBack.clear(); }
IdleThrdTrace ("Dav: Idle: thread is stopping\n");
// Shutdown this thread
//
return 0; }
} while (TRUE); }
CIdleThread::~CIdleThread() { if (m_hevShutDown && INVALID_HANDLE_VALUE != m_hevShutDown) { // Signal the idle thread to shutdown
//
SetEvent(m_hevShutDown);
// Wait for the idle thread to shutdown
//
WaitForSingleObject(m_hIdleThread, INFINITE); }
CloseHandle (m_hevShutDown); CloseHandle (m_hevRegister);
//$REVIEW
// Do I need to close the thread handle?
//$REVIEW
// Yes,
//
CloseHandle (m_hIdleThread);
DeleteCriticalSection (&m_csRegister);
// Free our array of items.
ExFree (m_pData); ExFree (m_pTask);
IdleThrdTrace ("DAV: Idle: CIdleThread destroyed\n"); }
//
// CIdleThread::FStartIdleThead
//
// Helper method to start the idle thread and create the events
//
BOOL CIdleThread::FStartIdleThread() { BOOL fRet = FALSE; DWORD dwThreadId;
m_hevShutDown = CreateEvent( NULL, // handle cannot be inherited
FALSE, // auto reset
FALSE, // nosignaled
NULL); // no name
if (!m_hevShutDown) { IdleThrdTrace( "Failed to create event: error: %d", GetLastError() ); TrapSz( "Failed to create idle thread event" ); goto ret; }
m_hevRegister = CreateEvent( NULL, // handle cannot be inherited
FALSE, // auto reset
FALSE, // nosignaled
NULL); // no name
if (!m_hevRegister) { IdleThrdTrace( "Failed to create register event: error: %d", GetLastError() ); TrapSz( "Failed to create register event" ); goto ret; }
m_hIdleThread = CreateThread( NULL, 0, DwIdleThreadProc, this, 0, // Start immediately -- no need to resume...
&dwThreadId ); if (!m_hIdleThread) { IdleThrdTrace( "Failed to create thread: error: %d", GetLastError() ); TrapSz( "Failed to create Notif Cache Timer thread" ); goto ret; }
fRet = TRUE;
ret: if (!fRet) { if (m_hevShutDown) { if (m_hevRegister && (INVALID_HANDLE_VALUE != m_hevRegister)) { CloseHandle (m_hevRegister); m_hevRegister = NULL; }
CloseHandle (m_hevShutDown); m_hevShutDown = NULL; } } return fRet; }
//
// CIdleThread::HeapAdd
//
// Add the the next node into the priority queue
//
inline VOID CIdleThread::HeapAdd () { ULONG ulCur = m_cSize + 1; ULONG ulParent;
Assert (m_pData);
// We go bottom up, compare the node with the parent node,
// exchange the two nodes if the child win. it stops when
// the parent win.
//
while ( ulCur != 1) { ulParent = ulCur >> 1; if (m_pData[ulParent].m_i64Wakeup <= m_pData[ulCur].m_i64Wakeup) break;
Exchange (ulCur, ulParent);
ulCur = ulParent; }
m_cSize++; }
//
// CIdleThread::HeapDelete
//
// Delete an arbitary node from the priority queue
//
inline VOID CIdleThread::HeapDelete (ULONG ulIndex) { Assert (ulIndex <= m_cSize);
// Exchange the node to the end of the queue first
// then heapify to maintain the heap property
//
//$HACK
// In order to avoid calling SetIndex on the deleted object
// in Exchange, we pass in a flag to indicate whether this
// Exchange() call is to delete a node, if so, then we should
// not call SetIndex on the node that is at the end of the queue
// (which is to be deleted)
//$HACK
Exchange (ulIndex, m_cSize, TRUE); m_cSize--; Heapify (ulIndex); // Must Release our ref. m_cSize+1 is the one just deleted
//
m_pData[m_cSize+1].m_pCallBack.clear(); }
//
// CIdleThread::Exchange
//
// Exchange two nodes
//
//$HACK
// In order to avoid calling SetIndex on the deleted object
// in Exchange, we pass in a flag to indicate whether this
// Exchange() call is to delete a node, if so, then we should
// not call SetIndex on the node that is at the end of the queue
// (which is to be deleted)
//$HACK
inline VOID CIdleThread::Exchange (ULONG ulIndex1, ULONG ulIndex2, BOOL fDelete) { Assert ((ulIndex1 != 0) && (ulIndex1 <= m_cAllocated) && (ulIndex2 != 0) && (ulIndex2 <= m_cAllocated));
if (ulIndex1 != ulIndex2) { // Use the 0th node as the temp node
//
CopyMemory (m_pData, m_pData + ulIndex1, sizeof(REGISTRATION)); CopyMemory (m_pData + ulIndex1, m_pData + ulIndex2, sizeof(REGISTRATION)); CopyMemory (m_pData + ulIndex2, m_pData, sizeof(REGISTRATION));
// Remember the index to facilitate unregister
// Note the index is set if only the node is not deleted
//
if (!((ulIndex1 == m_cSize) && fDelete)) m_pData[ulIndex1].m_pCallBack->SetIndex (ulIndex1);
if (!((ulIndex2 == m_cSize) && fDelete)) m_pData[ulIndex2].m_pCallBack->SetIndex (ulIndex2); } }
//
// CIdleThread::Heapify
//
// Maintain the heap property
//
inline VOID CIdleThread::Heapify (ULONG ulIndex) { ULONG ulLeft; ULONG ulRight; ULONG ulWin;
Assert (m_pData);
while (ulIndex <= m_cSize) { // Find out the winner (i.e. the one with earlier wakeup time)
// between the parent and left node.
//
ulLeft = ulIndex * 2; if (ulLeft > m_cSize) break; if (m_pData[ulIndex].m_i64Wakeup > m_pData[ulLeft].m_i64Wakeup) ulWin = ulLeft; else ulWin = ulIndex;
// Compare with the right node, and find out the final winner
//
ulRight = ulLeft + 1; if (ulRight <= m_cSize) { if (m_pData[ulWin].m_i64Wakeup > m_pData[ulRight].m_i64Wakeup) ulWin = ulRight; }
// If the parent node is already the winner, then we are done,
//
if (ulIndex == ulWin) break;
// Otherwise, exchange the parent node and winner node,
//
Exchange (ulWin, ulIndex); ulIndex = ulWin; } }
//
// CIdleThread::FAddNewTask
//
// Called by client to register or unregister a callback object
//
BOOL CIdleThread::FAddNewTask (IIdleThreadCallBack * pCallBack, BOOL fRegister) { BOOL fRet = TRUE;
// Caller must garantee a valid callback object
//
Assert (pCallBack);
EnterReg();
Assert (!m_cTaskAllocated || (m_cTask <= m_cTaskAllocated));
// Allocate more space if necessary
//
if (m_cTask == m_cTaskAllocated) { IDLETHREADTASKITEM * pTask = NULL; ULONG cNewSize = 0;
if (!m_pTask) { Assert (m_cTask == 0);
// Initial size of the priority queue
// Starting at 8, we don't expect this queue will grow too big
//
cNewSize = 8;
// Start idle thread when add the first registration
//
if (!FStartIdleThread ()) { fRet = FALSE; goto ret; }
pTask = static_cast<IDLETHREADTASKITEM *>(ExAlloc ( cNewSize * sizeof (IDLETHREADTASKITEM))); } else { // Double the size, to get "logarithmic allocation behavior"
//
cNewSize = m_cTaskAllocated * 2;
// Realloc the array
// If the realloc fails, the original remain unchanged
//
pTask = static_cast<IDLETHREADTASKITEM *>(ExRealloc (m_pTask, cNewSize * sizeof(IDLETHREADTASKITEM))); }
// It's possible that allocation failed
//
if (!pTask) { fRet = FALSE; goto ret; }
// Must initialize, otherwise, we may start with uninitialize auto_ref_ptr
//
ZeroMemory (pTask + m_cTask, sizeof(IDLETHREADTASKITEM) * (cNewSize - m_cTask));
// Update information
//
m_pTask = pTask; m_cTaskAllocated = cNewSize;
IdleThrdTrace ("Taskitem queue size = %d\n", m_cTaskAllocated); }
// Remember the new registration
//
m_pTask[m_cTask].m_pCallBack = pCallBack; m_pTask[m_cTask].m_fRegister = fRegister; m_cTask++;
IdleThrdTrace ("New reg %x added at %d\n", pCallBack, m_cTask-1);
ret: LeaveReg();
// Inform the idle thread that new registration arrived
//
if (fRet) SetEvent (m_hevRegister);
return fRet; }
// FInitIdleThread
//
// Initialize the idle thread object. It can be out only once,
// Note this call only initialize the CIdleThread object, the
// idle thread is not started until the first registration
//
BOOL FInitIdleThread() { Assert (!g_pIdleThread);
g_pIdleThread = new CIdleThread();
return (g_pIdleThread != NULL); }
// FDeleteIdleThread
//
// Delete the idle thread object. again, it can be called only once.
//
// Note this must be called before any other uninitialization work,
// Because we don't own a ref to the callback object, all what we
// have is a pointer to the object. in the shutdown time, we must
// clear all the callback registration before the callback object
// go away.
//
VOID DeleteIdleThread() { if (g_pIdleThread) delete g_pIdleThread; }
// FRegister
//
// Register a callback
//
BOOL FRegister (IIdleThreadCallBack * pCallBack) { Assert (g_pIdleThread);
return g_pIdleThread->FAddNewTask (pCallBack, TRUE); }
VOID Unregister (IIdleThreadCallBack * pCallBack) { Assert (g_pIdleThread); g_pIdleThread->FAddNewTask (pCallBack, FALSE); }
|