Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

781 lines
21 KiB

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1992.
//
// File: thread.cxx
//
// Contents: Job scheduler thread code.
//
// Classes: CWorkerThread
// CWorkerThreadMgr
//
// Functions: RequestService
// WorkerThreadStart
//
// History: 25-Oct-95 MarkBl Created
//
//----------------------------------------------------------------------------
#include "..\pch\headers.hxx"
#pragma hdrstop
#include "debug.hxx"
#include "queue.hxx"
#include "task.hxx"
#include "jpmgr.hxx"
#include "globals.hxx"
#include "thread.hxx"
#include "proto.hxx"
extern "C"
HRESULT WorkerThreadStart(CWorkerThread *);
//
// Thread idle time before termination.
//
#define MAX_THREAD_IDLE_TIME (1000 * 60 * 3) // 3 minutes
//+---------------------------------------------------------------------------
//
// Function: WorkerThreadStart
//
// Synopsis: The entry point for a worker thread.
//
// Arguments: [pWrkThrd] -- Worker thread object to start.
//
// Returns: TBD
//
// Effects: Calls the StartWorking function for the provided worker
//
//----------------------------------------------------------------------------
extern "C"
HRESULT WorkerThreadStart(CWorkerThread * pWrkThrd)
{
schDebugOut((DEB_USER3,
"WorkerThreadStart pWrkThrd(0x%x)\n",
pWrkThrd));
HRESULT hr;
//
// Call the worker thread class
//
hr = pWrkThrd->StartWorking();
delete pWrkThrd;
return(hr);
}
// Class CWorkerThread
//
//+-------------------------------------------------------------------------
//
// Member: CWorkerThread::~CWorkerThread
//
// Synopsis: Destructor
//
// Arguments: N/A
//
// Returns: N/A
//
// Notes: A WorkerThread should only be deleted after its associated
// thread has been terminated. This is noted by having the
// _hThread being == NULL.
//
//--------------------------------------------------------------------------
CWorkerThread::~CWorkerThread()
{
TRACE3(CWorkerThread, ~CWorkerThread);
// a race condition at shutdown may result in this dtor being called *after*
// the thread manager has been destructed.
if (gpThreadMgr)
gpThreadMgr->SignalThreadTermination();
//
// A precondition to destroying this thread is for the thread to
// have been terminated already. If this isn't the case, we are
// in big trouble.
//
// Terminating the thread is not good, since it leaves the threads
// stack allocated in our address space. We also are not sure what
// the thread is up to. It could have resources locked. But, we
// also don't know what it will do next. We will have no record of it.
//
if (_hThread == NULL && _hWorkAvailable != NULL)
{
CloseHandle(_hWorkAvailable);
}
else
{
//
// BUGBUG : Commenting out the assertion below until
// (ServiceStatus != STOPPING) can be added.
//
#if 0
schAssert(0 && (_hThread != NULL) &&
"Destroying CWorkerThread while thread exists. Memory Leak!");
#endif // 0
}
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThread::AssignTask
//
// Synopsis: Assigns the task passed to this worker. A NULL task signals
// thread termination.
//
// Arguments: [pTask] -- Task to be serviced.
//
// Returns: S_OK
// E_FAIL -- Task already assigned.
// TBD
//
// Notes: None.
//
//----------------------------------------------------------------------------
HRESULT
CWorkerThread::AssignTask(CTask * pTask)
{
schDebugOut((DEB_USER3,
"CWorkerThread::AssignTask(0x%x) pTask(0x%x)\n",
this,
pTask));
HRESULT hr;
//
// A must not already be assigned.
//
if (_pTask != NULL)
{
return(E_FAIL);
}
_pTask = pTask;
if (_pTask != NULL)
{
_pTask->AddRef();
}
//
// Signal the thread to process the task.
//
if (!SetEvent(_hWorkAvailable))
{
if (_pTask != NULL)
{
_pTask->Release();
_pTask = NULL;
}
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
return(hr);
}
return(S_OK);
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThread::Initialize
//
// Synopsis: Performs initialization unable to be performed in the
// constructor.
//
// Arguments: None.
//
// Returns: S_OK
// TBD
//
// Effects: None.
//
//----------------------------------------------------------------------------
HRESULT
CWorkerThread::Initialize(void)
{
TRACE3(CWorkerThread, Initialize);
HRESULT hr = S_OK;
//
// Create the event used to signal the thread to start working.
//
_hWorkAvailable = CreateEvent(NULL, FALSE, FALSE, NULL);
if (_hWorkAvailable == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
return(hr);
}
ULONG ThreadID;
_hThread = CreateThread(NULL,
WORKER_STACK_SIZE,
(LPTHREAD_START_ROUTINE)WorkerThreadStart,
this,
0,
&ThreadID);
if (_hThread != NULL)
{
if (!SetThreadPriority(_hThread, THREAD_PRIORITY_LOWEST))
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
}
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
}
if (SUCCEEDED(hr))
{
gpThreadMgr->SignalThreadCreation();
}
return(hr);
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThread::StartWorking
//
// Synopsis: The almost endless loop for a worker thread
//
// Arguments: None.
//
// Returns: TBD
//
// Notes: This routine will sit in a loop, and wait for tasks to
// be assigned. When a task is assigned, it will call the
// PerformTask method of the task. If the assigned task
// is NULL, then the thread will kill itself.
//
//----------------------------------------------------------------------------
HRESULT
CWorkerThread::StartWorking(void)
{
TRACE3(CWorkerThread, StartWorking);
HRESULT hr = E_FAIL;
BOOL fTerminationSignalled = FALSE;
while (1)
{
//
// Wait on the work available semaphore for the signal that a task
// has been assigned.
//
DWORD dwRet = WaitForSingleObject(_hWorkAvailable,
MAX_THREAD_IDLE_TIME);
if (dwRet == WAIT_FAILED)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
break;
}
else if (dwRet == WAIT_TIMEOUT)
{
//
// This thread has timed out - see if it can be terminated.
// If this thread object exists in the global free thread
// pool, it can be terminated. If it doesn't exist in the pool,
// it means this thread is in use, therefore re-enter the wait.
//
// More detail on the above: It's possible another thread
// (thread A) can take this thread (thread B) for service just
// after thread B's wait has timed out. In absence of the thread
// pool check, thread B would exit out from under thread A.
//
CWorkerThread * pWrkThrd = gpThreadMgr->RemoveThread(_hThread);
if (pWrkThrd == NULL)
{
//
// This object doesn't exist in the pool. Assume another
// thread has taken this thread for service, re-enter the
// wait.
//
continue;
}
else
{
//
// This thread has expired.
//
// NB: DO NOT delete the thread object!
//
schAssert(pWrkThrd == this);
break;
}
}
//
// A NULL task signals thread termination.
//
if (_pTask == NULL)
{
fTerminationSignalled = TRUE;
break;
}
//
// Perform the task.
//
_pTask->PerformTask();
_pTask->UnServiced();
//
// Release the task.
//
schDebugOut((DEB_USER3, "CWorkerThread::StartWorking(0x%x) "
"Completed and Releasing task(0x%x)\n",
this,
_pTask));
_pTask->Release();
_pTask = NULL;
//
// Return this thread to the global pool, if the service is not
// in the process of stopping. If the service is stopping, this
// thread must exit.
//
if (IsServiceStopping())
{
fTerminationSignalled = TRUE;
break;
}
else
{
gpThreadMgr->AddThread(this);
}
}
//
// Scavenger duty. Perform global job processor pool housekeeping prior
// to thread termination.
//
// Do this only if the thread timed out; not when the thread is
// instructed to terminate.
//
if (!fTerminationSignalled)
{
gpJobProcessorMgr->GarbageCollection();
}
//
// By closing the handle, we allow the system to remove all remaining
// traces of this thread.
//
BOOL fTmp = CloseHandle(_hThread);
schAssert(fTmp && "Thread handle close failed - possible memory leak");
_hThread = NULL;
return(hr);
}
// Class CWorkerThreadMgr
//
//+---------------------------------------------------------------------------
//
// Member: CWorkerThreadMgr::~CWorkerThreadMgr
//
// Synopsis: Destructor.
//
// Arguments: N/A
//
// Returns: None.
//
// Notes: Memory leaks can occur with this destructor. This destructor
// must only be called with process termination.
//
//----------------------------------------------------------------------------
CWorkerThreadMgr::~CWorkerThreadMgr()
{
TRACE3(CWorkerThreadMgr, ~CWorkerThreadMgr);
//
// This destructor must only be called with process termination.
//
// If the global thread count is non-zero, there are threads
// remaining. In this case, DO NOT delete the critical section or
// close the event handle! An active thread could still access it.
//
// This will be a memory leak on process termination if the count is
// non-zero, but the leak is far better than an a/v.
//
if (!_cThreadCount)
{
DeleteCriticalSection(&_csThreadMgrCritSection);
CloseHandle(_hThreadTerminationEvent);
}
else
{
schDebugOut((DEB_FORCE,
"CWorkerThreadMgr dtor(0x%x) : Unavoidable memory leak. " \
"Leaking one or more CWorkerThread objects since worker " \
"thread(s) are still active.\n",
this));
}
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThreadMgr::AddThread
//
// Synopsis: Adds the worker thread indicated to the pool.
//
// Arguments: [pWrkThrd] -- Worker thread to add.
//
// Returns: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
void
CWorkerThreadMgr::AddThread(CWorkerThread * pWrkThrd)
{
schDebugOut((DEB_USER3,
"CWorkerThreadMgr::AddThread(0x%x) pWrkThrd(0x%x)\n",
this,
pWrkThrd));
//
// If the service is stopping, instruct the thread to terminate; else,
// add it to the pool. Note, this is safe since only the threads
// themselves perform AddThread - the thread is free.
//
if (IsServiceStopping())
{
pWrkThrd->AssignTask(NULL);
return;
}
EnterCriticalSection(&_csThreadMgrCritSection);
_WorkerThreadQueue.AddElement(pWrkThrd);
LeaveCriticalSection(&_csThreadMgrCritSection);
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThreadMgr::Initialize
//
// Synopsis: Creates the private data member, _hThreadTerminationEvent.
//
// Arguments: None.
//
// Returns: TRUE -- Creation succeeded;
// FALSE -- otherwise.
//
// Notes: None.
//
//----------------------------------------------------------------------------
BOOL
CWorkerThreadMgr::Initialize(void)
{
if ((_hThreadTerminationEvent = CreateEvent(NULL,
TRUE,
FALSE,
NULL)) == NULL)
{
schDebugOut((DEB_ERROR,
"CWorkerThreadMgr::Initialize(0x%x) CreateEvent failed, " \
"status = 0x%lx\n",
this,
GetLastError()));
return(FALSE);
}
return(TRUE);
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThreadMgr::Shutdown
//
// Synopsis: This member is called once, no wait, early on in the schedule
// service's shutdown sequence. It relinquishes all free threads.
// Done so by signalling the threads in the pool to terminate.
//
// This member is called a second time, with the wait option, to
// ensure any busy (non-free) worker threads have terminated
// also.
//
// Arguments: [fWait] -- Flag instructing this method to wait indefinitely
// until all worker threads have terminated.
// ** Use only in this case of service stop **
//
// Returns: TRUE -- All worker threads terminated.
// FALSE -- One ore more worker threads still active.
//
// Notes: It must not be possible for worker threads to enter the
// thread pool critical section in their termination code paths.
// Otherwise, a nested, blocking section will result.
//
//----------------------------------------------------------------------------
BOOL
CWorkerThreadMgr::Shutdown(BOOL fWait)
{
#define THRDMGR_INITIAL_WAIT_TIME 2000 // 2 seconds.
EnterCriticalSection(&_csThreadMgrCritSection);
CWorkerThread * pWrkThrd;
while ((pWrkThrd = _WorkerThreadQueue.RemoveElement()) != NULL)
{
pWrkThrd->AssignTask(NULL); // NULL task signals termination.
}
LeaveCriticalSection(&_csThreadMgrCritSection);
//
// Optionally wait for outstanding worker threads to terminate before
// returning. This is only to be done during service shutdown. All worker
// threads have logic to terminate with service shutdown.
//
// To actually have to wait is a very rare. Only occurring in a rare
// case, or if this machine is under *extreme* loads, or the absolutely
// unexpected occurs. The rare case mentioned is a small window where
// a job processor object is spun up immediately prior to the service
// shutdown sequence and its initialization phase coincides with
// CJobProcessor::Shutdown().
//
if (fWait)
{
//
// On destruction, each thread decrements the global thread count
// and sets the thread termination event.
//
DWORD dwWaitTime = THRDMGR_INITIAL_WAIT_TIME;
DWORD dwRet;
while (_cThreadCount)
{
//
// Wait initially THRDMGR_INITIAL_WAIT_TIME amount of time.
// If this wait times-out, re-issue a Shutdown of the global
// processor object then wait infinitely.
//
if ((dwRet = WaitForSingleObject(_hThreadTerminationEvent,
dwWaitTime)) == WAIT_OBJECT_0)
{
ResetEvent(_hThreadTerminationEvent);
}
else if (dwRet == WAIT_TIMEOUT)
{
//
// Address the case where the job processor was spun up
// inadvertently. This will shut it down.
//
gpJobProcessorMgr->Shutdown();
dwWaitTime = INFINITE;
}
else
{
//
// This return code will notify the service cleanup code
// to not free up resources associated with the worker
// threads. Otherwise, they may fault.
//
return(FALSE);
}
}
}
return(TRUE);
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThreadMgr::RemoveThread
//
// Synopsis: Remove & return a thread from the pool.
//
// Arguments: None.
//
// Returns: CWorkerThread * -- Returned thread.
// NULL -- Pool empty.
//
// Notes: None.
//
//----------------------------------------------------------------------------
CWorkerThread *
CWorkerThreadMgr::RemoveThread(void)
{
TRACE3(CWorkerThread, RemoveThread);
CWorkerThread * pWrkThrd = NULL;
EnterCriticalSection(&_csThreadMgrCritSection);
pWrkThrd = _WorkerThreadQueue.RemoveElement();
LeaveCriticalSection(&_csThreadMgrCritSection);
return(pWrkThrd);
}
//+---------------------------------------------------------------------------
//
// Member: CWorkerThreadMgr::RemoveThread
//
// Synopsis: Remove & return the thread from the pool with the associated
// handle.
//
// Arguments: [hThread] -- Target thread handle.
//
// Returns: CWorkerThread * -- Found it.
// NULL -- Worker thread not found.
//
// Notes: None.
//
//----------------------------------------------------------------------------
CWorkerThread *
CWorkerThreadMgr::RemoveThread(HANDLE hThread)
{
schDebugOut((DEB_USER3,
"CWorkerThreadMgr::RemoveThread(0x%x) hThread(0x%x)\n",
this,
hThread));
CWorkerThread * pWrkThrd;
EnterCriticalSection(&_csThreadMgrCritSection);
pWrkThrd = _WorkerThreadQueue.GetFirstElement();
while (pWrkThrd != NULL)
{
if (pWrkThrd->GetHandle() == hThread)
{
_WorkerThreadQueue.RemoveElement(pWrkThrd);
break;
}
pWrkThrd = pWrkThrd->Next();
}
LeaveCriticalSection(&_csThreadMgrCritSection);
return(pWrkThrd);
}
//+---------------------------------------------------------------------------
//
// Function: RequestService
//
// Synopsis: Request a free worker thread from the global thread pool to
// service the task indicated. If no free threads exist in the
// pool, create a new one.
//
// Arguments: [pTask] -- Task to undergo service.
//
// Returns: S_OK
// E_OUTOFMEMORY
// TBD
//
// Notes: None.
//
//----------------------------------------------------------------------------
HRESULT
RequestService(CTask * pTask)
{
schDebugOut((DEB_USER3, "RequestService pTask(0x%x)\n", pTask));
HRESULT hr = S_OK;
//
// Take no requests if the service is stopping.
//
if (IsServiceStopping())
{
schDebugOut((DEB_ERROR,
"RequestService pTask(0x%x) Service stopping - request " \
"refused\n",
pTask));
return(E_FAIL);
}
//
// Obtain a free thread from the global thread pool.
//
CWorkerThread * pWrkThrd = gpThreadMgr->RemoveThread();
if (pWrkThrd == NULL)
{
//
// Create a new worker thread object if none were available.
//
pWrkThrd = new CWorkerThread;
if (pWrkThrd == NULL)
{
hr = E_OUTOFMEMORY;
CHECK_HRESULT(hr);
return(hr);
}
hr = pWrkThrd->Initialize();
if (FAILED(hr))
{
delete pWrkThrd;
return(hr);
}
}
hr = pWrkThrd->AssignTask(pTask);
if (FAILED(hr))
{
delete pWrkThrd;
}
return(hr);
}