text: /*++
  
  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();
  }
  
  
  
  