windows-nt/Source/XPSP1/NT/ds/netapi/svcdlls/browser/server/bowqueue.c
2020-09-26 16:20:57 +08:00

780 lines
18 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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();
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++;
}
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 ++;
}
}
}
}
else
{
EnterCriticalSection( &BrAsyncIOCriticalSection );
BrDgWorkerThreadsOutstanding++;
LeaveCriticalSection( &BrAsyncIOCriticalSection );
}
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;
ASSERT (!IsListEmpty(&BrWorkerQueueHead));
if (!IsListEmpty(&BrWorkerQueueHead)) {
WorkItem = (PWORKER_ITEM)RemoveHeadList( &BrWorkerQueueHead );
ASSERT (WorkItem->Inserted);
WorkItem->Inserted = FALSE;
} else {
WorkItem = NULL;
}
UNLOCK_WORK_QUEUE();
BrPrint(( BR_QUEUE, "%lx: Pulling off work item %lx (%lx)\n", StartContext, WorkItem, WorkItem->WorkerRoutine));
//
// 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();
}