/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    tpswork.cpp

Abstract:

    Contains Win32 thread pool services worker thread functions

    Contents:
        SHSetThreadPoolLimits
        SHTerminateThreadPool
        SHQueueUserWorkItem
        SHCancelUserWorkItems
        TerminateWorkers
        TpsEnter
        (InitializeWorkerThreadPool)
        (StartIOWorkerThread)
        (QueueIOWorkerRequest)
        (IOWorkerThread)
        (ExecuteIOWorkItem)
        (CThreadPool::WorkerThread)
        (CThreadPool::Worker)

Author:

    Richard L Firth (rfirth) 10-Feb-1998

Environment:

    Win32 user-mode

Revision History:

    10-Feb-1998 rfirth
        Created

    12-Aug-1998 rfirth
        Rewritten for DEMANDTHREAD and LONGEXEC work items. Officially
        divergent from original which was based on NT5 base thread pool API

--*/

#include "priv.h"
#include "threads.h"
#include "tpsclass.h"
#include "tpswork.h"

//
// private prototypes
//
typedef BOOL (WINAPI * t_QueueUserWorkItem)(LPTHREAD_START_ROUTINE, LPVOID, BOOL);

struct WorkItem {
    LPTHREAD_START_ROUTINE  pfnCallback;
    LPVOID                  pContext;
    HMODULE                 hModuleToFree;
};


DWORD
InitializeWorkerThreadPool(
    VOID
    );

PRIVATE
DWORD
StartIOWorkerThread(
    VOID
    );

PRIVATE
DWORD
QueueIOWorkerRequest(
    IN LPTHREAD_START_ROUTINE pfnCallback,
    IN LPVOID pContext
    );

PRIVATE
VOID
IOWorkerThread(
    IN HANDLE hEvent
    );

PRIVATE
VOID
ExecuteIOWorkItem(
    IN CIoWorkerRequest * pPacket
    );


PRIVATE
VOID
ExecuteWorkItem(
    IN WorkItem *pItem
    );

//
// global data
//

BOOL g_StartedWorkerInitialization = FALSE;
BOOL g_CompletedWorkerInitialization = FALSE;
BOOL g_bTpsTerminating = FALSE;

DWORD g_NumIoWorkerThreads = 0;
DWORD g_NumIoWorkRequests = 0;
DWORD g_LastIoThreadCreationTickCount = 0;
DWORD g_MaximumIoThreads = MAX_IO_WORKER_THREADS;
DWORD g_MaximumIoQueueDepth = NEW_THREAD_THRESHOLD;
DWORD g_ThreadCreationDelta = THREAD_CREATION_DAMPING_TIME;

CDoubleLinkedList g_IoWorkerThreads;
CCriticalSection_NoCtor g_IoWorkerCriticalSection;
CThreadPool g_ThreadPool;
DWORD g_dwWorkItemId = 0;

const char g_cszShlwapi[] = "SHLWAPI.DLL";

DWORD g_ActiveRequests = 0;
DWORD g_dwTerminationThreadId = 0;
BOOL g_bDeferredWorkerTermination = FALSE;



//
// functions
//

LWSTDAPI_(BOOL)
SHSetThreadPoolLimits(
    IN PSH_THREAD_POOL_LIMITS pLimits
    )

/*++

Routine Description:

    Change internal settings

Arguments:

    pLimits - pointer to SH_THREAD_POOL_LIMITS structure containing limits
              to set

Return Value:

    BOOL
        Success - TRUE

        Failure - FALSE. See GetLastError() for more info

--*/

{
    if (!pLimits || (pLimits->dwStructSize != sizeof(SH_THREAD_POOL_LIMITS))) {
        return ERROR_INVALID_PARAMETER;
    }

    BOOL success = FALSE;
    DWORD error = ERROR_SHUTDOWN_IN_PROGRESS; // error code? look valid -justmann

    if (!g_bTpsTerminating) {
        InterlockedIncrement((LPLONG)&g_ActiveRequests);
        if (!g_bTpsTerminating) {
            error = ERROR_SUCCESS;
            if (!g_CompletedWorkerInitialization) {
                error = InitializeWorkerThreadPool();
            }
            if (error == ERROR_SUCCESS) {
                g_ThreadPool.SetLimits(pLimits->dwMinimumWorkerThreads,
                                       pLimits->dwMaximumWorkerThreads,
                                       pLimits->dwMaximumWorkerQueueDepth,
                                       pLimits->dwWorkerThreadIdleTimeout,
                                       pLimits->dwWorkerThreadCreationDelta
                                       );
                g_MaximumIoThreads = pLimits->dwMaximumIoWorkerThreads;
                g_MaximumIoQueueDepth = pLimits->dwMaximumIoWorkerQueueDepth;
                g_ThreadCreationDelta = pLimits->dwIoWorkerThreadCreationDelta;
                success = TRUE;
            }
        }
        InterlockedDecrement((LPLONG)&g_ActiveRequests);
    }
    if (success) {
        return success;
    }
    SetLastError(error);
    return success;
}

