656 lines
17 KiB
C
656 lines
17 KiB
C
/*++
|
||
|
||
Copyright (c) 1992-1996 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
worker.c
|
||
|
||
Abstract:
|
||
|
||
This module implements a worker thread and a set of functions for
|
||
passing work to it.
|
||
|
||
Author:
|
||
|
||
Larry Osterman (LarryO) 13-Jul-1992
|
||
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
//
|
||
// Common include files.
|
||
//
|
||
|
||
#include "logonsrv.h" // Include files common to entire service
|
||
#pragma hdrstop
|
||
|
||
//
|
||
// Number of worker threads to create and the usage count array.
|
||
//
|
||
|
||
|
||
#define NL_MAX_WORKER_THREADS 5
|
||
ULONG NlNumberOfCreatedWorkerThreads = 0;
|
||
|
||
ULONG NlWorkerThreadStats[NL_MAX_WORKER_THREADS];
|
||
PHANDLE NlThreadArray[NL_MAX_WORKER_THREADS];
|
||
BOOLEAN NlThreadExitted[NL_MAX_WORKER_THREADS];
|
||
|
||
//
|
||
// CritSect guard the WorkQueue list.
|
||
//
|
||
|
||
BOOLEAN NlWorkerInitialized = FALSE;
|
||
CRITICAL_SECTION NlWorkerCritSect;
|
||
|
||
#define LOCK_WORK_QUEUE() EnterCriticalSection(&NlWorkerCritSect);
|
||
#define UNLOCK_WORK_QUEUE() LeaveCriticalSection(&NlWorkerCritSect);
|
||
|
||
//
|
||
// Head of singly linked list of work items queued to the worker thread.
|
||
//
|
||
|
||
LIST_ENTRY NlWorkerQueueHead = {0};
|
||
LIST_ENTRY NlWorkerHighQueueHead = {0};
|
||
|
||
VOID
|
||
NlWorkerThread(
|
||
IN PVOID StartContext
|
||
)
|
||
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
ULONG ThreadIndex = (ULONG)((ULONG_PTR)StartContext);
|
||
|
||
ULONG Index;
|
||
PWORKER_ITEM WorkItem;
|
||
|
||
HANDLE EventHandle = NULL;
|
||
|
||
|
||
//
|
||
// Every thread should loop until the queue is empty.
|
||
//
|
||
// This loop completes even though Netlogon has been asked to terminate.
|
||
// The individual worker routines are designed to terminate quickly when netlogon
|
||
// is terminating. This philosophy allows the worker routines to do there own
|
||
// cleanup.
|
||
//
|
||
|
||
while( TRUE ) {
|
||
|
||
//
|
||
// Pull an entry off the queue
|
||
//
|
||
// Prefer a workitem from the high priority queue
|
||
//
|
||
|
||
LOCK_WORK_QUEUE();
|
||
|
||
if (!IsListEmpty(&NlWorkerHighQueueHead)) {
|
||
WorkItem = (PWORKER_ITEM)RemoveHeadList( &NlWorkerHighQueueHead );
|
||
} else if (!IsListEmpty(&NlWorkerQueueHead)) {
|
||
WorkItem = (PWORKER_ITEM)RemoveHeadList( &NlWorkerQueueHead );
|
||
} else {
|
||
UNLOCK_WORK_QUEUE();
|
||
break;
|
||
}
|
||
|
||
NlAssert(WorkItem->Inserted);
|
||
WorkItem->Inserted = FALSE;
|
||
|
||
NlWorkerThreadStats[NlNumberOfCreatedWorkerThreads-1] += 1;
|
||
|
||
UNLOCK_WORK_QUEUE();
|
||
|
||
NlPrint(( NL_WORKER, "%lx: Pulling off work item %lx (%lx)\n", ThreadIndex, WorkItem, WorkItem->WorkerRoutine));
|
||
|
||
|
||
//
|
||
// Execute the specified routine.
|
||
//
|
||
|
||
(WorkItem->WorkerRoutine)( WorkItem->Parameter );
|
||
|
||
|
||
|
||
//
|
||
// A thread can ditch dangling thread handles for the other threads.
|
||
//
|
||
// This will ensure there is at most one dangling thread handle.
|
||
//
|
||
|
||
LOCK_WORK_QUEUE();
|
||
for (Index = 0; Index < NL_MAX_WORKER_THREADS; Index++ ) {
|
||
if ( ThreadIndex != Index && NlThreadArray[Index] != NULL && NlThreadExitted[Index] ) {
|
||
DWORD WaitStatus;
|
||
NlPrint(( NL_WORKER, "%lx: %lx: Ditching worker thread\n", Index, NlThreadArray[Index]));
|
||
|
||
// Always wait for the thread to exit before closing the handle to
|
||
// ensure the thread has left netlogon.dll before we unload the dll.
|
||
WaitStatus = WaitForSingleObject( NlThreadArray[Index], 0xffffffff );
|
||
if ( WaitStatus != 0 ) {
|
||
NlPrint(( NL_CRITICAL, "%lx: worker thread handle cannot be awaited. %ld 0x%lX\n", Index, WaitStatus, NlThreadArray[Index] ));
|
||
}
|
||
if (!CloseHandle( NlThreadArray[Index] ) ) {
|
||
NlPrint(( NL_CRITICAL, "%lx: worker thread handle cannot be closed. %ld 0x%lX\n", Index, GetLastError(), NlThreadArray[Index] ));
|
||
}
|
||
|
||
NlThreadArray[Index] = NULL;
|
||
NlThreadExitted[Index] = FALSE;
|
||
}
|
||
}
|
||
UNLOCK_WORK_QUEUE();
|
||
|
||
}
|
||
|
||
NlPrint(( NL_WORKER, "%lx: worker thread exitting\n", ThreadIndex ));
|
||
|
||
LOCK_WORK_QUEUE();
|
||
NlThreadExitted[ThreadIndex] = TRUE;
|
||
NlNumberOfCreatedWorkerThreads--;
|
||
UNLOCK_WORK_QUEUE();
|
||
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NlWorkerInitialization(
|
||
VOID
|
||
)
|
||
{
|
||
ULONG ThreadId;
|
||
|
||
NET_API_STATUS NetStatus;
|
||
|
||
//
|
||
// Perform initialization that allows us to call NlWorkerTermination
|
||
//
|
||
|
||
try {
|
||
InitializeCriticalSection( &NlWorkerCritSect );
|
||
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
InitializeListHead( &NlWorkerQueueHead );
|
||
InitializeListHead( &NlWorkerHighQueueHead );
|
||
NlNumberOfCreatedWorkerThreads = 0;
|
||
|
||
|
||
RtlZeroMemory( NlThreadArray, sizeof(NlThreadArray) );
|
||
RtlZeroMemory( NlThreadExitted, sizeof(NlThreadExitted) );
|
||
RtlZeroMemory( NlWorkerThreadStats, sizeof(NlWorkerThreadStats) );
|
||
|
||
NlWorkerInitialized = TRUE;
|
||
|
||
|
||
return NERR_Success;
|
||
}
|
||
|
||
VOID
|
||
NlWorkerKillThreads(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Terminate all worker threads.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
ULONG Index;
|
||
|
||
//
|
||
// Wait for all the threads to exit.
|
||
//
|
||
|
||
for ( Index = 0; Index < NL_MAX_WORKER_THREADS; Index ++ ) {
|
||
if ( NlThreadArray[Index] != NULL ) {
|
||
DWORD WaitStatus;
|
||
NlPrint(( NL_WORKER, "%lx: %lx: Ditching worker thread\n", Index, NlThreadArray[Index]));
|
||
|
||
// Always wait for the thread to exit before closing the handle to
|
||
// ensure the thread has left netlogon.dll before we unload the dll.
|
||
WaitStatus = WaitForSingleObject( NlThreadArray[Index], 0xffffffff );
|
||
if ( WaitStatus != 0 ) {
|
||
NlPrint(( NL_CRITICAL, "%lx: worker thread handle cannot be awaited. %ld 0x%lX\n", Index, WaitStatus, NlThreadArray[Index] ));
|
||
}
|
||
|
||
if (!CloseHandle( NlThreadArray[Index] ) ) {
|
||
NlPrint(( NL_CRITICAL, "%lx: worker thread handle cannot be closed. %ld 0x%lX\n", Index, GetLastError(), NlThreadArray[Index] ));
|
||
}
|
||
NlThreadArray[Index] = NULL;
|
||
NlThreadExitted[Index] = FALSE;
|
||
}
|
||
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
NlWorkerTermination(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Undo initialization of the worker threads.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Status value -
|
||
|
||
--*/
|
||
{
|
||
|
||
//
|
||
// Only cleanup if we've successfully initialized.
|
||
//
|
||
|
||
if ( NlWorkerInitialized ) {
|
||
|
||
//
|
||
//
|
||
// Ensure the threads have been terminated.
|
||
//
|
||
|
||
NlWorkerKillThreads();
|
||
|
||
DeleteCriticalSection( &NlWorkerCritSect );
|
||
NlWorkerInitialized = FALSE;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
BOOL
|
||
NlQueueWorkItem(
|
||
IN PWORKER_ITEM WorkItem,
|
||
IN BOOL InsertNewItem,
|
||
IN BOOL HighPriority
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function modifies the work item queue by either inserting
|
||
a new specified work item to a specified queue or increasing the
|
||
priority of the already inserted item to the highest priority
|
||
in the specified queue.
|
||
|
||
Arguments:
|
||
|
||
WorkItem - Supplies a pointer to the work item to add the the queue.
|
||
It is the caller's responsibility to reclaim the storage occupied by
|
||
the WorkItem structure.
|
||
|
||
InsertNewItem - If TRUE, we are to insert the new item into the queue
|
||
specified by the value of the HighPriority parameter; the new item
|
||
will be inserted at the end of the queue. Otherwise, we are to
|
||
modify (boost) the priority of already inserted work item by moving
|
||
it to the front of the queue specified by the HighPriority value.
|
||
If TRUE and the item is already inserted (as determined by this
|
||
routine), this routine is no-op except for some possible cleanup.
|
||
If FALSE and the item is not already inserted (as determined by
|
||
this routine), this routine is no-op except for some possible cleanup.
|
||
|
||
HighPriority - The queue entry should be processed at a higher priority than
|
||
normal.
|
||
|
||
Return Value:
|
||
|
||
TRUE if item got queued or modified
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG Index;
|
||
|
||
//
|
||
// Ignore this attempt if the worker threads aren't initialized.
|
||
//
|
||
|
||
if ( !NlWorkerInitialized ) {
|
||
NlPrint(( NL_CRITICAL, "NlQueueWorkItem when worker not initialized\n"));
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Ditch any dangling thread handles
|
||
//
|
||
|
||
LOCK_WORK_QUEUE();
|
||
for (Index = 0; Index < NL_MAX_WORKER_THREADS; Index++ ) {
|
||
if ( NlThreadArray[Index] != NULL && NlThreadExitted[Index] ) {
|
||
DWORD WaitStatus;
|
||
NlPrint(( NL_WORKER, "%lx: %lx: Ditching worker thread\n", Index, NlThreadArray[Index]));
|
||
|
||
// Always wait for the thread to exit before closing the handle to
|
||
// ensure the thread has left netlogon.dll before we unload the dll.
|
||
WaitStatus = WaitForSingleObject( NlThreadArray[Index], 0xffffffff );
|
||
if ( WaitStatus != 0 ) {
|
||
NlPrint(( NL_CRITICAL, "%lx: worker thread handle cannot be awaited. %ld 0x%lX\n", Index, WaitStatus, NlThreadArray[Index] ));
|
||
}
|
||
|
||
if (!CloseHandle( NlThreadArray[Index] ) ) {
|
||
NlPrint(( NL_CRITICAL, "%lx: worker thread handle cannot be closed. %ld 0x%lX\n", Index, GetLastError(), NlThreadArray[Index] ));
|
||
}
|
||
|
||
NlThreadArray[Index] = NULL;
|
||
NlThreadExitted[Index] = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// If we are to insert a new work item,
|
||
// do so
|
||
//
|
||
|
||
if ( InsertNewItem ) {
|
||
|
||
//
|
||
// If the work item is already inserted,
|
||
// we are done expect for some possible
|
||
// cleanup
|
||
//
|
||
if ( WorkItem->Inserted ) {
|
||
|
||
//
|
||
// If there is no worker thread processing this
|
||
// work item (i.e. we failed to create a thread
|
||
// at the time this work item was inserted),
|
||
// fall through and retry to create a thread
|
||
// below. Otherwise, we are done.
|
||
//
|
||
if ( NlNumberOfCreatedWorkerThreads > 0 ) {
|
||
UNLOCK_WORK_QUEUE();
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Otherwise, insert this work item
|
||
//
|
||
} else {
|
||
|
||
NlPrint(( NL_WORKER, "Inserting work item %lx (%lx)\n",WorkItem, WorkItem->WorkerRoutine));
|
||
|
||
if ( HighPriority ) {
|
||
InsertTailList( &NlWorkerHighQueueHead, &WorkItem->List );
|
||
} else {
|
||
InsertTailList( &NlWorkerQueueHead, &WorkItem->List );
|
||
}
|
||
WorkItem->Inserted = TRUE;
|
||
}
|
||
|
||
//
|
||
// Otherwise, we are to boost the priority
|
||
// of an already inserted work item
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// If the work item isn't already inserted,
|
||
// we are done
|
||
//
|
||
if ( !WorkItem->Inserted ) {
|
||
UNLOCK_WORK_QUEUE();
|
||
return TRUE;
|
||
|
||
//
|
||
// Otherwise, boost the priority
|
||
//
|
||
} else {
|
||
NlPrint(( NL_WORKER,
|
||
"Boosting %s priority work item %lx (%lx)\n",
|
||
(HighPriority ? "high" : "low"),
|
||
WorkItem,
|
||
WorkItem->WorkerRoutine ));
|
||
|
||
RemoveEntryList( &WorkItem->List );
|
||
if ( HighPriority ) {
|
||
InsertHeadList( &NlWorkerHighQueueHead, &WorkItem->List );
|
||
} else {
|
||
InsertHeadList( &NlWorkerQueueHead, &WorkItem->List );
|
||
}
|
||
|
||
//
|
||
// If there is no worker thread processing this
|
||
// work item (i.e. we failed to create a thread
|
||
// at the time this work item was inserted),
|
||
// fall through and retry to create a thread
|
||
// below. Otherwise, we are done.
|
||
//
|
||
if ( NlNumberOfCreatedWorkerThreads > 0 ) {
|
||
UNLOCK_WORK_QUEUE();
|
||
return TRUE;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// If there isn't a worker thread to handle the request,
|
||
// create one now.
|
||
//
|
||
|
||
if ( NlNumberOfCreatedWorkerThreads < NL_MAX_WORKER_THREADS ) {
|
||
|
||
//
|
||
// Find a spot for the thread handle.
|
||
//
|
||
|
||
for (Index = 0; Index < NL_MAX_WORKER_THREADS; Index++ ) {
|
||
if ( NlThreadArray[Index] == NULL ) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
if ( Index >= NL_MAX_WORKER_THREADS ) {
|
||
NlPrint(( NL_CRITICAL, "NlQueueWorkItem: Internal Error\n" ));
|
||
UNLOCK_WORK_QUEUE();
|
||
return FALSE;
|
||
} else {
|
||
DWORD ThreadId;
|
||
NlThreadArray[Index] = CreateThread(
|
||
NULL, // No security attributes
|
||
0,
|
||
(LPTHREAD_START_ROUTINE)NlWorkerThread,
|
||
(PVOID) ULongToPtr( Index ),
|
||
0, // No special creation flags
|
||
&ThreadId );
|
||
|
||
NlPrint(( NL_WORKER, "%lx: %lx: %lx: Starting worker thread\n", Index, NlThreadArray[Index], ThreadId ));
|
||
|
||
//
|
||
// Note that if we fail to create a thread,
|
||
// the work item remains queued and possibly not processed.
|
||
// This is not critical because the item will be processed
|
||
// next time a work item gets queued which will happen at
|
||
// the next scavenging time at latest.
|
||
//
|
||
if (NlThreadArray[Index] == NULL) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlQueueWorkItem: Cannot create thread %ld\n", GetLastError() ));
|
||
} else {
|
||
NlNumberOfCreatedWorkerThreads++;
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|
||
UNLOCK_WORK_QUEUE();
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
#ifdef notdef // Don't need timers yet
|
||
NET_API_STATUS
|
||
NlCreateTimer(
|
||
IN PNlOWSER_TIMER Timer
|
||
)
|
||
{
|
||
OBJECT_ATTRIBUTES ObjA;
|
||
NTSTATUS Status;
|
||
|
||
InitializeObjectAttributes(&ObjA, NULL, 0, NULL, NULL);
|
||
|
||
Status = NtCreateTimer(&Timer->TimerHandle,
|
||
TIMER_ALL_ACCESS,
|
||
&ObjA,
|
||
NotificationTimer);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint(( NL_CRITICAL, "Failed to create timer %lx: %X\n", Timer, Status));
|
||
return(NlMapStatus(Status));
|
||
}
|
||
|
||
NlPrint(( Nl_TIMER, "Creating timer %lx: Handle: %lx\n", Timer, Timer->TimerHandle));
|
||
|
||
return(NERR_Success);
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NlDestroyTimer(
|
||
IN PNlOWSER_TIMER Timer
|
||
)
|
||
{
|
||
HANDLE Handle;
|
||
|
||
//
|
||
// Avoid destroying a timer twice.
|
||
//
|
||
|
||
if ( Timer->TimerHandle == NULL ) {
|
||
return NERR_Success;
|
||
}
|
||
|
||
// Closing doesn't automatically cancel the timer.
|
||
(VOID) NlCancelTimer( Timer );
|
||
|
||
//
|
||
// Close the handle and prevent future uses.
|
||
//
|
||
|
||
Handle = Timer->TimerHandle;
|
||
Timer->TimerHandle = NULL;
|
||
|
||
NlPrint(( Nl_TIMER, "Destroying timer %lx\n", Timer));
|
||
return NlMapStatus(NtClose(Handle));
|
||
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NlCancelTimer(
|
||
IN PNlOWSER_TIMER Timer
|
||
)
|
||
{
|
||
//
|
||
// Avoid cancelling a destroyed timer.
|
||
//
|
||
|
||
if ( Timer->TimerHandle == NULL ) {
|
||
NlPrint(( Nl_TIMER, "Canceling destroyed timer %lx\n", Timer));
|
||
return NERR_Success;
|
||
}
|
||
|
||
NlPrint(( Nl_TIMER, "Canceling timer %lx\n", Timer));
|
||
return NlMapStatus(NtCancelTimer(Timer->TimerHandle, NULL));
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NlSetTimer(
|
||
IN PNlOWSER_TIMER Timer,
|
||
IN ULONG MillisecondsToExpire,
|
||
IN PNlOWSER_WORKER_ROUTINE WorkerFunction,
|
||
IN PVOID Context
|
||
)
|
||
{
|
||
LARGE_INTEGER TimerDueTime;
|
||
NTSTATUS NtStatus;
|
||
//
|
||
// Avoid setting a destroyed timer.
|
||
//
|
||
|
||
if ( Timer->TimerHandle == NULL ) {
|
||
NlPrint(( Nl_TIMER, "Setting a destroyed timer %lx\n", Timer));
|
||
return NERR_Success;
|
||
}
|
||
|
||
NlPrint(( Nl_TIMER, "Setting timer %lx to %ld milliseconds, WorkerFounction %lx, Context: %lx\n", Timer, MillisecondsToExpire, WorkerFunction, Context));
|
||
|
||
//
|
||
// Figure out the timeout.
|
||
//
|
||
|
||
TimerDueTime.QuadPart = Int32x32To64( MillisecondsToExpire, -10000 );
|
||
|
||
NlInitializeWorkItem(&Timer->WorkItem, WorkerFunction, Context);
|
||
|
||
//
|
||
// Set the timer to go off when it expires.
|
||
//
|
||
|
||
NtStatus = NtSetTimer(Timer->TimerHandle,
|
||
&TimerDueTime,
|
||
NlTimerRoutine,
|
||
Timer,
|
||
FALSE,
|
||
0,
|
||
NULL
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
#if DBG
|
||
NlPrint(( NL_CRITICAL, "Unable to set Netlogon timer expiration: %X (%lx)\n", NtStatus, Timer));
|
||
DbgbreakPoint();
|
||
#endif
|
||
|
||
return(NlMapStatus(NtStatus));
|
||
}
|
||
|
||
return NERR_Success;
|
||
|
||
|
||
}
|
||
|
||
VOID
|
||
NlTimerRoutine(
|
||
IN PVOID TimerContext,
|
||
IN ULONG TImerLowValue,
|
||
IN LONG TimerHighValue
|
||
)
|
||
{
|
||
PNlOWSER_TIMER Timer = TimerContext;
|
||
|
||
NlPrint(( Nl_TIMER, "Timer %lx fired\n", Timer));
|
||
|
||
NlQueueWorkItem(&Timer->WorkItem);
|
||
}
|
||
#endif // notdef // Don't need timers yet
|