|
|
/*++
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; }
|