LWSTDAPI_(BOOL)
SHTerminateThreadPool(
    VOID
    )

/*++

Routine Description:

    Required to clean up threads before unloading SHLWAPI

Arguments:

    None.

Return Value:

    BOOL
        Success - TRUE

        Failure - FALSE

--*/

{
    if (InterlockedExchange((PLONG)&g_bTpsTerminating, TRUE)) {
        return TRUE;
    }

    //
    // wait until all in-progress requests have finished
    //

    while (g_ActiveRequests != 0) {
        SleepEx(0, FALSE);
    }

    //
    // kill all I/O worker threads. Queued work items will be lost
    //

    TerminateWorkers();

    //
    // kill all timer threads
    //

    TerminateTimers();

    //
    // kill all wait threads
    //

    TerminateWaiters();

    if (!g_bDeferredWorkerTermination
        && !g_bDeferredTimerTermination
        && !g_bDeferredWaiterTermination) {
        g_dwTerminationThreadId = 0;
        g_bTpsTerminating = FALSE;
    } else {
        g_dwTerminationThreadId = GetCurrentThreadId();
    }
    return TRUE;
}

LWSTDAPI_(BOOL)
SHQueueUserWorkItem(
    IN LPTHREAD_START_ROUTINE pfnCallback,
    IN LPVOID pContext,
    IN LONG lPriority,
    IN DWORD_PTR dwTag,
    OUT DWORD_PTR * pdwId OPTIONAL,
    IN LPCSTR pszModule OPTIONAL,
    IN DWORD dwFlags
    )

/*++

Routine Description:

    Queues a work item and associates a user-supplied tag for use by
    SHCancelUserWorkItems()

    N.B. IO work items CANNOT be cancelled due to the fact that they are
    queued as APCs and there is no OS support to revoke an APC

Arguments:

    pfnCallback - caller-supplied function to call

    pContext    - caller-supplied context argument to pfnCallback

    lPriority   - relative priority of non-IO work item. Default is 0

    dwTag       - caller-supplied tag for non-IO work item if TPS_TAGGEDITEM

    pdwId       - pointer to returned ID. Pass NULL if not required. ID will be
                  0 for an IO work item

    pszModule   - if specified, name of library (DLL) to load and free so that
                  the dll will reamin in our process for the lifetime of the work
                  item.

    dwFlags     - flags modifying request:

                    TPS_EXECUTEIO
                        - execute work item in I/O thread. If set, work item
                          cannot be tagged (and therefore cannot be subsequently
                          cancelled) and cannot have an associated priority
                          (both tag and priority are ignored for I/O work items)

                    TPS_TAGGEDITEM
                        - the dwTag field is meaningful

                    TPS_DEMANDTHREAD
                        - a thread will be created for this work item if one is
                          not currently available. DEMANDTHREAD work items are
                          queued at the head of the work queue. That is, they
                          get the highest priority

                    TPS_LONGEXECTIME
                        - caller expects this work item to take relatively long
                          time to complete (e.g. it could be in a UI loop). Work
                          items thus described remove a thread from the pool for
                          an indefinite amount of time

Return Value:

    BOOL
        Success - TRUE

        Failure - FALSE. See GetLastError() for more info

--*/

