mirror of https://github.com/lianthony/NT4.0
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.
1932 lines
52 KiB
1932 lines
52 KiB
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
THREADS.C
|
|
|
|
Abstract:
|
|
|
|
This file contains thread management routines. Callers can register
|
|
waitable object handles and the functions to be called when a handle
|
|
becomes signaled. Also, work items can be queued, and operated on when
|
|
threads become free.
|
|
|
|
The following functions reside in this file:
|
|
|
|
SvcAddWorkItem
|
|
SvcRemoveWorkItem
|
|
SvcObjectWatcher
|
|
SvcInitThreadManager
|
|
ThreadManagerCleanup
|
|
SvcShutdownObjectWatcher
|
|
SvcRemoveWaitEntry
|
|
SvcAddWaitEntry
|
|
SvcAdjustTimeouts
|
|
SvcQueueWorkItem
|
|
TmRemoveJobFromWorkQueue
|
|
TmWorkerThread
|
|
TmGetWaitTime
|
|
TmCheckStartWorkerThread
|
|
TmGetThreadMaxFromReg
|
|
|
|
|
|
Author:
|
|
|
|
Dan Lafferty (danl) 10-Jan-1994
|
|
|
|
Environment:
|
|
|
|
User Mode - Win32
|
|
|
|
Notes:
|
|
|
|
There are two levels of thread management supported by these functions.
|
|
|
|
1) Waitable Object Registration. (SvcAddWorkItem)
|
|
|
|
The caller can pass in a waitable handle, and a function pointer
|
|
with some context that is to be called when the handle becomes
|
|
signaled. Whenever the handle is signaled, a work item gets queued.
|
|
Then either an existing thread will handle the item, or a new thread
|
|
is created to handle the work item. After the work item is
|
|
queued, the waitable object is removed the list. If this handle
|
|
should be waited on again, it should be added with another call
|
|
to SvcAddWorkItem.
|
|
|
|
2) Work Item Queuing (SvcAddWorkItem)
|
|
|
|
If the caller passes in NULL for the waitable handle, then a
|
|
work item is placed directly in the work queue. Then either a
|
|
new thread is created to handle the item, or an existing
|
|
thread will handle the item.
|
|
|
|
|
|
NOTES: Worker threads will age down to 0 if none are needed.
|
|
Max number of threads should be configured in the registry under
|
|
System\CurrentControlSet\Control.
|
|
|
|
|
|
Revision History:
|
|
|
|
01-Nov-1995 anirudhs
|
|
SvcAddWorkItem: Fixed race condition wherein a DLL could add a
|
|
work item and another thread would execute the work item, decrement
|
|
the DLL ref count and unload the DLL before its ref count was
|
|
incremented.
|
|
|
|
18-Jul-1994 danl
|
|
TmWorkerThread: Fixed Access Violation problem which will occur
|
|
if pWorkItem is NULL and we go on to see if the DLL should be
|
|
free'd. Now we check to see if pWorkItem is NULL first.
|
|
|
|
10-Jan-1994 danl
|
|
Created
|
|
|
|
--*/
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#include <svcs.h> // SVCS_ENTRY_POINT, SVCS_GLOBAL_DATA
|
|
#include <scdebug.h>
|
|
#include <svcslib.h> // SvcDecrementDllRefAndFree
|
|
|
|
//---------------------------------
|
|
// Definitions and Data Structures
|
|
//---------------------------------
|
|
|
|
//
|
|
// Replaces an existing entry in a linked list
|
|
// with an new entry.
|
|
//
|
|
#define ReplaceEntryInList(OldEntry, NewEntry) { \
|
|
(NewEntry)->Blink = (OldEntry)->Blink; \
|
|
(NewEntry)->Flink = (OldEntry)->Flink; \
|
|
((NewEntry)->Blink)->Flink = NewEntry; \
|
|
((NewEntry)->Flink)->Blink = NewEntry; \
|
|
}
|
|
|
|
//
|
|
// Operation Definitions.
|
|
// These are used to tell the watcher thread what kind of operation
|
|
// is in the CHANGE_ENTRY packet.
|
|
// The entry is either added to the queue, deleted from the queue, or
|
|
// operated upon immediately by the Watcher Thread.
|
|
//
|
|
#define ADD_WAIT_ENTRY 0x00000001
|
|
#define DELETE_WAIT_ENTRY 0x00000002
|
|
#define OPERATE_IMMEDIATELY 0x00000003
|
|
|
|
#define THREAD_WAIT_TIMEOUT 60000
|
|
#define DEFAULT_MAX_THREADS 20
|
|
#define REG_MAX_THREADS L"system\\CurrentControlSet\\Control"
|
|
|
|
#define DLL_REF_SIGNATURE (0x4c4c4468) // hDLL
|
|
|
|
//
|
|
// Special indicies in the SvcWaitArray:
|
|
//
|
|
#define WAKEUP_WATCHER 0 // Set this event to wakeup the watcher
|
|
|
|
typedef struct _WORK_ITEM {
|
|
LIST_ENTRY List;
|
|
PSVCS_WORKER_CALLBACK CallbackFunction;
|
|
PVOID Parameter;
|
|
DWORD WaitStatus;
|
|
HANDLE hDllReference;
|
|
} WORK_ITEM, *PWORK_ITEM, *LPWORK_ITEM;
|
|
|
|
//
|
|
// In the WaitEntry structure, the WORK_ITEM element MUST be the
|
|
// first element so that pWaitEntry is the same pointer as
|
|
// pWaitEntry->Job. This way we can free either the job, or
|
|
// the wait entry.
|
|
//
|
|
typedef struct _WAIT_ENTRY {
|
|
WORK_ITEM Job; // This MUST be the 1st element.
|
|
HANDLE ObjectHandle;
|
|
DWORD Timeout;
|
|
} WAIT_ENTRY, *PWAIT_ENTRY, *LPWAIT_ENTRY;
|
|
|
|
typedef struct _CHANGE_ENTRY {
|
|
LPWAIT_ENTRY EntryToChange; // list entry to operate on.
|
|
DWORD Operation; // The operation to be performed.
|
|
DWORD ReturnStatus; // status of operation when done.
|
|
} CHANGE_ENTRY, *PCHANGE_ENTRY, *LPCHANGE_ENTRY;
|
|
|
|
typedef struct _DLL_REFERENCE {
|
|
LIST_ENTRY List;
|
|
DWORD Signature;
|
|
DWORD Count;
|
|
HINSTANCE hDll;
|
|
} DLL_REFERENCE, *PDLL_REFERENCE;
|
|
|
|
//
|
|
// This lock blocks further Adds or Deletes.
|
|
//
|
|
HANDLE SvcAddDelLock = NULL;
|
|
#define BLOCK_ADD_DELETE() WaitForSingleObject(SvcAddDelLock, INFINITE);
|
|
#define UNBLOCK_ADD_DELETE() SetEvent(SvcAddDelLock);
|
|
|
|
//
|
|
// This spin lock guards access to the WorkItem queue.
|
|
//
|
|
HANDLE SvcWorkerLock = NULL;
|
|
#define LOCK_WORK_QUEUE() WaitForSingleObject(SvcWorkerLock, INFINITE);
|
|
#define UNLOCK_WORK_QUEUE() SetEvent(SvcWorkerLock);
|
|
|
|
|
|
//----------
|
|
// GLOBALS
|
|
//----------
|
|
|
|
//
|
|
// This is the location of the array of object handles that can be
|
|
// waited on. Only the Watcher thread can modify these arrays.
|
|
//
|
|
|
|
LPHANDLE SvcObjectArray = NULL;
|
|
LPWAIT_ENTRY *SvcWaitArray = NULL;
|
|
|
|
//
|
|
// The number of objects actively in the array.
|
|
// Only the Watcher thread can modify this count.
|
|
//
|
|
DWORD SvcObjectCount=0;
|
|
|
|
//
|
|
// The maximum number of objects allowed in the array. If the
|
|
// array gets this large, a new array with a new MaxNumObjects
|
|
// can be allocated.
|
|
//
|
|
DWORD SvcMaxNumObjects = 0;
|
|
|
|
//
|
|
// This is set when the service controller is shutting down. It
|
|
// causes the thread manager to be exited so this thread can return and
|
|
// proceed with shutting down the service controller.
|
|
//
|
|
BOOL WatcherShutdown = FALSE;
|
|
//
|
|
// Shortest timeout.
|
|
// Only the Watcher thread can modify the timeouts.
|
|
//
|
|
DWORD SvcShortestTimeout = INFINITE;
|
|
|
|
//
|
|
// This structure is used to send an Add or Delete work item to the
|
|
// Watcher thread. The BLOCK_ADD_DELETE lock allows only one add
|
|
// or delete to take place at a time.
|
|
//
|
|
CHANGE_ENTRY SvcChangeEntry={0};
|
|
|
|
//
|
|
// EventHandle that allows the Watcher thread to tell the
|
|
// function that submitted a SvcChangeEntry work item to be
|
|
// notified that the operation is complete.
|
|
//
|
|
HANDLE SvcChangeComplete=NULL;
|
|
|
|
//
|
|
// Saves the reason for why initialialization failed.
|
|
//
|
|
DWORD SvcInitStatus=NO_ERROR;
|
|
|
|
//
|
|
// Thread Management Globals.
|
|
// Access to these is protected by LOCK_WORK_QUEUE.
|
|
//
|
|
DWORD TmMaxNumThreads=0;
|
|
DWORD TmNumThreads=0;
|
|
DWORD TmNumWaitingThreads=0;
|
|
HANDLE TmWorkerSemaphore=NULL;
|
|
LIST_ENTRY TmWorkerQueueHead = {0}; // Linked list for WorkerQueue
|
|
|
|
//
|
|
// Beginning of linked list of DLL Reference structures.
|
|
//
|
|
LIST_ENTRY SvcDllRefListHead = {0};
|
|
|
|
CRITICAL_SECTION SvcDllRefCountCritSec = {0};
|
|
|
|
//
|
|
// Local Functions
|
|
//
|
|
|
|
|
|
VOID
|
|
ThreadManagerCleanup(
|
|
VOID
|
|
);
|
|
|
|
DWORD
|
|
SvcRemoveWaitEntry(
|
|
LPWAIT_ENTRY pEntryToDelete
|
|
);
|
|
|
|
DWORD
|
|
SvcAddWaitEntry(
|
|
LPWAIT_ENTRY pEntryToAdd
|
|
);
|
|
|
|
|
|
DWORD
|
|
SvcQueueWorkItem(
|
|
IN PWORK_ITEM pWorkItem
|
|
);
|
|
|
|
DWORD
|
|
SvcAdjustTimeouts(
|
|
LPWAIT_ENTRY *pJobList,
|
|
DWORD ElapsedTime
|
|
);
|
|
|
|
BOOL
|
|
TmRemoveJobFromWorkQueue(
|
|
IN PWORK_ITEM pWorkItem
|
|
);
|
|
|
|
DWORD
|
|
TmGetThreadMaxFromReg(
|
|
LPDWORD pMaxNumThreads
|
|
);
|
|
|
|
VOID
|
|
TmCheckStartWorkerThread(
|
|
VOID
|
|
);
|
|
|
|
BOOL
|
|
SvcIncrementDllRef(
|
|
HANDLE hDllReference
|
|
);
|
|
|
|
|
|
HANDLE
|
|
SvcAddWorkItem (
|
|
IN HANDLE hWaitableObject,
|
|
IN PSVCS_WORKER_CALLBACK pCallbackFunction,
|
|
IN PVOID pContext,
|
|
IN DWORD dwFlags,
|
|
IN DWORD dwTimeout,
|
|
IN HANDLE hDllReference
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function queues a work item that is to be operated upon as soon
|
|
as possible, or after a waitable handle has become signaled, or when
|
|
a timeout occurs.
|
|
|
|
NOTE: The callback function should not terminate the thread created
|
|
for the work item. Instead, it should simply return, and give it back
|
|
to the thread manager.
|
|
|
|
ALLOCATIONS: This function allocates memory for the WorkItem structure.
|
|
This memory is released when (1) the work item is removed, or (2) the
|
|
job is complete (The worker thread releases it).
|
|
|
|
Arguments:
|
|
|
|
hWaitableObject - This is a handle for a waitable object. If this is
|
|
NULL then work item is dropped directly into the work queue for
|
|
processing as soon as possible. The dwTimeout parameter is ignored
|
|
in this case.
|
|
|
|
pCallbackFunction - This is a pointer to a function that is to be called
|
|
when the waitable handle becomes signaled.
|
|
|
|
pContext - This is a pointer to some context information that is to
|
|
be passed to the pWorkerThread function. The caller is responsible
|
|
for allocating and freeing any memory associated with the context.
|
|
|
|
dwFlags - Currently the following flags are supported:
|
|
SVC_QUEUE_WORK_ITEM - This indicates that the work item should be
|
|
placed in the work queue to be operated upon when a worker
|
|
thread becomes available.
|
|
SVC_IMMEDIATE_CALLBACK - This indicates that the Watcher Thread
|
|
should make the callback and return the response prior to
|
|
returning from this call to SvcAddWorkItem. This allows
|
|
asynchronous I/O events to be setup in the context of the
|
|
Watcher Thread, which never goes away. If the service had
|
|
a worker thread setup the async I/O operation, the operation
|
|
would become signalled as soon as the worker thread was
|
|
terminated. I/O is terminated when the requesting thread
|
|
goes away.
|
|
|
|
dwTimeout - This is a timeout that is to be used when waiting on the
|
|
hWaitableObject.
|
|
|
|
hDllReference - This is a handle to a Dll Reference Structure. The
|
|
handle is opaque to the caller. The structure contains a reference
|
|
count that allows the service controller to determine when it needs
|
|
unload the services dll.
|
|
|
|
Return Value:
|
|
|
|
Returns a handle to the WorkItem. This is a pointer to a structure
|
|
containing all relevant information concerning this wait request.
|
|
This should be treated as an opaque handle.
|
|
|
|
Returns 0xffffffff for successfully handling SVC_IMMEDIATE_CALLBACK.
|
|
|
|
Returns a NULL if an error occurs. Use GetLastError to Obtain the
|
|
error.
|
|
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
LPWAIT_ENTRY pWaitEntry=NULL;
|
|
DWORD status=ERROR_SUCCESS;
|
|
|
|
SC_LOG0(THREADS,"SvcAddWorkItem: ENTRY POINT\n");
|
|
|
|
ASSERT(dwFlags == SVC_IMMEDIATE_CALLBACK ||
|
|
dwFlags == SVC_QUEUE_WORK_ITEM);
|
|
//
|
|
// If the thread manager didn't initialize properly (Array=NULL), then
|
|
// just return.
|
|
//
|
|
if (SvcObjectArray == NULL) {
|
|
SC_LOG0(ERROR,"Couldn't add entry, Thread Manager Not Initialized\n");
|
|
SetLastError(SvcInitStatus);
|
|
return(NULL);
|
|
}
|
|
|
|
pWaitEntry = LocalAlloc(LPTR, sizeof(WAIT_ENTRY));
|
|
if (pWaitEntry == NULL) {
|
|
SC_LOG0(ERROR,"SvcAddWorkItem: Couldn't allocate for Wait Entry\n");
|
|
return(NULL);
|
|
}
|
|
|
|
pWaitEntry->Job.CallbackFunction = pCallbackFunction;
|
|
pWaitEntry->Job.Parameter = pContext;
|
|
pWaitEntry->Job.hDllReference = hDllReference;
|
|
pWaitEntry->ObjectHandle = hWaitableObject;
|
|
pWaitEntry->Timeout = dwTimeout;
|
|
|
|
//
|
|
// If there is no handle or timeout, then they must be wanting to
|
|
// drop a work item directly in the queue. So we do that and then
|
|
// return a handle for the client to use in case they need to
|
|
// terminate the job before it gets operated upon.
|
|
// NOTE: We don't want to queue the work item if we are to make an
|
|
// immediate callback.
|
|
//
|
|
if (dwFlags == SVC_QUEUE_WORK_ITEM) {
|
|
//
|
|
// We want to increment the DLL reference as long as a work item has
|
|
// been queued and we have a Dll reference handle.
|
|
// The reference count must be incremented BEFORE the item is queued,
|
|
// otherwise a worker thread might perform the item and free the DLL
|
|
// before we increment it.
|
|
// NOTE: Immediate callbacks don't queue work items, so we don't want
|
|
// to increment the reference count.
|
|
//
|
|
if (hDllReference != NULL) {
|
|
SvcIncrementDllRef(hDllReference);
|
|
}
|
|
|
|
//
|
|
// We queue the work item now if it doesn't have an object to wait on.
|
|
// Otherwise, we'll ask the watcher thread to wait on the object first.
|
|
//
|
|
if (hWaitableObject == NULL) {
|
|
SvcQueueWorkItem(&(pWaitEntry->Job));
|
|
}
|
|
}
|
|
|
|
//
|
|
// if (it's an operation for the watcher thread to perform)
|
|
//
|
|
if (dwFlags == SVC_IMMEDIATE_CALLBACK || hWaitableObject != NULL) {
|
|
|
|
BLOCK_ADD_DELETE(); // Block Further Adds and Deletes
|
|
|
|
if (dwFlags == SVC_IMMEDIATE_CALLBACK) {
|
|
SvcChangeEntry.Operation = OPERATE_IMMEDIATELY;
|
|
}
|
|
else {
|
|
SvcChangeEntry.Operation = ADD_WAIT_ENTRY;
|
|
}
|
|
SvcChangeEntry.EntryToChange = pWaitEntry;
|
|
|
|
//
|
|
// Wake up the Watcher Thread so that it will perform or add the entry,
|
|
// and wait until it's done.
|
|
//
|
|
if (SetEvent(SvcObjectArray[WAKEUP_WATCHER])) {
|
|
WaitForSingleObject(SvcChangeComplete,INFINITE);
|
|
SC_LOG0(THREADS,"SvcAddWorkItem: Watcher finished adding wait entry\n");
|
|
status = SvcChangeEntry.ReturnStatus;
|
|
}
|
|
else {
|
|
status = GetLastError();
|
|
SC_LOG1(ERROR,"SvcAddWorkItem: Couldn't wake up the watcher thread "
|
|
"for delete %d\n",status);
|
|
LocalFree(pWaitEntry);
|
|
pWaitEntry = NULL;
|
|
}
|
|
|
|
UNBLOCK_ADD_DELETE(); // Allow Adds and Deletes
|
|
}
|
|
|
|
if (dwFlags == SVC_IMMEDIATE_CALLBACK) {
|
|
if (pWaitEntry != NULL) {
|
|
LocalFree(pWaitEntry);
|
|
pWaitEntry = (LPWAIT_ENTRY)0xffffffff;
|
|
}
|
|
}
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
SetLastError(status);
|
|
pWaitEntry = NULL;
|
|
}
|
|
|
|
SC_LOG1(THREADS,"SvcAddWorkItem: EXIT POINT - new work item 0x%lx\n",pWaitEntry);
|
|
return((PVOID)pWaitEntry);
|
|
|
|
}
|
|
|
|
BOOL
|
|
SvcRemoveWorkItem(
|
|
IN HANDLE hWorkItem
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function removes a work item from either the wait list, or the
|
|
work queue. If the item didn't have a wait handle, then the
|
|
removal from the work queue can be handled by this routine.
|
|
If the item DID have a wait handle, then we must tell the Watcher
|
|
Thread to wakeup and removed the item from the wait list.
|
|
|
|
Arguments:
|
|
|
|
WaitHandle - This identifier identifies the handle to the waitable object.
|
|
|
|
Return Value:
|
|
|
|
TRUE - if the work item was successfully removed.
|
|
|
|
FALSE - if the work item wasn't successfully removed.
|
|
GetLastError() will return further error information. If
|
|
ERROR_INVALID_HANDLE is returned that means we couldn't find
|
|
the work item in the database. So either the handle is bogus, or
|
|
the handle is for a work item that was already operated on, and
|
|
is no-longer under our control. NOTE: A worker thread may be
|
|
servicing this workitem at the time when this is returned.
|
|
|
|
Note:
|
|
|
|
When deleting an entry from the array of waitable objects, the following
|
|
sequence should be followed. Wake up the current wait. Remove the handle
|
|
and replace it with the last one in the WaitEntry list. re-wait.
|
|
|
|
|
|
--*/
|
|
{
|
|
LPWAIT_ENTRY pWaitEntry=hWorkItem;
|
|
DWORD status;
|
|
BOOL bStat=TRUE;
|
|
HANDLE hDllReference=NULL;
|
|
|
|
SC_LOG1(THREADS,"SvcRemoveWorkItem: ENTRY POINT. WorkItem = 0x%lx\n",hWorkItem);
|
|
|
|
if (hWorkItem == NULL) {
|
|
SC_LOG0(THREADS,"SvcRemoveWorkItem: Item has already been removed!\n");
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// If the thread manager didn't initialize properly (Array=NULL), then just
|
|
// return.
|
|
//
|
|
if (SvcObjectArray == NULL) {
|
|
SetLastError(SvcInitStatus);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Get a Copy of the Dll Reference Handle.
|
|
//
|
|
hDllReference = pWaitEntry->Job.hDllReference;
|
|
|
|
//
|
|
// If the object being removed is in the WorkQueue, remove it if we can.
|
|
//
|
|
if ((pWaitEntry->ObjectHandle == NULL) &&
|
|
(pWaitEntry->Job.CallbackFunction != NULL)) {
|
|
|
|
//
|
|
// NOTE: If the following call is successful, the memory for
|
|
// pWaitEntry will be released.
|
|
//
|
|
if (!TmRemoveJobFromWorkQueue(&(pWaitEntry->Job))) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
bStat = FALSE;
|
|
}
|
|
goto CleanExit;
|
|
}
|
|
|
|
|
|
BLOCK_ADD_DELETE(); // Block Further Adds and Deletes
|
|
|
|
//
|
|
// Set up data so Watcher will know what to do when it wakes up.
|
|
//
|
|
SvcChangeEntry.Operation = DELETE_WAIT_ENTRY;
|
|
SvcChangeEntry.EntryToChange = pWaitEntry;
|
|
|
|
SC_LOG0(THREADS,"SvcRemoveWorkItem: Wait for Watcher to finish deleting "
|
|
"wait entry\n");
|
|
|
|
//
|
|
// Wake up the Watcher thread.
|
|
//
|
|
if (SetEvent(SvcObjectArray[WAKEUP_WATCHER])) {
|
|
WaitForSingleObject(SvcChangeComplete,INFINITE);
|
|
SC_LOG0(THREADS,"SvcRemoveWorkItem:Watcher finished deleting wait entry\n");
|
|
status = SvcChangeEntry.ReturnStatus;
|
|
}
|
|
else {
|
|
SC_LOG1(ERROR,"SvcRemoveWorkItem: Couldn't wake up the watcher thread "
|
|
"for delete %d\n",GetLastError());
|
|
status = GetLastError();
|
|
}
|
|
|
|
UNBLOCK_ADD_DELETE(); // Allow Adds and Deletes
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
SetLastError(status);
|
|
bStat = FALSE;
|
|
}
|
|
|
|
CleanExit:
|
|
|
|
//
|
|
// We only want to decrement the Dll Ref Count if we actually
|
|
// removed the work item. If we couldn't remove it, this means
|
|
// the work item is being operated on by a worker thread. That
|
|
// worker thread will decrement the reference count.
|
|
//
|
|
if ((bStat) && (hDllReference != NULL)) {
|
|
SvcDecrementDllRefAndFree(hDllReference);
|
|
}
|
|
|
|
SC_LOG1(THREADS,"SvcRemoveWorkItem: EXIT POINT. WorkItem = 0x%lx\n",hWorkItem);
|
|
return(bStat);
|
|
}
|
|
|
|
VOID
|
|
SvcObjectWatcher (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the thread that waits on the object handles for one to
|
|
become signaled. If one does, it determines which one, and then
|
|
calls the appropriate functions to service the signaled handle.
|
|
|
|
The array of object handles also contains a wakeup event handle.
|
|
That handle is signaled when it is necessary to wake up this thread
|
|
and have it wait again. This action is necessary when a new
|
|
handle has been added to, or removed from, the array of handles.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
DWORD objIndex;
|
|
DWORD i;
|
|
DWORD Time1;
|
|
DWORD ElapsedTime;
|
|
|
|
// The waitEntryJobList is a list of entries in the wait array that
|
|
// all timed out, or have become signaled at the same time.
|
|
//
|
|
LPWAIT_ENTRY waitEntryJobList[MAXIMUM_WAIT_OBJECTS+1];
|
|
LPWAIT_ENTRY *pTempWaitEntryJobList;
|
|
DWORD numEntries; // number of entries in waitEntryJobList
|
|
|
|
//
|
|
// If the watcher couldn't be initialized, the ScObjectArray will be
|
|
// NULL. Therefore, there is nothing to do here, so we will return.
|
|
//
|
|
if (SvcObjectArray == NULL) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Loop until shutdown waiting on process handles
|
|
//
|
|
while(1) {
|
|
numEntries = 0;
|
|
status = WAIT_OBJECT_0;
|
|
|
|
SC_LOG(THREADS,"[WATCHER]: Waiting on Multiple Objects (%d)...\n",
|
|
SvcObjectCount);
|
|
|
|
pTempWaitEntryJobList = waitEntryJobList;
|
|
Time1 = GetTickCount();
|
|
|
|
objIndex = WaitForMultipleObjectsEx(
|
|
SvcObjectCount,
|
|
SvcObjectArray,
|
|
FALSE, // Wait for any one to become signaled.
|
|
SvcShortestTimeout, // Timeout
|
|
TRUE // Alertable
|
|
);
|
|
|
|
ElapsedTime = GetTickCount() - Time1;
|
|
|
|
SC_LOG(THREADS,"[WATCHER]: Wake up from waiting on objects "
|
|
"index = %d\n",objIndex);
|
|
|
|
switch (objIndex) {
|
|
case WAKEUP_WATCHER:
|
|
break;
|
|
case WAIT_FAILED:
|
|
SC_LOG1(ERROR,"[WATCHER] FATAL ERROR: WaitForMultipleObjects "
|
|
"in Watcher failed %d\n", GetLastError());
|
|
return;
|
|
break;
|
|
case WAIT_TIMEOUT:
|
|
status = objIndex;
|
|
break;
|
|
case WAIT_IO_COMPLETION:
|
|
//
|
|
// For this to work with the browser, we need to turn around
|
|
// and wait again if we get this.
|
|
//
|
|
status = objIndex;
|
|
break;
|
|
default:
|
|
SC_LOG0(THREADS,"\n[WATCHER] *** An ObjHandle has become SIGNALED ***\n");
|
|
|
|
waitEntryJobList[0] = SvcWaitArray[objIndex];
|
|
pTempWaitEntryJobList = &(waitEntryJobList[1]);
|
|
numEntries = 1;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Look for any entries that would have timed out, and adjust all
|
|
// timeouts for the next wait.
|
|
//
|
|
numEntries += SvcAdjustTimeouts(pTempWaitEntryJobList, ElapsedTime);
|
|
|
|
if (numEntries > 0) {
|
|
|
|
//
|
|
// Queue the WorkItems and Remove the wait entries from the
|
|
// WaitArray.
|
|
//
|
|
for (i=0;i<numEntries ;i++ ) {
|
|
//
|
|
// The first item in the list might be added because it
|
|
// became signaled. It could also happen to fall in the
|
|
// timeout window. So it might appear in the list twice.
|
|
// This test skips item #i if it is the same as item #0.
|
|
//
|
|
if ((i != 0) && (waitEntryJobList[0] == waitEntryJobList[i])) {
|
|
continue;
|
|
}
|
|
else {
|
|
waitEntryJobList[i]->Job.WaitStatus = status;
|
|
waitEntryJobList[i]->ObjectHandle = NULL;
|
|
status = SvcQueueWorkItem(&(waitEntryJobList[i]->Job));
|
|
SvcRemoveWaitEntry(waitEntryJobList[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// ADD or DELETE an entry if necessary
|
|
//
|
|
if (SvcChangeEntry.EntryToChange != NULL) {
|
|
|
|
if (SvcChangeEntry.Operation == ADD_WAIT_ENTRY) {
|
|
SvcChangeEntry.ReturnStatus =
|
|
SvcAddWaitEntry(SvcChangeEntry.EntryToChange);
|
|
}
|
|
else if (SvcChangeEntry.Operation == OPERATE_IMMEDIATELY) {
|
|
PWORK_ITEM pWorkItem;
|
|
|
|
SC_LOG0(THREADS,"Calling WorkItem Callback Function\n");
|
|
|
|
try {
|
|
pWorkItem = &(SvcChangeEntry.EntryToChange->Job);
|
|
SvcChangeEntry.ReturnStatus = (pWorkItem->CallbackFunction)(
|
|
pWorkItem->Parameter,
|
|
pWorkItem->WaitStatus);
|
|
}
|
|
except(EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
SvcChangeEntry.ReturnStatus = GetExceptionCode();
|
|
SC_LOG(ERROR,"Watcher Operation Exception %lx\n",
|
|
SvcChangeEntry.ReturnStatus);
|
|
}
|
|
|
|
}
|
|
else {
|
|
//
|
|
// We are to delete an entry.
|
|
//
|
|
BOOL DoDelete=TRUE;
|
|
|
|
SvcChangeEntry.ReturnStatus = NO_ERROR;
|
|
|
|
//
|
|
// Look through the list of jobs that we just submitted
|
|
// to the work queue to see if this job is in that list.
|
|
//
|
|
// These jobs have already been removed from the
|
|
// WaitEntry list (wait array), so we use the DoDelete
|
|
// flag to tell us not to attempt to delete it from
|
|
// the WaitEntry list a second time.
|
|
//
|
|
for (i=0;i<numEntries ;i++ ) {
|
|
if (waitEntryJobList[i] == SvcChangeEntry.EntryToChange) {
|
|
//
|
|
// If the item is still in the WorkQueue, then
|
|
// remove it. If this fails, then it means it is
|
|
// already been operated on by a worker thread.
|
|
// In that case, we will consider it deleted.
|
|
//
|
|
TmRemoveJobFromWorkQueue(
|
|
&(SvcChangeEntry.EntryToChange->Job));
|
|
|
|
DoDelete = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (DoDelete) {
|
|
//
|
|
// The entry wasn't in the list of jobs just submitted,
|
|
// we can attempt to delete it from the wait array.
|
|
// If the entry doesn't exist in the wait array, because
|
|
// it was queued immediately, then we just return.
|
|
//
|
|
SvcChangeEntry.ReturnStatus =
|
|
SvcRemoveWaitEntry(SvcChangeEntry.EntryToChange);
|
|
|
|
if (SvcChangeEntry.ReturnStatus == NO_ERROR) {
|
|
LocalFree(SvcChangeEntry.EntryToChange);
|
|
}
|
|
}
|
|
}
|
|
SvcChangeEntry.EntryToChange = NULL;
|
|
SetEvent(SvcChangeComplete);
|
|
}
|
|
|
|
//
|
|
// If the watcher is to shutdown, then return from this thread.
|
|
//
|
|
if (WatcherShutdown) {
|
|
SC_LOG0(THREADS,"[WATCHER] The watcher has been told to terminate\n");
|
|
ThreadManagerCleanup();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
SvcInitThreadManager (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called during service controller initialization.
|
|
It sets up the critical section used by the watcher thread. It
|
|
also creates various events and allocates memory used by the watcher.
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The initialization was successful.
|
|
|
|
FALSE - The initialization failed.
|
|
|
|
Note:
|
|
|
|
This should be called before the RPC Server is started.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
HANDLE hWatcher=NULL;
|
|
DWORD ThreadId;
|
|
|
|
//
|
|
// Initialize the critical section for Service DLL Reference count
|
|
// manipulation.
|
|
//
|
|
InitializeCriticalSection(&SvcDllRefCountCritSec);
|
|
|
|
//
|
|
// These events are initially signaled. The lock is obtained by
|
|
// waiting on it which causes it to be reset to the non-signaled
|
|
// state. Another thread will wait forever until the lock is
|
|
// released. The lock is released by setting the event, making it
|
|
// signaled again.
|
|
//
|
|
SvcAddDelLock = CreateEvent(
|
|
NULL, //
|
|
FALSE, // ManualReset (Auto reset to non-signaled).
|
|
TRUE, // Initial state (signaled)
|
|
NULL); //
|
|
|
|
if (SvcAddDelLock == NULL) {
|
|
SvcInitStatus = GetLastError();
|
|
SC_LOG0(ERROR,"ThreadManager: Couldn't allocate AddDeleteLock\n");
|
|
return(FALSE);
|
|
}
|
|
|
|
SvcWorkerLock = CreateEvent(
|
|
NULL, //
|
|
FALSE, // ManualReset (Auto reset to non-signaled).
|
|
TRUE, // Initial state (signaled)
|
|
NULL); //
|
|
if (SvcWorkerLock == NULL) {
|
|
SC_LOG0(ERROR,"ThreadManager: Couldn't allocate WorkerLock\n");
|
|
goto ErrorExit;
|
|
}
|
|
|
|
SvcChangeComplete = CreateEvent(
|
|
NULL, //
|
|
FALSE, // ManualReset (auto-reset to non-signaled selected)
|
|
FALSE, // Initial state (not-signaled)
|
|
NULL); //
|
|
if (SvcChangeComplete == NULL) {
|
|
SC_LOG0(ERROR,"ThreadManager: Couldn't allocate ChangeComplete Event\n");
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Create SvcWaitArray
|
|
//
|
|
SvcMaxNumObjects = MAXIMUM_WAIT_OBJECTS;
|
|
|
|
SvcWaitArray = (LPWAIT_ENTRY *)LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
sizeof(HANDLE) * SvcMaxNumObjects);
|
|
|
|
if (SvcWaitArray == NULL) {
|
|
SC_LOG0(ERROR,"ThreadManager: Couldn't allocate Wait array\n");
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Initialize the ObjectArray and the counts
|
|
//
|
|
SvcObjectArray = (LPHANDLE)LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
sizeof(HANDLE) * SvcMaxNumObjects);
|
|
|
|
if (SvcObjectArray == NULL) {
|
|
|
|
SC_LOG0(ERROR,"ThreadManager: Couldn't allocate watcher array\n");
|
|
|
|
//
|
|
// EVENTLOG - Log an event here!
|
|
// (only the eventlog isn't running yet)
|
|
//
|
|
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Create the WAKEUP_WATCHER event.
|
|
//
|
|
SvcObjectArray[WAKEUP_WATCHER] =
|
|
CreateEvent(
|
|
NULL, // Event Attributes
|
|
FALSE, // ManualReset (auto-reset selected)
|
|
TRUE, // Initial State (signaled)
|
|
NULL); // Name
|
|
|
|
if (SvcObjectArray[WAKEUP_WATCHER] == NULL) {
|
|
SC_LOG(ERROR, "ThreadManager:CreateEvent: FAILURE %ld\n",
|
|
GetLastError());
|
|
//
|
|
// EVENTLOG - Log an event here?
|
|
// (only the eventlog isn't running yet)
|
|
//
|
|
|
|
goto ErrorExit;
|
|
|
|
}
|
|
|
|
//----------------------------------------
|
|
// Initialize Thread Manager Resources
|
|
//----------------------------------------
|
|
InitializeListHead(&TmWorkerQueueHead);
|
|
InitializeListHead(&SvcDllRefListHead);
|
|
|
|
status = TmGetThreadMaxFromReg(&TmMaxNumThreads);
|
|
if (status != NO_ERROR) {
|
|
goto ErrorExit;
|
|
return(status);
|
|
}
|
|
|
|
SC_LOG(THREADS,"Thread MAXIMUM = %d\n",TmMaxNumThreads);
|
|
|
|
TmWorkerSemaphore = CreateSemaphore(
|
|
NULL, // Attributes
|
|
0, // Initial Count
|
|
0x7fffffff, // maximum count
|
|
NULL); // name
|
|
|
|
if (TmWorkerSemaphore == NULL) {
|
|
status = GetLastError();
|
|
goto ErrorExit;
|
|
}
|
|
|
|
SvcObjectCount = 1;
|
|
|
|
//
|
|
// Start the a thread for the Watcher.
|
|
//
|
|
|
|
hWatcher = CreateThread(
|
|
NULL, // Thread Attributes.
|
|
0L, // Stack Size
|
|
(LPTHREAD_START_ROUTINE)SvcObjectWatcher, // lpStartAddress
|
|
(LPVOID)NULL, // lpParameter
|
|
0L, // Creation Flags
|
|
&ThreadId); // lpThreadId
|
|
|
|
if (hWatcher == NULL) {
|
|
SC_LOG1(ERROR,"Failed to create watcher thread %d \n",GetLastError());
|
|
goto ErrorExit;
|
|
}
|
|
CloseHandle(hWatcher);
|
|
|
|
SC_LOG0(THREADS,"Thread Manager Initialization is Successful\n");
|
|
|
|
return(TRUE);
|
|
|
|
ErrorExit:
|
|
SvcInitStatus = GetLastError();
|
|
SC_LOG1(ERROR,"ThreadManager: Error Code = %d\n",SvcInitStatus);
|
|
|
|
if (SvcObjectArray != NULL) {
|
|
if (SvcObjectArray[WAKEUP_WATCHER] != NULL) {
|
|
CloseHandle(SvcObjectArray[WAKEUP_WATCHER]);
|
|
SvcObjectArray[WAKEUP_WATCHER] = NULL;
|
|
}
|
|
LocalFree(SvcObjectArray);
|
|
SvcObjectArray = NULL;
|
|
}
|
|
if (SvcAddDelLock != NULL) {
|
|
CloseHandle(SvcAddDelLock);
|
|
SvcAddDelLock = NULL;
|
|
}
|
|
if (SvcWorkerLock != NULL) {
|
|
CloseHandle(SvcWorkerLock);
|
|
SvcWorkerLock = NULL;
|
|
}
|
|
if (TmWorkerSemaphore != NULL) {
|
|
CloseHandle(TmWorkerSemaphore);
|
|
TmWorkerSemaphore = NULL;
|
|
}
|
|
SvcMaxNumObjects = 0;
|
|
|
|
SC_LOG1(THREADS,"Thread Manager Initialization FAILED %d\n",SvcInitStatus);
|
|
return(FALSE);
|
|
}
|
|
|
|
VOID
|
|
ThreadManagerCleanup(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleans up resources used by the ThreadManager.
|
|
|
|
For best results, call this function after the rpc interface has shut
|
|
down.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
SC_LOG0(THREADS,"ThreadManagerCleanup: ENTRY POINT/n");
|
|
//
|
|
// If the ThreadManager didn't initialize properly (Array=NULL), then just
|
|
// return.
|
|
//
|
|
if (SvcObjectArray == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (SvcObjectArray[WAKEUP_WATCHER] != NULL) {
|
|
(void) CloseHandle(SvcObjectArray[WAKEUP_WATCHER]);
|
|
}
|
|
|
|
SvcObjectCount = 0;
|
|
SvcMaxNumObjects = 0;
|
|
|
|
if (SvcObjectArray != NULL) {
|
|
LocalFree(SvcObjectArray);
|
|
}
|
|
|
|
CloseHandle(SvcAddDelLock);
|
|
SvcAddDelLock = NULL;
|
|
|
|
SC_LOG0(THREADS,"ThreadManagerCleanup: EXIT POINT/n");
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
SvcShutdownObjectWatcher(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calling this function causes the Thread Manager to begin its shutdown
|
|
sequence. It will discontinue any pending waits, and free up resources.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// If the watcher didn't initialize properly (Array=NULL), then just
|
|
// return.
|
|
//
|
|
if (SvcObjectArray == NULL) {
|
|
return;
|
|
}
|
|
|
|
WatcherShutdown = TRUE;
|
|
|
|
if (!SetEvent(SvcObjectArray[WAKEUP_WATCHER])) {
|
|
//
|
|
// If this fails, there isn't much we can do about it. So we
|
|
// just continue on.
|
|
//
|
|
SC_LOG(ERROR,"SvcShutdownThreadManager: Couldn't wake up the watcher thread %d\n",
|
|
GetLastError());
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
SvcRemoveWaitEntry(
|
|
LPWAIT_ENTRY pEntryToDelete
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes an entry from the WaitArray and the SvcObjectArray.
|
|
The order of entries in the WaitArray are expected to be exactly the
|
|
same as the order of entries in the SvcObjectArray.
|
|
|
|
Entries are removed from the SvcObjectArray by moving the handle at
|
|
the end of the array into the location that is being vacated.
|
|
The WaitArray of WaitEntries must then have the same order change.
|
|
|
|
Memory for pEntryToDelete is NOT Free'd.
|
|
|
|
RESTRICTIONS:
|
|
Only the Watcher Thread can call this function!
|
|
|
|
Arguments:
|
|
|
|
pEntryToDelete - This is a pointer to the WaitList entry that is being
|
|
deleted.
|
|
|
|
Return Value:
|
|
|
|
status of the operation.
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD index;
|
|
|
|
//
|
|
// Make sure the entry is in the array first.
|
|
//
|
|
for (index=1; index<SvcObjectCount; index++ ) {
|
|
if (SvcWaitArray[index] == pEntryToDelete) {
|
|
break;
|
|
}
|
|
}
|
|
if (index >= SvcObjectCount) {
|
|
SC_LOG1(THREADS,"RemoveWaitEntry bad handle %d\n",pEntryToDelete);
|
|
return(ERROR_INVALID_HANDLE);
|
|
}
|
|
|
|
SC_LOG2(THREADS, "RemoveWaitEntry 0x%lx, handle=0x%lx\n",
|
|
pEntryToDelete, pEntryToDelete->ObjectHandle);
|
|
|
|
|
|
// Put last handle in list into location of the
|
|
// handle that is being removed.
|
|
|
|
SvcObjectCount--;
|
|
|
|
SvcObjectArray[index] = SvcObjectArray[SvcObjectCount];
|
|
SvcObjectArray[SvcObjectCount] = NULL;
|
|
|
|
// Update the wait entry for both items. (the one
|
|
// being removed, and the one whose handle changed
|
|
// location).
|
|
|
|
// Put the last pointer in wait array into location of
|
|
// the handle that is being removed.
|
|
|
|
SvcWaitArray[index] = SvcWaitArray[SvcObjectCount];
|
|
SvcWaitArray[SvcObjectCount] = NULL;
|
|
|
|
if (index < SvcObjectCount) {
|
|
ASSERT(SvcWaitArray[index]->ObjectHandle == SvcObjectArray[index]);
|
|
}
|
|
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
DWORD
|
|
SvcAddWaitEntry(
|
|
LPWAIT_ENTRY pEntryToAdd
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function adds an entry to the WaitArray and the SvcObjectArray.
|
|
The order of entries in the WaitArray are expected to be exactly the
|
|
same as the order of entries in the SvcObjectArray.
|
|
|
|
RESTRICTIONS:
|
|
Only the Watcher Thread can call this function!
|
|
|
|
|
|
Arguments:
|
|
|
|
pEntryToAdd - This is a pointer to the WaitList entry that is being
|
|
deleted.
|
|
|
|
Return Value:
|
|
|
|
status.
|
|
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Add the new handle to the array, and increment the count.
|
|
//
|
|
|
|
if (SvcObjectCount >= SvcMaxNumObjects) {
|
|
SC_LOG0(ERROR,"Thread Manager: Array full! Couldn't add the handle\n");
|
|
return(WAIT_FAILED);
|
|
}
|
|
|
|
SC_LOG1(THREADS,"SvcAddWaitEntry 0x%lx\n",pEntryToAdd);
|
|
|
|
SvcObjectArray[SvcObjectCount]= pEntryToAdd->ObjectHandle;
|
|
|
|
SC_LOG1(THREADS," HANDLE ADDED TO WAIT ARRAY: 0x%lx\n",pEntryToAdd->ObjectHandle);
|
|
|
|
//
|
|
// If this entry has a smaller timeout than the current shortest
|
|
// timeout, then reset the timeout to the smallest.
|
|
//
|
|
if (pEntryToAdd->Timeout < SvcShortestTimeout) {
|
|
SvcShortestTimeout = pEntryToAdd->Timeout;
|
|
}
|
|
|
|
SvcWaitArray[SvcObjectCount] = pEntryToAdd;
|
|
|
|
SvcObjectCount++;
|
|
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
DWORD
|
|
SvcAdjustTimeouts(
|
|
LPWAIT_ENTRY *pJobList,
|
|
DWORD ElapsedTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function looks through the array of wait items for timeout values
|
|
that match the timeout in SvcShortestTimeout. The wait items
|
|
have all just become signaled. A pointer to an array of pointers to
|
|
those wait entries is returned.
|
|
|
|
Then all the non-infinite timeouts are then adjusted by subtracting
|
|
SvcShortestTimout. Then SvcShortestTimeout is updated to be the
|
|
shortest of the remaining timeouts.
|
|
|
|
RESTRICTIONS:
|
|
Only the Watcher Thread can call this function!
|
|
|
|
Arguments:
|
|
|
|
pJobList - A pointer to an array of size MAXIMUM_WAIT_OBJECTS
|
|
that is to be filled in by this function.
|
|
|
|
ElapsedTime - This is the time (in milliseconds) that has elapsed
|
|
while waiting for handles to become signaled.
|
|
|
|
Return Value:
|
|
|
|
Indicates how many entries were returned in the pJobList.
|
|
|
|
--*/
|
|
{
|
|
DWORD i;
|
|
DWORD numEntries=0;
|
|
DWORD newShortestTime=INFINITE;
|
|
DWORD FudgedElapsedTime;
|
|
|
|
SC_LOG1(THREADS,"\nSvcAdjustTimeouts: ENTRY_POINT (elapsed time = %d)\n", ElapsedTime);
|
|
|
|
if (SvcShortestTimeout == INFINITE) {
|
|
//
|
|
// The shortest timeout will be INFINITE only if there are no other
|
|
// entries with timeouts.
|
|
//
|
|
SC_LOG0(THREADS,"SvcAdjustTimeouts: ShortestTimeout=INFINITE\n");
|
|
return(0);
|
|
}
|
|
|
|
//
|
|
// If the elapsed time is within 50msec of the
|
|
// SvcShortestTime, then we will consider the entry
|
|
// to be timed out.
|
|
//
|
|
FudgedElapsedTime = ElapsedTime + 50;
|
|
|
|
for (i=1; i<SvcObjectCount ;i++ ) {
|
|
if (SvcWaitArray[i]->Timeout != INFINITE) {
|
|
|
|
if (SvcWaitArray[i]->Timeout <= FudgedElapsedTime) {
|
|
SC_LOG0(THREADS,"SvcAdjustTimeouts: A Timeout was found!\n");
|
|
pJobList[numEntries] = SvcWaitArray[i];
|
|
numEntries++;
|
|
}
|
|
else {
|
|
(SvcWaitArray[i]->Timeout) -= ElapsedTime;
|
|
if (SvcWaitArray[i]->Timeout < newShortestTime ) {
|
|
newShortestTime = SvcWaitArray[i]->Timeout;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SC_LOG2(THREADS,"Shortest Timeout Old = %d, New = %d\n",
|
|
SvcShortestTimeout,newShortestTime);
|
|
|
|
//
|
|
// We will always overwrite the shortest timeout with the new value.
|
|
// Since we have accounted for all the known timeouts at this point in time.
|
|
//
|
|
SvcShortestTimeout = newShortestTime;
|
|
|
|
return(numEntries);
|
|
}
|
|
|
|
DWORD
|
|
SvcQueueWorkItem(
|
|
IN PWORK_ITEM pWorkItem
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deposits a WorkItem in the WorkQueue. If there are no
|
|
threads available to read the queue, it will spawn a thread to handle
|
|
it.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
|
|
LOCK_WORK_QUEUE();
|
|
|
|
SC_LOG1(THREADS,"SvcQueueWorkItem 0x%lx\n",pWorkItem);
|
|
|
|
TmCheckStartWorkerThread();
|
|
|
|
InsertTailList( &TmWorkerQueueHead, &pWorkItem->List);
|
|
|
|
ReleaseSemaphore(TmWorkerSemaphore,1,NULL);
|
|
|
|
UNLOCK_WORK_QUEUE();
|
|
|
|
return(0);
|
|
}
|
|
|
|
BOOL
|
|
TmRemoveJobFromWorkQueue(
|
|
IN PWORK_ITEM pWorkItem
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks to see if it can remove a WorkItem in the WorkQueue.
|
|
If it can, it removes it, and deletes the memory associated with it.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - This is a pointer to the work item to be removed.
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY pList;
|
|
BOOL Found=FALSE;
|
|
|
|
LOCK_WORK_QUEUE();
|
|
|
|
//
|
|
// Remove Job From WorkQueue.
|
|
//
|
|
|
|
SC_LOG(THREADS,"TmRemoveJobFromWorkQueue 0x%lx\n",pWorkItem);
|
|
|
|
if (!IsListEmpty(&TmWorkerQueueHead)) {
|
|
|
|
pList = &TmWorkerQueueHead;
|
|
|
|
while (pList->Flink != &TmWorkerQueueHead) {
|
|
if (pList->Flink == &(pWorkItem->List)) {
|
|
RemoveEntryList(&(pWorkItem->List));
|
|
Found=TRUE;
|
|
break;
|
|
}
|
|
pList = pList->Flink;
|
|
}
|
|
}
|
|
|
|
UNLOCK_WORK_QUEUE();
|
|
|
|
if (Found) {
|
|
LocalFree(pWorkItem);
|
|
return(TRUE);
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
VOID
|
|
TmWorkerThread(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a worker thread. It will wait for a timeout period (60 secs)
|
|
for the TmWorkerSemaphore to become signaled. When the semaphore is
|
|
signaled, that means a work item needs servicing. The work item is
|
|
then removed from the list and the users callback function is called.
|
|
Upon return, this thread then waits again for a request.
|
|
|
|
When the wait times out, this function will one more time. If the
|
|
second wait times out, then this thread goes away.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD Timeout;
|
|
DWORD WaitStatus=WAIT_OBJECT_0;
|
|
BOOL FirstTimeout=TRUE;
|
|
LPWORK_ITEM pWorkItem;
|
|
|
|
do {
|
|
|
|
Timeout = THREAD_WAIT_TIMEOUT;
|
|
|
|
SC_LOG0(THREADS,"TmWorkerThread: Waiting for a job\n");
|
|
//
|
|
// Wait For WorkItem
|
|
//
|
|
WaitStatus = WaitForSingleObject(TmWorkerSemaphore,Timeout);
|
|
|
|
LOCK_WORK_QUEUE();
|
|
|
|
//
|
|
// Decrement NumWaitingThreads
|
|
//
|
|
TmNumWaitingThreads--;
|
|
|
|
//
|
|
// Wait Status
|
|
//
|
|
switch (WaitStatus) {
|
|
case WAIT_OBJECT_0 :
|
|
//
|
|
// Monitor Thread Activity
|
|
//
|
|
|
|
//
|
|
// CheckStartWorkerThread.
|
|
// If this fails, we can't do much about it, so we go and
|
|
// process the next work item with the thread we have.
|
|
//
|
|
// BUGBUG: This is being removed because it doesn't look like
|
|
// we need to start up a new thread just to listen for requests.
|
|
//
|
|
// TmCheckStartWorkerThread();
|
|
|
|
//
|
|
// Pull WorkItem out of Queue and process it.
|
|
//
|
|
|
|
pWorkItem = NULL;
|
|
if (!IsListEmpty(&TmWorkerQueueHead)) {
|
|
pWorkItem = (LPWORK_ITEM)RemoveHeadList(&TmWorkerQueueHead);
|
|
}
|
|
|
|
UNLOCK_WORK_QUEUE();
|
|
|
|
if (pWorkItem != NULL) {
|
|
SC_LOG1(THREADS,"TmWorkerThread: Make Callback for job 0x%1lx\n",
|
|
pWorkItem);
|
|
// ---------------------------------------
|
|
// Call the entry point for the work item.
|
|
// NOTE: The service that owns the work
|
|
// item may have shutdown, so this
|
|
// function could access violate.
|
|
// ---------------------------------------
|
|
try {
|
|
(pWorkItem->CallbackFunction)(
|
|
pWorkItem->Parameter,
|
|
pWorkItem->WaitStatus);
|
|
}
|
|
except(EXCEPTION_EXECUTE_HANDLER) {
|
|
SC_LOG(ERROR,"Worker Thread Exception %lx\n",
|
|
GetExceptionCode());
|
|
}
|
|
|
|
//***************************************************************
|
|
//
|
|
// See if we are to unload the service DLL library.
|
|
//
|
|
//
|
|
if (pWorkItem->hDllReference != NULL) {
|
|
SvcDecrementDllRefAndFree(pWorkItem->hDllReference);
|
|
}
|
|
|
|
//***************************************************************
|
|
|
|
//
|
|
// Free up the memory for the WorkItem.
|
|
//
|
|
LocalFree(pWorkItem);
|
|
|
|
}
|
|
|
|
LOCK_WORK_QUEUE();
|
|
|
|
//
|
|
// Increment NumWaitingThreads
|
|
//
|
|
TmNumWaitingThreads++;
|
|
|
|
FirstTimeout = TRUE;
|
|
break;
|
|
case WAIT_TIMEOUT:
|
|
//
|
|
// If this is the last thread, then wait for one
|
|
// more timeout period.
|
|
//
|
|
SC_LOG1(THREADS,"Thread timed out. Current count = %d\n",
|
|
TmNumThreads);
|
|
|
|
if ((TmNumThreads == 1) && (FirstTimeout)) {
|
|
FirstTimeout = FALSE;
|
|
WaitStatus = WAIT_OBJECT_0;
|
|
TmNumWaitingThreads++;
|
|
}
|
|
else {
|
|
TmNumThreads--;
|
|
}
|
|
break;
|
|
default:
|
|
//
|
|
// DO WHAT FOR OTHER WaitStatus's???
|
|
//
|
|
TmNumThreads--;
|
|
SC_LOG1(THREADS,"%d Status from WorkQueueWait Exiting Thread "
|
|
" new thread count = %d\n",TmNumThreads);
|
|
break;
|
|
}
|
|
|
|
UNLOCK_WORK_QUEUE();
|
|
|
|
} while (WaitStatus == WAIT_OBJECT_0);
|
|
|
|
//
|
|
// Exit this Thread
|
|
//
|
|
SC_LOG1(THREADS,"TmWorkerThread: This thread is terminating.\n"
|
|
"Remaining count = %d\n",TmNumThreads);
|
|
|
|
ExitThread(NO_ERROR);
|
|
}
|
|
|
|
|
|
VOID
|
|
TmCheckStartWorkerThread(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks to see if a new worker thread should be started.
|
|
A new thread will not be started if:
|
|
> We are already running with the maximum number of threads.
|
|
> There is already a thread waiting to read from the work queue.
|
|
|
|
If a new worker thread is to be created, we increment the number
|
|
of waiting threads after we create it. If we waited until the thread
|
|
was actually running before we incremented this number, then we would
|
|
find ourselves in the situation where we might call create thread
|
|
many times before one of them is actually running. That would be the
|
|
case where this routine is called from many threads at virtually the
|
|
same time.
|
|
|
|
LOCKS: Must hold the WORK_QUEUE lock.
|
|
|
|
Arguments:
|
|
none
|
|
|
|
Return Value:
|
|
none
|
|
|
|
--*/
|
|
{
|
|
HANDLE hThread;
|
|
DWORD threadId;
|
|
|
|
//
|
|
// If we are already running MaxNumThreads, then don't start
|
|
// a new one.
|
|
//
|
|
if (TmNumThreads >= TmMaxNumThreads) {
|
|
SC_LOG0(THREADS,"CheckStartWorkerThread: Already have MaxNumThreads "
|
|
"don't start a new one\n");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If there is already one waiting thread, then don't start
|
|
// a new one.
|
|
//
|
|
|
|
if (TmNumWaitingThreads > 0) {
|
|
SC_LOG0(THREADS,"CheckStartWorkerThread: Thread already waiting "
|
|
"don't start a new one\n");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Create the new Thread.
|
|
//
|
|
hThread = CreateThread(
|
|
NULL, // Thread Attributes.
|
|
0L, // Stack Size
|
|
(LPTHREAD_START_ROUTINE)TmWorkerThread, // lpStartAddress
|
|
(LPVOID)NULL, // lpParameter
|
|
0L, // Creation Flags
|
|
&threadId); // lpThreadId
|
|
|
|
if (hThread == NULL) {
|
|
SC_LOG1(ERROR, "CheckStartWorkerThread: Couldn't start worker thread %d",
|
|
GetLastError());
|
|
}
|
|
else {
|
|
TmNumThreads++;
|
|
|
|
SC_LOG1(THREADS,"A new Worker Thread has been started. NumThreads=%d\n",
|
|
TmNumThreads);
|
|
SC_LOG1(THREADS," THREAD HANDLE = 0x%lx\n",hThread);
|
|
//
|
|
// Increment the NumWaitingThreads
|
|
//
|
|
TmNumWaitingThreads++;
|
|
|
|
//
|
|
// We shouldn't ever need to shut down and clean up, so
|
|
// there isn't any need to keep the thread handle around.
|
|
//
|
|
CloseHandle(hThread);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
DWORD
|
|
TmGetThreadMaxFromReg(
|
|
LPDWORD pMaxNumThreads
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
HKEY controlKey;
|
|
DWORD valueType;
|
|
DWORD bufSize = sizeof(DWORD);
|
|
|
|
*pMaxNumThreads = DEFAULT_MAX_THREADS;
|
|
//
|
|
// Read the Registry Information for Notifiees.
|
|
// If the key doesn't exist, then there is no one to notify.
|
|
//
|
|
status = RegOpenKey (
|
|
HKEY_LOCAL_MACHINE, // hKey
|
|
REG_MAX_THREADS, // lpSubKey
|
|
&controlKey); // Newly Opened Key Handle
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG0(ERROR,"Can't find Control Key in Registry\n");
|
|
return(GetLastError());
|
|
}
|
|
status = RegQueryValueEx(
|
|
controlKey,
|
|
L"ServicesMaxWorkers",
|
|
NULL,
|
|
&valueType,
|
|
(LPBYTE)pMaxNumThreads,
|
|
&bufSize);
|
|
|
|
if (status != NO_ERROR) {
|
|
SC_LOG0(THREADS,"Can't find ServicesMaxWorkers Value in Registry\n");
|
|
return(GetLastError());
|
|
}
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
|
|
HANDLE
|
|
SvcCreateDllReference(
|
|
HINSTANCE hDll
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
PDLL_REFERENCE pReference;
|
|
|
|
|
|
|
|
EnterCriticalSection(&SvcDllRefCountCritSec);
|
|
|
|
pReference = LocalAlloc(LPTR, sizeof(DLL_REFERENCE));
|
|
if (pReference == NULL) {
|
|
SC_LOG1(ERROR,"SvcCreateDllReference: LocalAlloc Failed %d\n",
|
|
GetLastError());
|
|
LeaveCriticalSection(&SvcDllRefCountCritSec);
|
|
return(NULL);
|
|
}
|
|
|
|
pReference->Signature = DLL_REF_SIGNATURE;
|
|
pReference->hDll = hDll;
|
|
pReference->Count = 1;
|
|
|
|
//
|
|
// Add the entry to the linked list.
|
|
//
|
|
InsertTailList (&SvcDllRefListHead, &pReference->List);
|
|
|
|
LeaveCriticalSection(&SvcDllRefCountCritSec);
|
|
return((HANDLE)pReference);
|
|
}
|
|
BOOL
|
|
SvcIncrementDllRef(
|
|
HANDLE hDllReference
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
PDLL_REFERENCE pReference=(PDLL_REFERENCE)hDllReference;
|
|
|
|
EnterCriticalSection(&SvcDllRefCountCritSec);
|
|
|
|
//
|
|
// Check the signature of this structure to make sure we aren't
|
|
// modifying some random memory location.
|
|
//
|
|
if (pReference->Signature != DLL_REF_SIGNATURE) {
|
|
SC_LOG0(ERROR,"SvcIncrementDllRef: Bad Signature\n");
|
|
ASSERT(pReference->Signature == DLL_REF_SIGNATURE);
|
|
LeaveCriticalSection(&SvcDllRefCountCritSec);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Increment the Count
|
|
//
|
|
pReference->Count++;
|
|
LeaveCriticalSection(&SvcDllRefCountCritSec);
|
|
return(TRUE);
|
|
}
|
|
|
|
BOOL
|
|
SvcDecrementDllRefAndFree(
|
|
HANDLE hDllReference
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
PDLL_REFERENCE pReference=(PDLL_REFERENCE)hDllReference;
|
|
|
|
EnterCriticalSection(&SvcDllRefCountCritSec);
|
|
|
|
//
|
|
// Check the signature of this structure to make sure we aren't
|
|
// modifying some random memory location.
|
|
//
|
|
if (pReference->Signature != DLL_REF_SIGNATURE) {
|
|
SC_LOG0(ERROR,"SvcDecrementDllRef: Bad Signature\n");
|
|
ASSERT(pReference->Signature == DLL_REF_SIGNATURE);
|
|
LeaveCriticalSection(&SvcDllRefCountCritSec);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Decrement the Count. If it reaches 0, free the lib and clean up.
|
|
//
|
|
if (--(pReference->Count) <= 0) {
|
|
if (!FreeLibrary(pReference->hDll)) {
|
|
SC_LOG1(ERROR,"SERVICES: Can't unload Service DLL %ld\n",
|
|
GetLastError());
|
|
}
|
|
//
|
|
// Remove entry from the linked list, and free memory.
|
|
//
|
|
RemoveEntryList(&pReference->List);
|
|
|
|
LocalFree(pReference);
|
|
SC_LOG0(THREADS,"SvcDecrementDllRefAndFree: Removed entry from list\n");
|
|
}
|
|
|
|
LeaveCriticalSection(&SvcDllRefCountCritSec);
|
|
|
|
return(TRUE);
|
|
}
|
|
|