553 lines
13 KiB
C++
553 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
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|