{
    DWORD error;
    static t_QueueUserWorkItem pfQueueUserWorkItem = NULL;

    if (dwFlags & TPS_INVALID_FLAGS) {
        error = ERROR_INVALID_PARAMETER;
        goto exit;
    }

    error = TpsEnter();
    if (error != ERROR_SUCCESS) {
        goto exit;
    }

    if (!g_CompletedWorkerInitialization) {
        error = InitializeWorkerThreadPool();
        if (error != ERROR_SUCCESS) {
            goto leave;
        }
    }

    if (!pfQueueUserWorkItem && IsOS(OS_WHISTLERORGREATER))
    {
        HMODULE hKernel32 = GetModuleHandle("KERNEL32.DLL");
        if (hKernel32)
        {
            pfQueueUserWorkItem = (t_QueueUserWorkItem)GetProcAddress(hKernel32, "QueueUserWorkItem");
        }
    }

    if (!(dwFlags & TPS_EXECUTEIO)) 
    {
        if (pfQueueUserWorkItem)
        {
            // Use NT Thread pool!

            WorkItem *pItem = new WorkItem;
            if (pItem)
            {
                pItem->pfnCallback = pfnCallback;
                pItem->pContext = pContext;
                if (pszModule && *pszModule)
                {
                    pItem->hModuleToFree = LoadLibrary(pszModule);
                }
                ULONG uFlags = WT_EXECUTEDEFAULT;
                if (dwFlags & TPS_LONGEXECTIME)
                    uFlags |= WT_EXECUTELONGFUNCTION;

                error = ERROR_SUCCESS;
                if (!pfQueueUserWorkItem((LPTHREAD_START_ROUTINE)ExecuteWorkItem, (PVOID)pItem, uFlags))
                {
                    error = GetLastError();
                    if (pItem->hModuleToFree)
                        FreeLibrary(pItem->hModuleToFree);

                    delete pItem;
                }
            } 
            else 
            {
                error = ERROR_NOT_ENOUGH_MEMORY;
            }
        }
        else
        {
            error = g_ThreadPool.QueueWorkItem((FARPROC)pfnCallback,
                                               (ULONG_PTR)pContext,
                                               lPriority,
                                               dwTag,
                                               pdwId,
                                               pszModule,
                                               dwFlags
                                               );
        }
    } 
    else 
    {

        DWORD threshold = (g_NumIoWorkerThreads < g_MaximumIoThreads)
                        ? (g_MaximumIoQueueDepth * g_NumIoWorkerThreads)
                        : 0xffffffff;

        g_IoWorkerCriticalSection.Acquire();

        if ((g_NumIoWorkerThreads == 0)
            || ((g_NumIoWorkRequests > threshold)
            && (g_LastIoThreadCreationTickCount + g_ThreadCreationDelta
                < GetTickCount()))) {
            error = StartIOWorkerThread();
        }
        if (error == ERROR_SUCCESS) {
            error = QueueIOWorkerRequest(pfnCallback, pContext);
        }

        g_IoWorkerCriticalSection.Release();

        if (pdwId != NULL) {
            *pdwId = (DWORD_PTR)NULL;
        }
    }

leave:

    TpsLeave();

exit:

    BOOL success = TRUE;

    if (error != ERROR_SUCCESS) {
        SetLastError(error);
        success = FALSE;
    }
    return success;
}

LWSTDAPI_(DWORD)
SHCancelUserWorkItems(
    IN DWORD_PTR dwTagOrId,
    IN BOOL bTag
    )

/*++

Routine Description:

    Cancels one or more queued work items. By default, if ID is supplied, only
    one work item can be cancelled. If tag is supplied, all work items with same
    tag will be deleted

Arguments:

    dwTagOrId   - user-supplied tag or API-supplied ID of work item(s) to
                  cancel. Used as search key

    bTag        - TRUE if dwTagOrId is tag else ID

Return Value:

    DWORD
        Success - Number of work items successfully cancelled (0..0xFFFFFFFE)

        Failure - 0xFFFFFFFF. Use GetLastError() for more info

                    ERROR_SHUTDOWN_IN_PROGRESS
                        - DLL being unloaded/support terminated

--*/

{
    DWORD error = TpsEnter();
    DWORD result = 0xFFFFFFFF;

    if (error == ERROR_SUCCESS) {
        if (g_CompletedWorkerInitialization) {
            result = g_ThreadPool.RemoveTagged(dwTagOrId, bTag);
        }
        TpsLeave();
    }
    if (result != 0xFFFFFFFF) {
        return result;
    }
    SetLastError(error);
    return result;
}

VOID
TerminateWorkers(
    VOID
    )

