/*++

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();
}