/*
* Process Hacker -
* thread pool
*
* Copyright (C) 2009-2010 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see .
*/
#include
typedef struct _PH_WORK_QUEUE_ITEM
{
PTHREAD_START_ROUTINE Function;
PVOID Context;
} PH_WORK_QUEUE_ITEM, *PPH_WORK_QUEUE_ITEM;
NTSTATUS PhpWorkQueueThreadStart(
__in PVOID Parameter
);
PPH_FREE_LIST PhWorkQueueItemFreeList;
PH_WORK_QUEUE PhGlobalWorkQueue;
VOID PhWorkQueueInitialization()
{
PhWorkQueueItemFreeList = PhCreateFreeList(sizeof(PH_WORK_QUEUE_ITEM), 32);
PhInitializeWorkQueue(
&PhGlobalWorkQueue,
0,
3,
1000
);
}
VOID PhInitializeWorkQueue(
__out PPH_WORK_QUEUE WorkQueue,
__in ULONG MinimumThreads,
__in ULONG MaximumThreads,
__in ULONG NoWorkTimeout
)
{
PhInitializeRundownProtection(&WorkQueue->RundownProtect);
WorkQueue->Terminating = FALSE;
WorkQueue->Queue = PhCreateQueue(1);
PhInitializeQueuedLock(&WorkQueue->QueueLock);
WorkQueue->MinimumThreads = MinimumThreads;
WorkQueue->MaximumThreads = MaximumThreads;
WorkQueue->NoWorkTimeout = NoWorkTimeout;
PhInitializeQueuedLock(&WorkQueue->StateLock);
WorkQueue->SemaphoreHandle = CreateSemaphore(NULL, 0, MAXLONG, NULL);
WorkQueue->CurrentThreads = 0;
WorkQueue->BusyThreads = 0;
}
VOID PhDeleteWorkQueue(
__inout PPH_WORK_QUEUE WorkQueue
)
{
PPH_WORK_QUEUE_ITEM workQueueItem;
// Wait for all worker threads to exit.
WorkQueue->Terminating = TRUE;
ReleaseSemaphore(WorkQueue->SemaphoreHandle, WorkQueue->CurrentThreads, NULL);
PhWaitForRundownProtection(&WorkQueue->RundownProtect);
// Free all un-executed work items.
while (PhDequeueQueueItem(WorkQueue->Queue, &workQueueItem))
PhFreeToFreeList(PhWorkQueueItemFreeList, workQueueItem);
PhDereferenceObject(WorkQueue->Queue);
NtClose(WorkQueue->SemaphoreHandle);
}
FORCEINLINE VOID PhpInitializeWorkQueueItem(
__out PPH_WORK_QUEUE_ITEM WorkQueueItem,
__in PTHREAD_START_ROUTINE Function,
__in PVOID Context
)
{
WorkQueueItem->Function = Function;
WorkQueueItem->Context = Context;
}
FORCEINLINE VOID PhpExecuteWorkQueueItem(
__inout PPH_WORK_QUEUE_ITEM WorkQueueItem
)
{
WorkQueueItem->Function(WorkQueueItem->Context);
}
BOOLEAN PhpCreateWorkQueueThread(
__inout PPH_WORK_QUEUE WorkQueue
)
{
HANDLE threadHandle;
// Make sure the structure doesn't get deleted while the thread is running.
if (!PhAcquireRundownProtection(&WorkQueue->RundownProtect))
return FALSE;
threadHandle = PhCreateThread(0, PhpWorkQueueThreadStart, WorkQueue);
if (threadHandle)
{
WorkQueue->CurrentThreads++;
NtClose(threadHandle);
return TRUE;
}
else
{
PhReleaseRundownProtection(&WorkQueue->RundownProtect);
return FALSE;
}
}
NTSTATUS PhpWorkQueueThreadStart(
__in PVOID Parameter
)
{
PPH_WORK_QUEUE workQueue = (PPH_WORK_QUEUE)Parameter;
while (TRUE)
{
PPH_WORK_QUEUE_ITEM workQueueItem = NULL;
ULONG result;
// Check if we have more threads than the limit.
if (workQueue->CurrentThreads > workQueue->MaximumThreads)
{
BOOLEAN terminate = FALSE;
// Lock and re-check.
PhAcquireQueuedLockExclusiveFast(&workQueue->StateLock);
// Check the minimum as well.
if (
workQueue->CurrentThreads > workQueue->MaximumThreads &&
workQueue->CurrentThreads > workQueue->MinimumThreads
)
{
workQueue->CurrentThreads--;
terminate = TRUE;
}
PhReleaseQueuedLockExclusiveFast(&workQueue->StateLock);
if (terminate)
break;
}
// Wait for work.
result = WaitForSingleObject(workQueue->SemaphoreHandle, workQueue->NoWorkTimeout);
if (workQueue->Terminating)
{
// The work queue is being deleted.
PhAcquireQueuedLockExclusiveFast(&workQueue->StateLock);
workQueue->CurrentThreads--;
PhReleaseQueuedLockExclusiveFast(&workQueue->StateLock);
break;
}
if (result == WAIT_OBJECT_0)
{
// Dequeue the work item.
PhAcquireQueuedLockExclusiveFast(&workQueue->QueueLock);
PhDequeueQueueItem(workQueue->Queue, &workQueueItem);
PhReleaseQueuedLockExclusiveFast(&workQueue->QueueLock);
// Make sure we got work.
if (workQueueItem)
{
_InterlockedIncrement(&workQueue->BusyThreads);
PhpExecuteWorkQueueItem(workQueueItem);
_InterlockedDecrement(&workQueue->BusyThreads);
PhFreeToFreeList(PhWorkQueueItemFreeList, workQueueItem);
}
}
else
{
BOOLEAN terminate = FALSE;
// No work arrived before the timeout passed (or some error occurred).
// Terminate the thread.
PhAcquireQueuedLockExclusiveFast(&workQueue->StateLock);
// Check the minimum.
if (workQueue->CurrentThreads > workQueue->MinimumThreads)
{
workQueue->CurrentThreads--;
terminate = TRUE;
}
PhReleaseQueuedLockExclusiveFast(&workQueue->StateLock);
if (terminate)
break;
}
}
PhReleaseRundownProtection(&workQueue->RundownProtect);
return STATUS_SUCCESS;
}
VOID PhQueueWorkQueueItem(
__inout PPH_WORK_QUEUE WorkQueue,
__in PTHREAD_START_ROUTINE Function,
__in PVOID Context
)
{
PPH_WORK_QUEUE_ITEM workQueueItem;
workQueueItem = PhAllocateFromFreeList(PhWorkQueueItemFreeList);
PhpInitializeWorkQueueItem(workQueueItem, Function, Context);
// Enqueue the work item.
PhAcquireQueuedLockExclusiveFast(&WorkQueue->QueueLock);
PhEnqueueQueueItem(WorkQueue->Queue, workQueueItem);
PhReleaseQueuedLockExclusiveFast(&WorkQueue->QueueLock);
// Signal the semaphore once to let a worker thread continue.
ReleaseSemaphore(WorkQueue->SemaphoreHandle, 1, NULL);
// Check if all worker threads are currently busy,
// and if we can create more threads.
if (
WorkQueue->BusyThreads == WorkQueue->CurrentThreads &&
WorkQueue->CurrentThreads < WorkQueue->MaximumThreads
)
{
// Lock and re-check.
PhAcquireQueuedLockExclusiveFast(&WorkQueue->StateLock);
if (WorkQueue->CurrentThreads < WorkQueue->MaximumThreads)
{
PhpCreateWorkQueueThread(WorkQueue);
}
PhReleaseQueuedLockExclusiveFast(&WorkQueue->StateLock);
}
}
VOID PhQueueGlobalWorkQueueItem(
__in PTHREAD_START_ROUTINE Function,
__in PVOID Context
)
{
PhQueueWorkQueueItem(
&PhGlobalWorkQueue,
Function,
Context
);
}