/*++

Routine Description:

    Terminate worker threads

Arguments:

    None.

Return Value:

    None.

--*/

{
    if (g_CompletedWorkerInitialization) {
        g_IoWorkerCriticalSection.Acquire();
        while (!g_IoWorkerThreads.IsEmpty()) {

            CIoWorkerThreadInfo * pInfo;

            pInfo = (CIoWorkerThreadInfo *)g_IoWorkerThreads.RemoveHead();

            HANDLE hThread = pInfo->GetHandle();

            if ((hThread != NULL) && (hThread != (HANDLE)-1)) {
                pInfo->SetHandle(NULL);
                QueueNullFunc(hThread);
                SleepEx(0, TRUE);
            }
        }
        g_IoWorkerCriticalSection.Release();
        g_IoWorkerCriticalSection.Terminate();

        //
        // protect ourselves against termination from within a thread pool
        // thread
        //

        DWORD_PTR sig = (DWORD_PTR)TlsGetValue(g_TpsTls);
        DWORD limit = (sig == TPS_WORKER_SIGNATURE) ? 1 : 0;

        g_ThreadPool.Terminate(limit);

        g_StartedWorkerInitialization = FALSE;
        g_CompletedWorkerInitialization = FALSE;
        g_NumIoWorkerThreads = 0;
        g_NumIoWorkRequests = 0;
        g_LastIoThreadCreationTickCount = 0;
        g_MaximumIoThreads = MAX_IO_WORKER_THREADS;
        g_MaximumIoQueueDepth = NEW_THREAD_THRESHOLD;
        g_ThreadCreationDelta = THREAD_CREATION_DAMPING_TIME;
        g_dwWorkItemId = 0;

        if ((sig == TPS_IO_WORKER_SIGNATURE) || (sig == TPS_WORKER_SIGNATURE)) {
            g_bDeferredWorkerTermination = TRUE;
        }
    }
}

//
// private functions
//

PRIVATE
DWORD
InitializeWorkerThreadPool(
    VOID
    )

/*++

Routine Description:

    This routine initializes all aspects of the thread pool.

Arguments:

    None.

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure -

--*/

{
    DWORD error = ERROR_SUCCESS;

    if (!InterlockedExchange((LPLONG)&g_StartedWorkerInitialization, TRUE)) {
        g_IoWorkerCriticalSection.Init();
        g_IoWorkerThreads.Init();
        g_ThreadPool.Init();

        //
        // signal that initialization has completed
        //

        g_CompletedWorkerInitialization = TRUE;
    } else {

        //
        // relinquish the timeslice until the other thread has initialized
        //

        while (!g_CompletedWorkerInitialization) {
            SleepEx(0, FALSE);
        }
    }
    if (error == ERROR_SUCCESS) {
        error = g_ThreadPool.GetError();

        ASSERT(error == ERROR_SUCCESS);
    }
    return error;
}

PRIVATE
DWORD
StartIOWorkerThread(
    VOID
    )

