|
|
/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
bowqueue.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:
--*/
#include "precomp.h"
#pragma hdrstop
// defines
// Thread start definition helpers. Taken from article in URL below.
// mk:@MSITStore:\\INFOSRV2\MSDN_OCT99\MSDN\period99.chm::/html/msft/msj/0799/win32/win320799.htm
//
typedef unsigned (__stdcall *PTHREAD_START) (void *); #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \
pvParam, fdwCreate, pdwThreadID) \ ((HANDLE) _beginthreadex( \ (void *) (psa), \ (unsigned) (cbStack), \ (PTHREAD_START) (pfnStartAddr), \ (void *) (pvParam), \ (unsigned) (fdwCreate), \ (unsigned *) (pdwThreadID)))
//
// Limit the number of created worker threads.
//
// This count doesn't include the main thread.
//
#define BR_MAX_NUMBER_OF_WORKER_THREADS 10
ULONG BrNumberOfCreatedWorkerThreads = 0;
ULONG BrNumberOfActiveWorkerThreads = 0;
//
// Usage count array for determining how often each thread is used.
//
// Allow for the main thread.
//
ULONG BrWorkerThreadCount[BR_MAX_NUMBER_OF_WORKER_THREADS+1];
//
// Handles of created worker threads.
//
PHANDLE BrThreadArray[BR_MAX_NUMBER_OF_WORKER_THREADS];
//
// CritSect guard the WorkQueue list.
//
CRITICAL_SECTION BrWorkerCritSect; BOOL BrWorkerCSInitialized = FALSE;
#define LOCK_WORK_QUEUE() EnterCriticalSection(&BrWorkerCritSect);
#define UNLOCK_WORK_QUEUE() LeaveCriticalSection(&BrWorkerCritSect);
//
// Head of singly linked list of work items queued to the worker thread.
//
LIST_ENTRY BrWorkerQueueHead = {0};
//
// Event that is signal whenever a work item is put in the queue. The
// worker thread waits on this event.
//
HANDLE BrWorkerSemaphore = NULL;
//
// Synchronization mechanisms for shutdown
//
extern HANDLE BrDgAsyncIOShutDownEvent; extern HANDLE BrDgAsyncIOThreadShutDownEvent; extern BOOL BrDgShutDownInitiated; extern DWORD BrDgAsyncIOsOutstanding; extern DWORD BrDgWorkerThreadsOutstanding; extern CRITICAL_SECTION BrAsyncIOCriticalSection;
VOID BrTimerRoutine( IN PVOID TimerContext, IN ULONG TImerLowValue, IN LONG TimerHighValue );
NET_API_STATUS BrWorkerInitialization( VOID ) { ULONG Index; NET_API_STATUS NetStatus;
try { //
// Perform initialization that allows us to call BrWorkerTermination
//
try{ InitializeCriticalSection( &BrWorkerCritSect ); } except ( EXCEPTION_EXECUTE_HANDLER ) { return NERR_NoNetworkResource; } BrWorkerCSInitialized = TRUE;
InitializeListHead( &BrWorkerQueueHead ); BrNumberOfCreatedWorkerThreads = 0; BrNumberOfActiveWorkerThreads = 0;
//
// Initialize the work queue semaphore.
//
BrWorkerSemaphore = CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
if (BrWorkerSemaphore == NULL) { try_return ( NetStatus = GetLastError() ); }
NetStatus = NERR_Success;
//
// Done
//
try_exit:NOTHING; } finally {
if (NetStatus != NERR_Success) { (VOID) BrWorkerTermination(); } }
return NetStatus; }
VOID BrWorkerCreateThread( ULONG NetworkCount )
/*++
Routine Description:
Ensure there are enough worker threads to handle the current number of networks.
Worker threads are created but are never deleted until the browser terminates. Each worker thread has pending I/O. We don't keep track of which thread has which I/O pending. Thus, we can't delete any threads.
Arguments:
NetworkCount - Current number of networks.
Return Value:
None.
--*/ { ULONG ThreadId;
//
// Create 1 thread for every 2 networks.
// (round up)
LOCK_WORK_QUEUE(); EnterCriticalSection( &BrAsyncIOCriticalSection );
while ( BrNumberOfCreatedWorkerThreads < (NetworkCount+1)/2 && BrNumberOfCreatedWorkerThreads < BR_MAX_NUMBER_OF_WORKER_THREADS ) {
BrThreadArray[BrNumberOfCreatedWorkerThreads] = chBEGINTHREADEX(NULL, // CreateThread
0, (LPTHREAD_START_ROUTINE)BrWorkerThread, ULongToPtr(BrNumberOfCreatedWorkerThreads), 0, &ThreadId );
if (BrThreadArray[BrNumberOfCreatedWorkerThreads] == NULL) { break; }
//
// Set the browser threads to time critical priority.
//
SetThreadPriority(BrThreadArray[BrNumberOfCreatedWorkerThreads], THREAD_PRIORITY_ABOVE_NORMAL);
//
// Indicate we now have another thread.
//
BrNumberOfCreatedWorkerThreads++;
BrDgWorkerThreadsOutstanding++;
} LeaveCriticalSection( &BrAsyncIOCriticalSection );
UNLOCK_WORK_QUEUE(); }
VOID BrWorkerKillThreads( VOID )
/*++
Routine Description:
Terminate all worker threads.
Arguments:
None.
Return Value:
None.
--*/ { ULONG Index; HANDLE ThreadHandle;
//
// Make sure the terminate now event is in the signalled state to unwind
// all our threads.
//
SetEvent( BrGlobalData.TerminateNowEvent );
//
// Loop waiting for all the threads to stop.
//
LOCK_WORK_QUEUE(); for ( Index = 0 ; Index < BrNumberOfCreatedWorkerThreads ; Index += 1 ) { if ( BrThreadArray[Index] != NULL ) { ThreadHandle = BrThreadArray[Index]; UNLOCK_WORK_QUEUE();
WaitForSingleObject( ThreadHandle, 0xffffffff ); CloseHandle( ThreadHandle );
LOCK_WORK_QUEUE(); BrThreadArray[Index] = NULL; }
} UNLOCK_WORK_QUEUE();
return; }
NET_API_STATUS BrWorkerTermination( VOID )
/*++
Routine Description:
Undo initialization of the worker threads.
Arguments:
None.
Return Value:
Status value -
--*/ { //
// Ensure the threads have been terminated.
//
BrWorkerKillThreads();
if ( BrWorkerSemaphore != NULL ) { CloseHandle( BrWorkerSemaphore );
BrWorkerSemaphore = NULL; }
BrNumberOfActiveWorkerThreads = 0; BrNumberOfCreatedWorkerThreads = 0;
//
// BrWorkerCSInit is set upon successfull CS initialization.
// (see BrWorkerInitialization)
//
if ( BrWorkerCSInitialized ) { DeleteCriticalSection( &BrWorkerCritSect ); }
return NERR_Success; }
VOID BrQueueWorkItem( IN PWORKER_ITEM WorkItem )
/*++
Routine Description:
This function queues a work item to a queue that is processed by a worker thread. This thread runs at low priority, at IRQL 0
Arguments:
WorkItem - Supplies a pointer to the work item to add the the queue. This structure must be located in NonPagedPool. The work item structure contains a doubly linked list entry, the address of a routine to call and a parameter to pass to that routine. It is the routine's responsibility to reclaim the storage occupied by the WorkItem structure.
Return Value:
Status value -
--*/
{ //
// Acquire the worker thread spinlock and insert the work item in the
// list and release the worker thread semaphore if the work item is
// not already in the list.
//
LOCK_WORK_QUEUE();
if (WorkItem->Inserted == FALSE) {
BrPrint(( BR_QUEUE, "Inserting work item %lx (%lx)\n",WorkItem, WorkItem->WorkerRoutine));
InsertTailList( &BrWorkerQueueHead, &WorkItem->List );
WorkItem->Inserted = TRUE;
ReleaseSemaphore( BrWorkerSemaphore, 1, NULL ); }
UNLOCK_WORK_QUEUE();
return; }
VOID BrWorkerThread( IN PVOID StartContext )
{ NET_API_STATUS NetStatus;
#define WORKER_SIGNALED 0
#define TERMINATION_SIGNALED 1
#define REG_CHANGE_SIGNALED 2
#define NUMBER_OF_EVENTS 3
HANDLE WaitList[NUMBER_OF_EVENTS]; ULONG WaitCount = 0;
PWORKER_ITEM WorkItem; ULONG ThreadIndex = PtrToUlong(StartContext);
HKEY RegistryHandle = NULL; HANDLE EventHandle = NULL;
WaitList[WORKER_SIGNALED] = BrWorkerSemaphore; WaitCount ++; WaitList[TERMINATION_SIGNALED] = BrGlobalData.TerminateNowEvent; WaitCount ++;
//
// Primary thread waits on registry changes, too.
//
if ( ThreadIndex == 0xFFFFFFFF ) { DWORD RegStatus; NET_API_STATUS NetStatus;
//
// Register for notifications of changes to Parameters
//
// Failure doesn't affect normal operation of the browser.
//
RegStatus = RegOpenKeyExA( HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\Browser\\Parameters", 0, KEY_NOTIFY, &RegistryHandle );
if ( RegStatus != ERROR_SUCCESS ) { BrPrint(( BR_CRITICAL, "BrWorkerThead: Can't RegOpenKey %ld\n", RegStatus )); } else {
EventHandle = CreateEvent( NULL, // No security attributes
TRUE, // Automatically reset
FALSE, // Initially not signaled
NULL ); // No name
if ( EventHandle == NULL ) { BrPrint(( BR_CRITICAL, "BrWorkerThead: Can't CreateEvent %ld\n", GetLastError() )); } else { NetStatus = RegNotifyChangeKeyValue( RegistryHandle, FALSE, // Ignore subkeys
REG_NOTIFY_CHANGE_LAST_SET, // Notify of value changes
EventHandle, TRUE ); // Signal event upon change
if ( NetStatus != NERR_Success ) { BrPrint(( BR_CRITICAL, "BrWorkerThead: Can't RegNotifyChangeKeyValue %ld\n", NetStatus )); } else { WaitList[REG_CHANGE_SIGNALED] = EventHandle; WaitCount ++; } } } }
BrPrint(( BR_QUEUE, "Starting new work thread, Context: %lx\n", StartContext));
//
// Set the thread priority to the lowest realtime level.
//
while( TRUE ) { ULONG WaitItem;
//
// Wait until something is put in the queue (semaphore is
// released), remove the item from the queue, mark it not
// inserted, and execute the specified routine.
//
BrPrint(( BR_QUEUE, "%lx: worker thread waiting\n", StartContext));
do { WaitItem = WaitForMultipleObjectsEx( WaitCount, WaitList, FALSE, 0xffffffff, TRUE ); } while ( WaitItem == WAIT_IO_COMPLETION );
if (WaitItem == 0xffffffff) { BrPrint(( BR_CRITICAL, "WaitForMultipleObjects in browser queue returned %ld\n", GetLastError())); break; }
if (WaitItem == TERMINATION_SIGNALED) { break;
//
// If the registry has changed,
// process the changes.
//
} else if ( WaitItem == REG_CHANGE_SIGNALED ) {
//
// Setup for future notifications.
//
NetStatus = RegNotifyChangeKeyValue( RegistryHandle, FALSE, // Ignore subkeys
REG_NOTIFY_CHANGE_LAST_SET, // Notify of value changes
EventHandle, TRUE ); // Signal event upon change
if ( NetStatus != NERR_Success ) { BrPrint(( BR_CRITICAL, "BrWorkerThead: Can't RegNotifyChangeKeyValue %ld\n", NetStatus )); }
NetStatus = BrReadBrowserConfigFields( FALSE );
if ( NetStatus != NERR_Success) { BrPrint(( BR_CRITICAL, "BrWorkerThead: Can't BrReadConfigFields %ld\n", NetStatus )); }
continue;
}
BrPrint(( BR_QUEUE, "%lx: Worker thread waking up\n", StartContext));
LOCK_WORK_QUEUE();
BrWorkerThreadCount[BrNumberOfActiveWorkerThreads++] += 1;
if (!IsListEmpty(&BrWorkerQueueHead)) { WorkItem = (PWORKER_ITEM)RemoveHeadList( &BrWorkerQueueHead );
ASSERT (WorkItem->Inserted);
WorkItem->Inserted = FALSE;
} else { WorkItem = NULL; }
UNLOCK_WORK_QUEUE();
//
// Execute the specified routine.
//
if (WorkItem != NULL) { (WorkItem->WorkerRoutine)( WorkItem->Parameter ); }
LOCK_WORK_QUEUE(); BrNumberOfActiveWorkerThreads--; UNLOCK_WORK_QUEUE();
}
BrPrint(( BR_QUEUE, "%lx: worker thread exitting\n", StartContext));
if ( ThreadIndex != 0xFFFFFFFF ) { IO_STATUS_BLOCK IoSb; DWORD waitResult; BOOL SetThreadEvent = FALSE;
//
// Cancel the I/O operations outstanding on the browser.
// Then wait for the shutdown event to be signalled, but allow
// APC's to be called to call our completion routine.
//
NtCancelIoFile(BrDgReceiverDeviceHandle, &IoSb);
do { waitResult = WaitForSingleObjectEx(BrDgAsyncIOShutDownEvent,0xffffffff, TRUE); } while( waitResult == WAIT_IO_COMPLETION );
EnterCriticalSection( &BrAsyncIOCriticalSection );
BrDgWorkerThreadsOutstanding--; if( BrDgWorkerThreadsOutstanding == 0 ) { SetThreadEvent = TRUE; }
LeaveCriticalSection( &BrAsyncIOCriticalSection );
if( SetThreadEvent ) { SetEvent( BrDgAsyncIOThreadShutDownEvent ); }
} else { if( RegistryHandle ) CloseHandle( RegistryHandle ); if( EventHandle ) CloseHandle( EventHandle ); }
}
NET_API_STATUS BrCreateTimer( IN PBROWSER_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)) { BrPrint(( BR_CRITICAL, "Failed to create timer %lx: %X\n", Timer, Status)); return(BrMapStatus(Status)); }
BrPrint(( BR_TIMER, "Creating timer %lx: Handle: %lx\n", Timer, Timer->TimerHandle));
return(NERR_Success); }
NET_API_STATUS BrDestroyTimer( IN PBROWSER_TIMER Timer ) { HANDLE Handle;
//
// Avoid destroying a timer twice.
//
if ( Timer->TimerHandle == NULL ) { return NERR_Success; }
// Closing doesn't automatically cancel the timer.
(VOID) BrCancelTimer( Timer );
//
// Close the handle and prevent future uses.
//
Handle = Timer->TimerHandle; Timer->TimerHandle = NULL;
BrPrint(( BR_TIMER, "Destroying timer %lx\n", Timer)); return BrMapStatus(NtClose(Handle));
}
NET_API_STATUS BrCancelTimer( IN PBROWSER_TIMER Timer ) { //
// Avoid cancelling a destroyed timer.
//
if ( Timer->TimerHandle == NULL ) { BrPrint(( BR_TIMER, "Canceling destroyed timer %lx\n", Timer)); return NERR_Success; }
BrPrint(( BR_TIMER, "Canceling timer %lx\n", Timer)); return BrMapStatus(NtCancelTimer(Timer->TimerHandle, NULL)); }
NET_API_STATUS BrSetTimer( IN PBROWSER_TIMER Timer, IN ULONG MillisecondsToExpire, IN PBROWSER_WORKER_ROUTINE WorkerFunction, IN PVOID Context ) { LARGE_INTEGER TimerDueTime; NTSTATUS NtStatus; //
// Avoid setting a destroyed timer.
//
if ( Timer->TimerHandle == NULL ) { BrPrint(( BR_TIMER, "Setting a destroyed timer %lx\n", Timer)); return NERR_Success; }
BrPrint(( BR_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 );
BrInitializeWorkItem(&Timer->WorkItem, WorkerFunction, Context);
//
// Set the timer to go off when it expires.
//
NtStatus = NtSetTimer(Timer->TimerHandle, &TimerDueTime, BrTimerRoutine, Timer, FALSE, 0, NULL );
if (!NT_SUCCESS(NtStatus)) { #if DBG
BrPrint(( BR_CRITICAL, "Unable to set browser timer expiration: %X (%lx)\n", NtStatus, Timer)); DbgBreakPoint(); #endif
return(BrMapStatus(NtStatus)); }
return NERR_Success;
}
VOID BrTimerRoutine( IN PVOID TimerContext, IN ULONG TImerLowValue, IN LONG TimerHighValue ) { PBROWSER_TIMER Timer = TimerContext;
BrPrint(( BR_TIMER, "Timer %lx fired\n", Timer));
BrQueueWorkItem(&Timer->WorkItem); }
VOID BrInitializeWorkItem( IN PWORKER_ITEM Item, IN PBROWSER_WORKER_ROUTINE Routine, IN PVOID Context) /*++
Routine Description:
Initializes fields in Item under queue lock
Arguments:
Item -- worker item to init Routine -- routine to set Context -- work context to set
Return Value: none.
--*/ { LOCK_WORK_QUEUE();
Item->WorkerRoutine = Routine; Item->Parameter = Context; Item->Inserted = FALSE;
UNLOCK_WORK_QUEUE(); }
|