|
|
//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 2000.
//
// File: QQueue.cxx
//
// Contents: Query queue
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <worker.hxx>
#include <ciregkey.hxx>
#include <regacc.hxx>
#include <imprsnat.hxx>
extern BOOL g_fPerfmonCounterHackIsProcessDetached;
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::GetWorkQueueRegParams, public
//
// Synopsis: Fetches registry params for the work queue
//
// History: 30-Dec-96 SrikantS Moved the code from RefreshRegParams
//
//--------------------------------------------------------------------------
void CWorkQueue::GetWorkQueueRegParams( ULONG & cMaxActiveThreads, ULONG & cMinIdleThreads ) { CRegAccess reg( RTL_REGISTRY_CONTROL, wcsRegAdmin );
if ( CWorkQueue::workQueueQuery == _QueueType ) { cMaxActiveThreads = reg.Read( wcsMaxActiveQueryThreads, CI_MAX_ACTIVE_QUERY_THREADS_DEFAULT, CI_MAX_ACTIVE_QUERY_THREADS_MIN, CI_MAX_ACTIVE_QUERY_THREADS_MAX ); cMinIdleThreads = reg.Read( wcsMinIdleQueryThreads, CI_MIN_IDLE_QUERY_THREADS_DEFAULT, CI_MIN_IDLE_QUERY_THREADS_MIN, CI_MIN_IDLE_QUERY_THREADS_MAX ); } else if ( CWorkQueue::workQueueRequest == _QueueType ) { cMaxActiveThreads = reg.Read( wcsMaxActiveRequestThreads, CI_MAX_ACTIVE_REQUEST_THREADS_DEFAULT, CI_MAX_ACTIVE_REQUEST_THREADS_MIN, CI_MAX_ACTIVE_REQUEST_THREADS_MAX ); cMinIdleThreads = reg.Read( wcsMinIdleRequestThreads, CI_MIN_IDLE_REQUEST_THREADS_DEFAULT, CI_MIN_IDLE_REQUEST_THREADS_MIN, CI_MIN_IDLE_REQUEST_THREADS_MAX ); } else { cMaxActiveThreads = 2; cMinIdleThreads = 0; } } //GetWorkQueueRegParams
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::CWorkQueue, public
//
// Synopsis: Initialize queue.
//
// Arguments: [cThread] -- Number of worker threads.
// [workQueue] -- Work queue type for getting registry settings.
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
CWorkQueue::CWorkQueue( unsigned cThread, WorkQueueType workQueue ) : _cItems( 0 ), _pHead( 0 ), _pTail( 0 ), _apWorker( 0 ), // avoid allocation in construction of global
_pIdle( 0 ), _cIdle( 0 ), _fInit( FALSE ), _fAbort( FALSE ), _cWorker( 0 ), _cMaxActiveThreads(cThread), _cMinIdleThreads(0), _QueueType( workQueue ), _mtxLock( FALSE ) { Win4Assert( workQueueQuery == workQueue || workQueueRequest == workQueue || workQueueFrmwrkClient == workQueue );
// Note: don't call RefreshRegParams as long as the work queue
// is a global object -- there's no telling what security context will
// be used to load the .dll.
//RefreshParams( cThread, cThread );
} //CWorkQueue
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::~CWorkQueue, public
//
// Synopsis: Waits for queue threads to stop.
//
// Requires: An external source has killed all queue items.
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
CWorkQueue::~CWorkQueue() { Win4Assert( _cItems == 0 );
if ( _fInit ) { // If somehow we're unwinding a global object after the heap has
// been destroyed, don't do any work.
if ( g_fPerfmonCounterHackIsProcessDetached ) { // Don't free this since the heap is already gone!
_apWorker.AcquireAll();
return; }
Shutdown(); } } //~CWorkQueue
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::RefreshParams, public
//
// Synopsis: Refreshes registry params for the work queue
//
// History: 7-Nov-96 dlee Created
// 30-Dec-96 SrikantS Changed name to RefreshParams and removed
// the dependency on registry.
//
//--------------------------------------------------------------------------
#pragma warning(push)
#pragma warning(disable:4296)
void CWorkQueue::RefreshParams( ULONG cMaxActiveThread, ULONG cMinIdleThread ) { CLock lock( _mtxLock );
if ( CWorkQueue::workQueueQuery == _QueueType ) { _cMaxActiveThreads = min( CI_MAX_ACTIVE_QUERY_THREADS_MAX, max(CI_MAX_ACTIVE_QUERY_THREADS_MIN, cMaxActiveThread) ); _cMinIdleThreads = min( CI_MIN_IDLE_QUERY_THREADS_MAX, max(CI_MIN_IDLE_QUERY_THREADS_MIN, cMinIdleThread) ); } else if ( CWorkQueue::workQueueRequest == _QueueType ) { _cMaxActiveThreads = min( CI_MAX_ACTIVE_REQUEST_THREADS_MAX, max(CI_MAX_ACTIVE_REQUEST_THREADS_MIN, cMaxActiveThread) ); _cMinIdleThreads = min( CI_MIN_IDLE_REQUEST_THREADS_MAX, max(CI_MIN_IDLE_REQUEST_THREADS_MIN, cMinIdleThread) ); } else // framework client queue
{ _cMaxActiveThreads = 2; _cMinIdleThreads = 0; } } //RefreshParams
#pragma warning(pop)
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::Shutdown, public
//
// Synopsis: Shuts down the work queue
//
// History: 7-Nov-96 dlee Added header
//
//--------------------------------------------------------------------------
void CWorkQueue::Shutdown() { if (_fInit) { CLock lock( _mtxLock ); _fAbort = TRUE; for ( CWorkThread * pw = _pIdle; pw; pw = pw->Next() ) { Win4Assert(pw != pw->Next()); pw->Wakeup(); }
vqDebugOut(( DEB_ITRACE, "Work queue shutdown: woke up threads\n" )); }
_apWorker.Free(); _cWorker = 0;
vqDebugOut(( DEB_ITRACE, "Work queue shutdown: exiting\n" )); } //Shutdown
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::AddRefWorkThreads, public
//
// Synopsis: Addrefs the current set of worker threads
//
// History: 21-May-97 dlee Created
//
//--------------------------------------------------------------------------
void CWorkQueue::AddRefWorkThreads() { CLock lock( _mtxLock );
for ( ULONG x = 0; x < _cWorker; x++ ) _apWorker[x]->AddRef(); } //AddRefWorkThreads
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::ReleaseWorkThreads, public
//
// Synopsis: Releases the current set of worker threads
//
// History: 21-May-97 dlee Created
//
//--------------------------------------------------------------------------
void CWorkQueue::ReleaseWorkThreads() { CLock lock( _mtxLock );
for ( ULONG x = 0; x < _cWorker; x++ ) _apWorker[x]->Release(); } //ReleaseWorkThreads
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::Add, public
//
// Synopsis: Add a query to the queue.
//
// Arguments: [pitem] -- Query to add
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
void CWorkQueue::Add( PWorkItem * pitem ) { BOOL fTryRemove = FALSE;
{ CLock lock( _mtxLock );
if ( _fAbort ) { vqDebugOut(( DEB_ITRACE, "work queue, shutdown, so not adding item\n" )); return; }
pitem->AddRef();
//
// Insert at end of list
//
if ( _pTail ) { _pTail->Link( pitem ); _pTail = pitem; } else { Win4Assert( !_pHead );
_pHead = pitem; _pTail = pitem; }
_pTail->Link( 0 ); _cItems++;
vqDebugOut(( DEB_ITRACE, "Work queue: add 0x%x\n", pitem ));
//
// Add a new thread if there are no idle ones (or ones processing APCs)
//
if ( ( ( _cWorker == (unsigned) _cInAPC ) || ( 0 == _pIdle ) ) && ( _cWorker < _cMaxActiveThreads ) ) { TRY { lokAddThread(); } CATCH( CException, e ) { //
// Remove the item from the work queue so it gets released
// properly.
//
Remove( pitem );
//
// Now rethrow the exception, which will only happen if
// there are not worker threads at all.
//
RETHROW(); } END_CATCH;
Win4Assert( _pIdle == 0 || _pIdle->Next() != _pIdle ); }
//
// Wake up a thread if there is an idle one.
//
if ( _pIdle ) { Win4Assert( _cIdle > 0 );
// Move any thread not processing an APC to the start of the list
CWorkThread * pwPrev = 0;
for ( CWorkThread * pw = _pIdle; 0 != pw; pw = pw->Next() ) { if ( !pw->IsProcessingAPC() ) { if ( pw != _pIdle ) { Win4Assert( 0 != pwPrev );
pwPrev->Link( pw->Next() );
pw->Link( _pIdle );
_pIdle = pw; }
break; }
pwPrev = pw; }
CWorkThread * pWorker = _pIdle; _pIdle = _pIdle->Next(); _cIdle--; Win4Assert( _pIdle != pWorker ); pWorker->Link( 0 ); pWorker->Wakeup();
if ( _cIdle > _cMinIdleThreads ) fTryRemove = TRUE; } }
if ( fTryRemove ) RemoveThreads(); } //Add
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::Release, public
//
// Synopsis: Release refcount on worker thread
//
// Arguments: [pThread] -- Worker thread
//
// History: 13-Mar-96 KyleP Created
//
//--------------------------------------------------------------------------
void CWorkQueue::Release( CWorkThread * pThread ) { pThread->Release(); BOOL fTryRemove = FALSE;
if ( GetCurrentThreadId() != pThread->GetThreadId() ) { CLock lock( _mtxLock );
if ( _pIdle ) { Win4Assert( _cIdle > 0 );
if ( _cIdle > _cMinIdleThreads ) fTryRemove = TRUE; }
}
if ( fTryRemove ) RemoveThreads(); } //Release
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::Remove, private
//
// Synopsis: Remove a query from the queue.
//
// Arguments: [Worker] -- Worker thread to own the query
//
// History: 29-Dec-93 KyleP Created
//
// Notes: Remove is used by worker threads to acquire a new work
// item. In contrast, the other remove will just remove
// an item from the queue.
//
//--------------------------------------------------------------------------
void CWorkQueue::Remove( CWorkThread & Worker ) { Win4Assert( Worker.ActiveItem() == 0 );
for (;;) { //
// Look for an item
//
{ CLock lock( _mtxLock );
Worker.Reset(); //
// We may have been awoken by an APC. Be sure we're not
// still on the idle queue in this case.
//
if ( _pIdle ) { if ( _pIdle == &Worker ) { _cIdle--; _pIdle = Worker.Next(); } else { for ( CWorkThread * pw = _pIdle; pw; pw = pw->Next() ) { if (pw->Next() == &Worker) { _cIdle--; pw->Link(Worker.Next()); break; } } } }
if ( _fAbort || Worker.lokShouldAbort() ) { vqDebugOut(( DEB_ITRACE, "Work queue: abort worker thread.\n" )); break; }
if ( Count() > 0 ) { vqDebugOut(( DEB_ITRACE, "Work queue: %d pending\n", _cItems ));
Worker.lokSetActiveItem( _pHead ); _pHead = _pHead->Next();
if ( _pHead == 0 ) _pTail = 0;
_cItems--;
break; }
//
// Nothing on the queue right now. Wait for item.
//
Worker.Link( _pIdle ); _pIdle = &Worker; _cIdle++; Win4Assert(Worker.Next() != &Worker); }
Worker.Wait(); }
vqDebugOut(( DEB_ITRACE, "Work queue: remove 0x%x\n", Worker.ActiveItem() ));
//
// We're not sleeping the thread, make sure we
// process APCs.
//
SleepEx( 0, TRUE ); } //Remove
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::Remove, public
//
// Effects: Deletes all references to [pitem] from the queue. If
// pitem is a query-in-progress in a worker thread then
// we wait until that query completes before returning from
// Delete.
//
// Arguments: [pitem] -- Query to remove
//
// History: 29-Dec-93 KyleP Created
// 09-Feb-94 KyleP Speed up removal of active item
//
// Notes: The item may still be running on a worker thread when we
// return from this call.
//
//--------------------------------------------------------------------------
void CWorkQueue::Remove( PWorkItem * pitem ) { CLock lock( _mtxLock );
if ( _pHead == pitem ) { _pHead = pitem->Next();
if ( _pTail == pitem ) { Win4Assert( _pHead == 0 ); _pTail = 0; }
_cItems--;
pitem->Release();
vqDebugOut(( DEB_ITRACE, "Work queue: delete 0x%x\n", pitem )); } else { for ( PWorkItem * pCurrent = _pHead; pCurrent && pCurrent->Next() != pitem; pCurrent = pCurrent->Next() ) continue; // Null body
if ( pCurrent ) { Win4Assert( pCurrent->Next() == pitem );
if ( _pTail == pitem ) { _pTail = pCurrent; }
pCurrent->Link( pitem->Next() );
_cItems--;
pitem->Release();
vqDebugOut(( DEB_ITRACE, "Work queue: delete 0x%x\n", pitem )); } } } //Remove
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::AddThread, private
//
// Synopsis: Adds new worker thread to idle worker list.
//
// History: 12-Jan-94 KyleP Created
//
//--------------------------------------------------------------------------
void CWorkQueue::lokAddThread() { TRY { vqDebugOut(( DEB_ITRACE, "Add worker thread %d of %d\n", _cWorker+1, _apWorker.Size() ));
//
// Worker threads must be created in the system context or they
// won't have the permission to revert to system, which is needed
// for queries on remote volumes and to write to catalog files.
//
CImpersonateSystem impersonate;
CEventSem evt1; SHandle hEvt1( evt1.AcquireHandle() ); CEventSem evt2; SHandle hEvt2( evt2.AcquireHandle() );
_apWorker.Add( new CWorkThread( *this, _pIdle, hEvt1, hEvt2 ), _cWorker ); _pIdle = _apWorker[_cWorker]; _cIdle++; _cWorker++; } CATCH( CException, e ) { vqDebugOut(( DEB_ERROR, "Exception 0x%x caught creating query worker threads." " Running with %d threads.\n", e.GetErrorCode(), _cWorker ));
// Only throw an excption if there are no threads around at all.
// If there is at least 1 thread it'll eventually get to the work
// items.
if ( 0 == _cWorker ) { RETHROW(); } } END_CATCH
Win4Assert( _cWorker != 0 ); } //lokAddThread
//+-------------------------------------------------------------------------
//
// Member: CWorkQueue::RemoveThreads, private
//
// Synopsis: Removes one or more threads from idle queue.
//
// History: 28-Sep-95 KyleP Created
//
//--------------------------------------------------------------------------
const DWORD tenSeconds = 1000 * 10;
void CWorkQueue::RemoveThreads() { while ( TRUE ) { CWorkThread * pThread = 0;
//
// Check for too many idle threads under lock.
//
{ CLock lock( _mtxLock );
if ( _cIdle > _cMinIdleThreads ) { //
// Find an idle thread that has a refcount of 0 and hasn't
// done anything in 10 seconds.
//
CWorkThread * pPrev = 0;
for ( pThread = _pIdle; 0 != pThread; pThread = pThread->Next() ) { if ( !pThread->IsReferenced() ) { vqDebugOut(( DEB_ITRACE, "isidle? %d\n", pThread->IsIdleFor( tenSeconds ) ));
if ( pThread->IsIdleFor( tenSeconds ) ) break; }
pPrev = pThread; }
if ( 0 != pThread ) { for ( unsigned i = 0; i < _cWorker; i++ ) { //
// Remove first idle thread from all queues and abort it.
//
if ( _apWorker[i] == pThread ) { vqDebugOut(( DEB_ITRACE, "Deleting extra idle thread\n" ));
if ( 0 == pPrev ) _pIdle = _pIdle->Next(); else pPrev->Link( pThread->Next() );
_cIdle--;
_cWorker--; _apWorker.Acquire( i ); _apWorker.Add( _apWorker.Acquire(_cWorker), i );
pThread->lokAbort(); pThread->Wakeup(); break; } }
Win4Assert( i <= _cWorker ); } } }
//
// If we found an extra idle thread, delete it and try again, else just get out.
//
if ( pThread ) delete pThread; else break; } } //RemoveThreads
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::WorkerThread, static private
//
// Synopsis: Main loop that executes query.
//
// Arguments: [self] -- this pointer for CWorkThread object
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
DWORD WINAPI CWorkThread::WorkerThread( void * self ) { CWorkThread * pWorker = (CWorkThread *)self;
for( pWorker->_queue.Remove( *pWorker ); pWorker->ActiveItem(); pWorker->_queue.Remove( *pWorker ) ) { TRY { pWorker->ActiveItem()->DoIt( pWorker ); } CATCH( CException, e ) { vqDebugOut(( DEB_ERROR, "pWorker->DoIt() failed with error 0x%x\n", e.GetErrorCode() )); } END_CATCH
pWorker->Done(); }
//
// We should not do an ExitThread() here because there will be a deadlock
// during shutdown. The DLL_PROCESS_DETACH is called with the "LoaderLock"
// CriticalSection held by the LdrUnloadDll() during the DLL detach.
// Terminating the thread here is okay because there is no cleanup to be
// done after this.
//
vqDebugOut(( DEB_ITRACE, "WorkerThread 0x%x: exiting\n", self ));
return 0; } //WorkerThread
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::CWorkThread, public
//
// Synopsis: Creates worker thread.
//
// Arguments: [queue] -- Work queue which owns worker.
// [pNext] -- Link. Used by Work queue.
// [hEvt1] -- Event handle for first event in the worker thread
// [hEvt2] -- Event handle for second event in the worker thread
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
#pragma warning( disable : 4355 ) // this used in base initialization
CWorkThread::CWorkThread( CWorkQueue & queue, CWorkThread * pNext , SHandle & hEvt1 , SHandle & hEvt2 ) : _queue( queue ), _pNext( pNext ), _pitem( 0 ), _fAbort( FALSE ), _Thread( CWorkThread::WorkerThread, this, TRUE ), _cRef( 0 ) , _evtQueryAvailable( hEvt1.Acquire() ) , _evtDone( hEvt2.Acquire() ), _pDeferredAPCs( 0 ), _fProcessingAPC( FALSE ), _dwLastUsed( GetTickCount() ) { _Thread.Resume(); }
#pragma warning( default : 4355 )
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::~CWorkThread, public
//
// Synopsis: Waits for thread to die.
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
CWorkThread::~CWorkThread() { vqDebugOut(( DEB_ITRACE, "Worker 0x%x: WAIT for death\n", this )); Win4Assert( GetCurrentThreadId() != GetThreadId() ); _Thread.WaitForDeath(); vqDebugOut(( DEB_ITRACE, "Worker 0x%x: done waiting for death\n", this )); }
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::ProcessDeferredAPCs, private
//
// Synopsis: Processes all deferred APCs
//
// History: 17-Jul-00 dlee Created
//
//--------------------------------------------------------------------------
void CWorkThread::ProcessDeferredAPCs() { Win4Assert( !_fProcessingAPC );
while ( 0 != _pDeferredAPCs ) { //
// Pull the item out of the list and invoke it.
// DeferredAPC() is guaranteed not to throw.
//
PWorkItem * pItem = _pDeferredAPCs; _pDeferredAPCs = pItem->Next(); pItem->Link( 0 );
pItem->DeferredAPC();
// Release once per APC processed
Release(); } } //ProcessDeferredAPCs
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::Wait, public
//
// Synopsis: Waits for 'new work' event
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
void CWorkThread::Wait() { vqDebugOut(( DEB_RESULTS, "Worker 0x%x: WAIT for work\n", this ));
// Process any deferred APCs before sleeping to get more.
ProcessDeferredAPCs();
//
// There is no need to loop to look for work just because we woke
// up after processing an APC. But do process deferred APCs.
//
while ( STATUS_USER_APC == _evtQueryAvailable.Wait( INFINITE, TRUE ) ) ProcessDeferredAPCs(); } //Wait
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::Done, public
//
// Effects: Finishes one work item and sets 'done' event.
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
void CWorkThread::Done() { _dwLastUsed = GetTickCount();
CLock lock( _queue._mtxLock ); ActiveItem()->Release();
lokSetActiveItem( 0 );
vqDebugOut(( DEB_ITRACE, "Worker 0x%x: SET completion\n", this )); _evtDone.Set(); }
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::WaitForCompletion, public
//
// Synopsis: Waits for completion of current work item.
//
// History: 29-Dec-93 KyleP Created
//
//--------------------------------------------------------------------------
void CWorkThread::WaitForCompletion( PWorkItem * pitem ) { { CLock lock( _queue._mtxLock );
if ( ActiveItem() != pitem ) return;
vqDebugOut(( DEB_ITRACE, "Worker 0x%x: RESET completion\n", this )); _evtDone.Reset(); }
vqDebugOut(( DEB_ITRACE, "Worker 0x%x: WAIT for completion\n", this )); _evtDone.Wait( INFINITE, TRUE ); } //WaitForCompletion
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::DeferAPC, public
//
// Synopsis: Puts the work item on a list to be called back later when
// no APC is being processed.
//
// History: 17-Jul-00 dlee Created
//
//--------------------------------------------------------------------------
void CWorkThread::DeferAPC( PWorkItem * pItem ) { //
// Make sure the thread doesn't go away until all APCs are processed.
//
Win4Assert( _Thread.GetThreadId() == GetCurrentThreadId() );
AddRef();
//
// Add the item to the list.
// No locking is needed since only this thread can call it.
pItem->Link( _pDeferredAPCs ); _pDeferredAPCs = pItem; } //DeferAPC
//+-------------------------------------------------------------------------
//
// Member: CWorkThread::SetProcessingAPC, public
//
// Synopsis: Sets the flags that keep track of whether APC work is going
// on.
//
// Arguments: [f] -- TRUE if processing an APC or FALSE if all done.
//
// History: 7-Jan-01 dlee Created
//
//--------------------------------------------------------------------------
void CWorkThread::SetProcessingAPC( BOOL f ) { _fProcessingAPC = f;
if ( f ) _queue.IncrementAPC(); else _queue.DecrementAPC(); } //SetProcessingAPC
|