/*++

Routine Description:

    This routine starts an I/O worker thread

Arguments:

    None.

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    HANDLE hThread;
    DWORD error = StartThread((LPTHREAD_START_ROUTINE)IOWorkerThread,
                              &hThread,
                              TRUE
                              );

    if (error == ERROR_SUCCESS) {

        //
        // update the time at which the current thread was created
        //

        InterlockedExchange((LPLONG)&g_LastIoThreadCreationTickCount,
                            (LONG)GetTickCount()
                            );

        //
        // we have the g_IoWorkerCriticalSection. We know the CIoWorkerThreadInfo
        // added at the head is the one we just created
        //

        ((CIoWorkerThreadInfo *)g_IoWorkerThreads.Next())->SetHandle(hThread);

        //
        // increment the count of the thread type created
        //

        InterlockedIncrement((LPLONG)&g_NumIoWorkerThreads);
    } else {

        //
        // thread creation failed. If there is even one thread present do not
        // return failure since we can still service the work request.
        //

        if (g_NumIoWorkerThreads != 0) {
            error = ERROR_SUCCESS;
        }
    }
    return error;
}

PRIVATE
DWORD
QueueIOWorkerRequest(
    IN LPTHREAD_START_ROUTINE pfnCallback,
    IN LPVOID pContext
    )

/*++

Routine Description:

    This routine queues up the request to be executed in a IO worker thread.

Arguments:

    pfnCallback - Routine that is called by the worker thread

    pContext    - Opaque pointer passed in as an argument to pfnCallback

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    //
    // since we don't have access to the additional stack parameters of
    // NtQueueApcThread, we must allocate a packet off the heap in which
    // to pass the parameters
    //

    //
    // PERF: use a pre-allocated cache of request packets
    //

    CIoWorkerRequest * pPacket = new CIoWorkerRequest(pfnCallback, pContext);

    if (pPacket == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // increment the outstanding work request counter
    //

    InterlockedIncrement((LPLONG)&g_NumIoWorkRequests);

    //
    // in order to implement "fair" assignment of work items between IO worker
    // threads each time remove from head of list and reinsert at back. Keep
    // the pointer for thread handle
    //

    ASSERT(!g_IoWorkerThreads.IsEmpty());

    CIoWorkerThreadInfo * pInfo = (CIoWorkerThreadInfo *)g_IoWorkerThreads.RemoveHead();
    pInfo->InsertTail(&g_IoWorkerThreads);

    //
    // queue an APC to the IO worker thread. The worker thread will free the
    // request packet
    //

    BOOL bOk = QueueUserAPC((PAPCFUNC)ExecuteIOWorkItem,
                            pInfo->GetHandle(),
                            (ULONG_PTR)pPacket
                            );

    DWORD error = ERROR_SUCCESS;

    if (!bOk) {

        //
        // GetLastError() before ASSERT()!
        //

        error = GetLastError();
    }

    return error;
}

PRIVATE
VOID
IOWorkerThread(
    IN HANDLE hEvent
    )

/*++

Routine Description:

    All I/O worker threads execute in this routine. All the work requests execute as APCs
    in this thread.

Arguments:

    hEvent  - event handle signalled when initialization complete

Return Value:

    None.

--*/

{
    HMODULE hDll = LoadLibrary(g_cszShlwapi);
    DWORD dwExitCode = ERROR_OUTOFMEMORY;

    if (hDll) // LoadLibrary can fail under low-memory conditions, even though we're in shlwapi (hence it's loaded already)    
    {
        ASSERT(g_TpsTls != 0xFFFFFFFF);

        TlsSetValue(g_TpsTls, (LPVOID)TPS_IO_WORKER_SIGNATURE);

        CIoWorkerThreadInfo info(g_IoWorkerThreads.Head());

        SetEvent(hEvent);

        while (!g_bTpsTerminating) {
            SleepEx(INFINITE, TRUE);
        }
        InterlockedDecrement((LPLONG)&g_NumIoWorkerThreads);
        while (info.GetHandle() != NULL) {
            SleepEx(0, FALSE);
        }
        if (GetCurrentThreadId() == g_dwTerminationThreadId) {
            g_bTpsTerminating = FALSE;
            g_bDeferredWorkerTermination = FALSE;
            g_dwTerminationThreadId = 0;
        }
        FreeLibrary(hDll);
        dwExitCode = ERROR_SUCCESS;
    }
    ExitThread(dwExitCode);
}

PRIVATE
VOID
ExecuteIOWorkItem(
    IN CIoWorkerRequest * pPacket
    )

/*++

Routine Description:

    Executes an IO Work function. Runs in a APC in the IO Worker thread

Arguments:

    pPacket - pointer to CIoWorkerRequest allocated by the requesting
              thread. We need to free it/return it to packet cache

Return Value:

    None.

--*/

{
    LPTHREAD_START_ROUTINE fn = pPacket->GetCallback();
    LPVOID ctx = pPacket->GetContext();

    delete pPacket;

    if (!g_bTpsTerminating) {
        fn(ctx);
        InterlockedDecrement((LPLONG)&g_NumIoWorkRequests);
    }
}

PRIVATE
VOID
ExecuteWorkItem(
    IN WorkItem *pItem
    )

/*++

Routine Description:

    Executes a regular Work function. Runs in the NT thread pool

Arguments:

    pItem   - context information. Contains the worker function that needs to 
              be run and the hModule to free. We need to free it.

Return Value:

    None.

--*/

