//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 2000. // // File: QQueue.cxx // // Contents: Query queue // // History: 29-Dec-93 KyleP Created // //-------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include 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