/*++ Copyright (c) 1994 Microsoft Corporation All rights reserved Module Name: ThreadM.c Abstract: Generic thread manager for spooler. Author: Albert Ting (AlbertT) 13-Feb-1994 Environment: User Mode -Win32 Revision History: Albert Ting (AlbertT) 28-May-1994 C++ized --*/ #include "spllibp.hxx" #pragma hdrstop pfCreateThread gpfSafeCreateThread; /******************************************************************** Public interfaces. ********************************************************************/ TThreadM:: TThreadM( UINT uMaxThreads, UINT uIdleLife, MCritSec* pCritSec OPTIONAL ) : _uMaxThreads(uMaxThreads), _uIdleLife(uIdleLife), _uActiveThreads(0), _uRunNowThreads(0), _iIdleThreads(0) /*++ Routine Description: Construct a Thread Manager object. Arguments: uMaxThreads - Upper limit of threads that will be created. uIdleLife - Maximum life once a thread goes idle (in ms). pCritSec - Use this crit sec for synchronization (if not specified, a private one will be created). Return Value: Notes: _hTrigger is our validity variable. When this value is NULL, instantiation failed. If it's non-NULL, the entire object is valid. --*/ { _hTrigger = CreateEvent( NULL, FALSE, FALSE, NULL ); if( !_hTrigger ){ return; } // // If no critical section, create our own. // if (!pCritSec) { _pCritSec = new MCritSec(); if( !VALID_PTR( _pCritSec )){ // // _hTrigger is our valid variable. If we fail to create // the critical section, prepare to return failure. // CloseHandle( _hTrigger ); _hTrigger = NULL; return; } _State |= kPrivateCritSec; } else { _pCritSec = pCritSec; } } VOID TThreadM:: vDelete( VOID ) /*++ Routine Description: Indicates that the object is pending deletion. Any object that inherits from vDelete should _not_ call the destructor directly, since there may be pending jobs. Instead, they should call TThreadM::vDelete(). Arguments: Return Value: --*/ { BOOL bDestroy = FALSE; { TCritSecLock CSL( *_pCritSec ); // // Mark as wanting to be destroyed. // _State |= kDestroyReq; if( !_iIdleThreads && !_uActiveThreads ){ bDestroy = TRUE; } } if( bDestroy ){ // // Delete the object. Note that absolutely no object fields // can be accessed after this, since the object is returned // to free store. // delete this; } } BOOL TThreadM:: bJobAdded( BOOL bRunNow ) /*++ Routine Description: Notify the thread manager that a new job has been added. This job will be processed fifo. Arguments: bRunNow - Ignore the thread limits and run the job now. Return Value: TRUE - Job successfully added. FALSE - Job could not be added. --*/ { DWORD dwThreadId; HANDLE hThread; BOOL rc = TRUE; TCritSecLock CSL( *_pCritSec ); if( _State.bBit( kDestroyReq )){ DBGMSG( DBG_THREADM | DBG_ERROR, ( "ThreadM.bJobAdded: add failed since DESTROY requested.\n" )); SetLastError( ERROR_INVALID_PARAMETER ); rc = FALSE; } else { // // We either: give it to an idle thread, create a new thread, // or leave it in the queue. // if( _iIdleThreads > 0 ){ // // There are some idle threads--trigger the event and it // will be picked up. // --_iIdleThreads; DBGMSG( DBG_THREADM, ( "ThreadM.bJobAdded: Trigger: --iIdle %d, uActive %d\n", _iIdleThreads, _uActiveThreads )); // // If we set the event, then the worker thread that receives // this event should _not_ decrement _iIdleThreads, since // we already did this. // SetEvent( _hTrigger ); } else if( _uActiveThreads < _uMaxThreads || bRunNow ){ // // No idle threads, but we can create a new one since we // haven't reached the limit, or the bRunNow flag is set. // hThread = gpfSafeCreateThread( NULL, 0, xdwThreadProc, this, 0, &dwThreadId ); if( hThread ){ CloseHandle( hThread ); // // We have successfully created a thread; up the // count. // ++_uActiveThreads; // // We have less active threads than the max; create a new one. // DBGMSG( DBG_THREADM, ( "ThreadM.bJobAdded: ct: iIdle %d, ++uActive %d\n", _iIdleThreads, _uActiveThreads )); } else { rc = FALSE; DBGMSG( DBG_THREADM | DBG_WARN, ( "ThreadM.bJobAdded: unable to ct %d\n", GetLastError( ))); } } else { // // No idle threads, and we are already at the max so we // can't create new threads. Dec iIdleThreads anyway // (may go negative, but that's ok). // // iIdleThreads represents the number of threads that // are currently not processing jobs. If the number is // negative, this indicates that even if a thread suddenly // completes a job and would go idle, there is a queued // job that would immediately grab it, so the thread really // didn't go into an idle state. // // The negative number indicates the number of outstanding // jobs that are queued (e.g., -5 indicate 5 jobs queued). // // There is always an increment of iIdleThreads when a // job is compeleted. // --_iIdleThreads; // // No threads idle, and at max threads. // DBGMSG( DBG_THREADM, ( "ThreadM.bJobAdded: wait: --iIdle %d, uActive %d\n", _iIdleThreads, _uActiveThreads )); } // // If we succeeded and bRunNow is set, this indicates that // we were able to start a special thread, so we need to adjust // the maximum number of threads. When a this special thread // job completes, we will decrement it. // if( bRunNow && rc ){ ++_uMaxThreads; ++_uRunNowThreads; } } return rc; } /******************************************************************** Private routines. ********************************************************************/ TThreadM:: ~TThreadM( VOID ) /*++ Routine Description: Destroy the thread manager object. This is private; to request that the thread manager quit, call vDelete(). Arguments: Return Value: --*/ { SPLASSERT( _State.bBit( kDestroyReq )); if( _State.bBit( kPrivateCritSec )){ SPLASSERT( _pCritSec->bOutside( )); delete _pCritSec; } if( _hTrigger ) CloseHandle( _hTrigger ); vThreadMDeleteComplete(); } VOID TThreadM:: vThreadMDeleteComplete( VOID ) /*++ Routine Description: Stub routine for objects that don't need deletion complete notification. Arguments: Return Value: --*/ { } DWORD TThreadM:: xdwThreadProc( LPVOID pVoid ) /*++ Routine Description: Worker thread routine that calls the client to process the jobs. Arguments: pVoid - pTMStateVar Return Value: Ignored. --*/ { TThreadM* pThreadM = (TThreadM*)pVoid; return pThreadM->dwThreadProc(); } DWORD TThreadM:: dwThreadProc( VOID ) { BOOL bDestroy = FALSE; { TCritSecLock CSL( *_pCritSec ); DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: ct: iIdle %d, uActive %d\n", _iIdleThreads, _uActiveThreads)); PJOB pJob = pThreadMJobNext(); while( TRUE ){ for( ; pJob; pJob=pThreadMJobNext( )){ // // If bRunNow count is non-zero, this indicates that we just // picked up a RunNow job. As soon as it completes, we // can decrement the count. // BOOL bRunNowCompleted = _uRunNowThreads > 0; { TCritSecUnlock CSU( *_pCritSec ); // // Call back to client to process the job. // DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: %x processing\n", (ULONG_PTR)pJob )); // // Call through virtual function to process the // user's job. // vThreadMJobProcess( pJob ); DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: %x processing done\n", (ULONG_PTR)pJob )); } // // If a RunNow job has been completed, then decrement both // counts. uMaxThreads was increased by one when the job was // accepted, so now it must be lowered. // if( bRunNowCompleted ){ --_uMaxThreads; --_uRunNowThreads; } ++_iIdleThreads; DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: ++iIdle %d, uActive %d\n", _iIdleThreads, _uActiveThreads )); } DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: Sleep: iIdle %d, uActive %d\n", _iIdleThreads, _uActiveThreads )); { TCritSecUnlock CSU( *_pCritSec ); // // Done, now relax and go idle for a bit. We don't // care whether we timeout or get triggered; in either // case we check for another job. // WaitForSingleObject( _hTrigger, _uIdleLife ); } // // We must check here instead of relying on the return value // of WaitForSingleObject since someone may see iIdleThreads!=0 // and set the trigger, but we timeout before it gets set. // pJob = pThreadMJobNext(); if( pJob ){ DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: Woke and found job: iIdle %d, uActive %d\n", _iIdleThreads, _uActiveThreads )); } else { // // No jobs found; break. Be sure to reset the hTrigger, since // there are no waiting jobs, and the main thread might // have set it in the following case: // // MainThread: WorkerThread: // Sleeping // Awoke, not yet in CS. // GotJob // SetEvent // --iIdleThreads // Enter CS, found job, process it. // // In this case, the event is set, but there is no thread // to pick it up. // ResetEvent( _hTrigger ); break; } } // // Decrement ActiveThreads. This was incremented when the thread // was successfully created, and should be decremented when the thread // is about to exit. // --_uActiveThreads; // // The thread enters an idle state right before it goes to sleep. // // When a job is added, the idle count is decremented by the main // thread, so the worker thread doesn't decrement it (avoids sync // problems). If the worker thread timed out and there were no jobs, // then we need to decrement the matching initial increment here. // --_iIdleThreads; if( _State.bBit( kDestroyReq ) && !_uActiveThreads && !_iIdleThreads ){ // // Destroy requested. // bDestroy = TRUE; } DBGMSG( DBG_THREADM, ( "ThreadM.dwThreadProc: dt: --iIdle %d, --uActive %d\n", _iIdleThreads, _uActiveThreads)); } if( bDestroy ){ delete this; } return 0; }