NT4/private/windows/spooler/spllib/threadm.cxx
2020-09-30 17:12:29 +02:00

552 lines
13 KiB
C++

/*++
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
/********************************************************************
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 = CreateThread( 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",
(DWORD)pJob ));
//
// Call through virtual function to process the
// user's job.
//
vThreadMJobProcess( pJob );
DBGMSG( DBG_THREADM,
( "ThreadM.dwThreadProc: %x processing done\n",
(DWORD)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;
}