{
    HMODULE hModule = pItem->hModuleToFree;
    LPTHREAD_START_ROUTINE pfn = pItem->pfnCallback;
    LPVOID ctx = pItem->pContext;
    delete pItem;
#ifdef DEBUG
    HRESULT hrDebug = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (hrDebug == RPC_E_CHANGED_MODE)
    {
        ASSERTMSG(FALSE, "SHLWAPI Thread pool wrapper: Could not CoInitialize Appartment threaded. We got infected with an MTA!\n");
    }
    else
    {
        CoUninitialize();
    }
#endif

    // Do the work now
    pfn(ctx);

#ifdef DEBUG
    hrDebug = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (hrDebug == RPC_E_CHANGED_MODE)
    {
        ASSERTMSG(FALSE, "SHLWAPI Thread pool wrapper: Could not CoInitialize Appartment threaded. The task at %x forgot to CoUninitialize or "
                            "we got infected with an MTA!\n", pfn);
    }
    else
    {
        CoUninitialize();
    }
#endif

    if (hModule)
        FreeLibrary(hModule);

}

VOID
CThreadPool::WorkerThread(
    VOID
    )

/*++

Routine Description:

    Static thread function. Instantiates the thread pool object pointer and
    calls the non-IO worker member function

Arguments:
    None.

Return Value:

    None.

--*/

{
    HMODULE hDll = LoadLibrary(g_cszShlwapi);
    DWORD dwExitCode = ERROR_OUTOFMEMORY;

    if (hDll) // LoadLibrary can fail under low-memory conditions, even though we're in shlwapi (hence it's loaded already)
    {
        ASSERT(g_TpsTls != 0xFFFFFFFF);

        TlsSetValue(g_TpsTls, (LPVOID)TPS_WORKER_SIGNATURE);

        g_ThreadPool.Worker();

        if (GetCurrentThreadId() == g_dwTerminationThreadId) {
            g_dwTerminationThreadId = 0;
            g_bDeferredWorkerTermination = FALSE;
            g_bTpsTerminating = FALSE;
        }
        FreeLibrary(hDll);
        dwExitCode = ERROR_SUCCESS;
    }
    ExitThread(dwExitCode);
}

VOID
CThreadPool::Worker(
    VOID
    )

/*++

Routine Description:

    All non I/O worker threads execute in this routine. This function will
    terminate when it has not serviced a request for m_workerIdleTimeout mSec

Arguments:

    None.

Return Value:

    None.

--*/

{
    while (!g_bTpsTerminating)
    {
        FARPROC fn;
        ULONG_PTR ctx;
        DWORD flags;
        HMODULE hMouduleToFree = NULL;
        DWORD error = RemoveWorkItem(&fn, &ctx, &hMouduleToFree, &flags, m_workerIdleTimeout);

        ASSERT(error != ERROR_SUCCESS || !(flags & TPS_INVALID_FLAGS));

        if (g_bTpsTerminating)
        {
            break;
        }
        if (error == ERROR_SUCCESS)
        {
            // call the work function
            ((LPTHREAD_START_ROUTINE)fn)((LPVOID)ctx);

            if (hMouduleToFree)
            {
                // we completed the task, so free the hmodule associated with it
                FreeLibrary(hMouduleToFree);
            }

            if (flags & TPS_LONGEXECTIME)
            {
                MakeAvailable();
            }
        }
        else if (error == WAIT_TIMEOUT)
        {
            m_qlock.Acquire();

            if ((m_queueSize == 0) && (m_availableWorkerThreads > m_minWorkerThreads))
            {
                RemoveWorker();
                m_qlock.Release();

                //#if DBG
                //char buf[256];
                //wsprintf(buf, ">>>> terminating worker thread. Total = %d/%d. Avail = %d. Factor = %d/%d\n",
                //         m_totalWorkerThreads,
                //         m_maxWorkerThreadsCreated,
                //         m_availableWorkerThreads,
                //         m_qFactor,
                //         m_qFactorMax
                //         );
                //OutputDebugString(buf);
                //#endif

                return;
            }
            m_qlock.Release();
        }
    }

    //#if DBG
    //char buf[256];
    //wsprintf(buf, ">>>> terminating worker thread. Total = %d/%d. Avail = %d. Factor = %d/%d\n",
    //         m_totalWorkerThreads,
    //         m_maxWorkerThreadsCreated,
    //         m_availableWorkerThreads,
    //         m_qFactor,
    //         m_qFactorMax
    //         );
    //OutputDebugString(buf);
    //#endif

    RemoveWorker();
}
