639 lines
17 KiB
C
639 lines
17 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
threadq.c
|
|
|
|
Abstract:
|
|
|
|
Generic Worker Thread Queue Package
|
|
|
|
Author:
|
|
|
|
Mike Massa (mikemas) April 5, 1996
|
|
|
|
Revision History:
|
|
|
|
Who When What
|
|
-------- -------- ----------------------------------------------
|
|
mikemas 04-05-96 created
|
|
|
|
Notes:
|
|
|
|
Worker Thread Queues provide a single mechanism for processing
|
|
overlapped I/O completions as well as deferred work items. Work
|
|
queues are created and destroyed using ClRtlCreateWorkQueue()
|
|
and ClRtlDestroyWorkQueue(). Overlapped I/O completions are
|
|
directed to a work queue by associating an I/O handle with a work
|
|
queue using ClRtlAssociateIoHandleWorkQueue(). Deferred work items
|
|
are posted to a work queue using ClRtlPostItemWorkQueue().
|
|
|
|
Work queues are implemented using I/O completion ports. Each work
|
|
queue is serviced by a set of threads which dispatch work items
|
|
to specified work routines. Threads are created dynamically, up to
|
|
a specified maximum, to ensure that there is always a thread waiting
|
|
to service new work items. The priority of the threads servicing a
|
|
work queue can be specified.
|
|
|
|
[Future enhancement: dynamically shrink the thread pool when the
|
|
incoming work rate drops off. Currently, threads continue to service
|
|
the work queue until it is destroyed.]
|
|
|
|
Special care must be taken when destroying a work queue to ensure
|
|
that all threads terminate properly and no work items are lost.
|
|
See the notes under ClRtlDestroyWorkQueue.
|
|
|
|
--*/
|
|
|
|
#include "clusrtlp.h"
|
|
|
|
|
|
//
|
|
// Private Types
|
|
//
|
|
typedef struct _CLRTL_WORK_QUEUE {
|
|
HANDLE IoCompletionPort;
|
|
LONG MaximumThreads;
|
|
LONG TotalThreads;
|
|
LONG WaitingThreads;
|
|
LONG ReserveThreads;
|
|
LONG ConcurrentThreads;
|
|
DWORD Timeout;
|
|
int ThreadPriority;
|
|
HANDLE StopEvent;
|
|
} CLRTL_WORK_QUEUE;
|
|
|
|
|
|
//
|
|
// Private Routines
|
|
//
|
|
DWORD
|
|
ClRtlpWorkerThread(
|
|
LPDWORD Context
|
|
)
|
|
{
|
|
PCLRTL_WORK_QUEUE workQueue = (PCLRTL_WORK_QUEUE) Context;
|
|
DWORD bytesTransferred;
|
|
BOOL ioSuccess;
|
|
ULONG_PTR ioContext;
|
|
LPOVERLAPPED overlapped;
|
|
PCLRTL_WORK_ITEM workItem;
|
|
DWORD status;
|
|
LONG interlockedResult;
|
|
DWORD threadId;
|
|
HANDLE threadHandle;
|
|
DWORD timeout;
|
|
LONG myThreadId;
|
|
|
|
|
|
timeout = workQueue->Timeout;
|
|
myThreadId = GetCurrentThreadId();
|
|
|
|
if (!SetThreadPriority(GetCurrentThread(), workQueue->ThreadPriority)) {
|
|
status = GetLastError();
|
|
|
|
ClRtlLogPrint(
|
|
LOG_UNUSUAL,
|
|
"[WTQ] Thread %1!u! unable to set priority to %2!d!, status %3!u!\n",
|
|
myThreadId,
|
|
workQueue->ThreadPriority,
|
|
status
|
|
);
|
|
}
|
|
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(
|
|
LOG_CRITICAL,
|
|
"[WTQ] Thread %1!u! started, queue %2!lx!.\n",
|
|
myThreadId,
|
|
workQueue
|
|
);
|
|
#endif
|
|
|
|
while (TRUE) {
|
|
|
|
InterlockedIncrement(&(workQueue->WaitingThreads));
|
|
|
|
ioSuccess = GetQueuedCompletionStatus(
|
|
workQueue->IoCompletionPort,
|
|
&bytesTransferred,
|
|
&ioContext,
|
|
&overlapped,
|
|
timeout
|
|
);
|
|
|
|
interlockedResult = InterlockedDecrement(
|
|
&(workQueue->WaitingThreads)
|
|
);
|
|
|
|
if (overlapped) {
|
|
//
|
|
// Something was dequeued.
|
|
//
|
|
workItem = CONTAINING_RECORD(
|
|
overlapped,
|
|
CLRTL_WORK_ITEM,
|
|
Overlapped
|
|
);
|
|
|
|
if (interlockedResult == 0) {
|
|
//
|
|
// No more threads are waiting. Fire another one up.
|
|
// Make sure we haven't started too many first.
|
|
//
|
|
interlockedResult = InterlockedDecrement(
|
|
&(workQueue->ReserveThreads)
|
|
);
|
|
|
|
if (interlockedResult > 0) {
|
|
//
|
|
// We haven't started too many
|
|
//
|
|
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(
|
|
LOG_NOISE,
|
|
"[WTQ] Thread %1!u! starting another thread for queue %2!lx!.\n",
|
|
myThreadId,
|
|
workQueue
|
|
);
|
|
#endif // 0
|
|
|
|
InterlockedIncrement(&(workQueue->TotalThreads));
|
|
|
|
threadHandle = CreateThread(
|
|
NULL,
|
|
0,
|
|
ClRtlpWorkerThread,
|
|
workQueue,
|
|
0,
|
|
&threadId
|
|
);
|
|
|
|
if (threadHandle == NULL) {
|
|
InterlockedDecrement(&(workQueue->TotalThreads));
|
|
InterlockedIncrement(&(workQueue->ReserveThreads));
|
|
|
|
status = GetLastError();
|
|
|
|
ClRtlLogPrint(
|
|
LOG_CRITICAL,
|
|
"[WTQ] Thread %1!u! failed to create thread, %2!u!\n",
|
|
myThreadId,
|
|
status
|
|
);
|
|
}
|
|
else {
|
|
CloseHandle(threadHandle);
|
|
}
|
|
}
|
|
else {
|
|
InterlockedIncrement(&(workQueue->ReserveThreads));
|
|
}
|
|
} // end if (interlockedResult == 0)
|
|
|
|
if (ioSuccess) {
|
|
(*(workItem->WorkRoutine))(
|
|
workItem,
|
|
ERROR_SUCCESS,
|
|
bytesTransferred,
|
|
ioContext
|
|
);
|
|
}
|
|
else {
|
|
//
|
|
// The item was posted with an error.
|
|
//
|
|
status = GetLastError();
|
|
|
|
(*(workItem->WorkRoutine))(
|
|
workItem,
|
|
status,
|
|
bytesTransferred,
|
|
ioContext
|
|
);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else {
|
|
//
|
|
// No item was dequeued
|
|
//
|
|
if (ioSuccess) {
|
|
//
|
|
// This is our cue to start the termination process.
|
|
// Set the timeout to zero to make sure we don't block
|
|
// after the port is drained.
|
|
//
|
|
timeout = 0;
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(
|
|
LOG_NOISE,
|
|
"[WTQ] Thread %1!u! beginning termination process\n",
|
|
myThreadId
|
|
);
|
|
#endif // 0
|
|
}
|
|
else {
|
|
status = GetLastError();
|
|
|
|
if (status == WAIT_TIMEOUT) {
|
|
//
|
|
// No more items pending, time to exit.
|
|
//
|
|
CL_ASSERT(timeout == 0);
|
|
|
|
break;
|
|
}
|
|
|
|
CL_ASSERT(status == WAIT_TIMEOUT);
|
|
|
|
ClRtlLogPrint(
|
|
LOG_CRITICAL,
|
|
"[WTQ] Thread %1!u! No item, strange status %2!u! on queue %3!lx!\n",
|
|
myThreadId,
|
|
status,
|
|
workQueue
|
|
);
|
|
}
|
|
} // end if (overlapped)
|
|
} // end while(TRUE)
|
|
|
|
CL_ASSERT(workQueue->TotalThreads > 0);
|
|
InterlockedIncrement(&(workQueue->ReserveThreads));
|
|
InterlockedDecrement(&(workQueue->TotalThreads));
|
|
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(LOG_NOISE, "[WTQ] Thread %1!u! exiting.\n", myThreadId);
|
|
#endif // 0
|
|
|
|
//
|
|
// Let the ClRtlDestroyWorkQueue know we are terminating.
|
|
//
|
|
SetEvent(workQueue->StopEvent);
|
|
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
//
|
|
// Public Routines
|
|
//
|
|
PCLRTL_WORK_QUEUE
|
|
ClRtlCreateWorkQueue(
|
|
IN DWORD MaximumThreads,
|
|
IN int ThreadPriority
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates a work queue and a dynamic pool of threads to service it.
|
|
|
|
Arguments:
|
|
|
|
MaximumThreads - The maximum number of threads to create to service
|
|
the queue.
|
|
|
|
ThreadPriority - The priority level at which the queue worker threads
|
|
should run.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the created queue if the routine is successful.
|
|
|
|
NULL if the routine fails. Call GetLastError for extended
|
|
error information.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
PCLRTL_WORK_QUEUE workQueue = NULL;
|
|
DWORD threadId;
|
|
HANDLE threadHandle = NULL;
|
|
HANDLE bogusHandle = NULL;
|
|
|
|
|
|
if (MaximumThreads == 0) {
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return(NULL);
|
|
}
|
|
|
|
bogusHandle = CreateFileW(
|
|
L"NUL",
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL
|
|
);
|
|
|
|
|
|
if (bogusHandle == INVALID_HANDLE_VALUE) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_CRITICAL, "[WTQ] bogus file creation failed, %1!u!\n", status);
|
|
return(NULL);
|
|
}
|
|
|
|
workQueue = LocalAlloc(
|
|
LMEM_FIXED | LMEM_ZEROINIT,
|
|
sizeof(CLRTL_WORK_QUEUE)
|
|
);
|
|
|
|
if (workQueue == NULL) {
|
|
status = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto error_exit;
|
|
}
|
|
|
|
workQueue->MaximumThreads = MaximumThreads;
|
|
workQueue->TotalThreads = 1;
|
|
workQueue->WaitingThreads = 0;
|
|
workQueue->ReserveThreads = MaximumThreads - 1;
|
|
workQueue->ConcurrentThreads = 0;
|
|
workQueue->Timeout = INFINITE;
|
|
workQueue->ThreadPriority = ThreadPriority;
|
|
|
|
workQueue->IoCompletionPort = CreateIoCompletionPort(
|
|
bogusHandle,
|
|
NULL,
|
|
0,
|
|
workQueue->ConcurrentThreads
|
|
);
|
|
|
|
CloseHandle(bogusHandle); bogusHandle = NULL;
|
|
|
|
if (workQueue->IoCompletionPort == NULL) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_CRITICAL, "[WTQ] Creation of I/O Port failed, %1!u!\n", status);
|
|
}
|
|
|
|
workQueue->StopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
|
|
if (workQueue->StopEvent == NULL) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_CRITICAL, "[WTQ] Creation of stop event failed, %1!u!\n", status);
|
|
goto error_exit;
|
|
}
|
|
|
|
threadHandle = CreateThread(
|
|
NULL,
|
|
0,
|
|
ClRtlpWorkerThread,
|
|
workQueue,
|
|
0,
|
|
&threadId
|
|
);
|
|
|
|
if (threadHandle == NULL) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_CRITICAL, "[WTQ] Failed to create worker thread, %1!u!\n", status);
|
|
goto error_exit;
|
|
}
|
|
|
|
CloseHandle(threadHandle);
|
|
|
|
return(workQueue);
|
|
|
|
|
|
error_exit:
|
|
|
|
if (bogusHandle != NULL) {
|
|
CloseHandle(bogusHandle);
|
|
}
|
|
|
|
if (workQueue != NULL) {
|
|
if (workQueue->IoCompletionPort != NULL) {
|
|
CloseHandle(workQueue->IoCompletionPort);
|
|
}
|
|
|
|
if (workQueue->StopEvent != NULL) {
|
|
CloseHandle(workQueue->StopEvent);
|
|
}
|
|
|
|
LocalFree(workQueue);
|
|
}
|
|
|
|
SetLastError(status);
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
VOID
|
|
ClRtlDestroyWorkQueue(
|
|
IN PCLRTL_WORK_QUEUE WorkQueue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destroys a work queue and its thread pool.
|
|
|
|
Arguments:
|
|
|
|
WorkQueue - The queue to destroy.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Notes:
|
|
|
|
The following rules must be observed in order to safely destroy a
|
|
work queue:
|
|
|
|
1) No new work items may be posted to the queue once all previously
|
|
posted items have been processed by this routine.
|
|
|
|
2) WorkRoutines must be able to process items until this
|
|
call returns. After the call returns, no more items will
|
|
be delivered from the specified queue.
|
|
|
|
One workable cleanup procedure is as follows: First, direct the
|
|
WorkRoutines to silently discard completed items. Next, eliminate
|
|
all sources of new work. Finally, destroy the work queue. Note that
|
|
when in discard mode, the WorkRoutines may not access any structures
|
|
which will be destroyed by eliminating the sources of new work.
|
|
|
|
--*/
|
|
{
|
|
BOOL posted;
|
|
DWORD status;
|
|
|
|
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(LOG_NOISE, "[WTQ] Destroying work queue %1!lx!\n", WorkQueue);
|
|
#endif // 0
|
|
|
|
|
|
while (WorkQueue->TotalThreads != 0) {
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(
|
|
LOG_NOISE,
|
|
"[WTQ] Destroy: Posting terminate item, thread cnt %1!u!\n",
|
|
WorkQueue->TotalThreads
|
|
);
|
|
#endif // 0
|
|
|
|
posted = PostQueuedCompletionStatus(
|
|
WorkQueue->IoCompletionPort,
|
|
0,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
if (!posted) {
|
|
status = GetLastError();
|
|
|
|
ClRtlLogPrint(
|
|
LOG_CRITICAL,
|
|
"[WTQ] Destroy: Failed to post termination item, %1!u!\n",
|
|
status
|
|
);
|
|
|
|
CL_ASSERT(status == ERROR_SUCCESS);
|
|
|
|
break;
|
|
}
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(LOG_NOISE, "[WTQ] Destroy: Waiting for a thread to terminate.\n");
|
|
#endif // 0
|
|
|
|
status = WaitForSingleObject(WorkQueue->StopEvent, INFINITE);
|
|
|
|
CL_ASSERT(status == WAIT_OBJECT_0);
|
|
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(LOG_NOISE, "[WTQ] Destroy: A thread terminated.\n");
|
|
#endif // 0
|
|
}
|
|
|
|
CloseHandle(WorkQueue->IoCompletionPort);
|
|
CloseHandle(WorkQueue->StopEvent);
|
|
|
|
LocalFree(WorkQueue);
|
|
|
|
#if THREADQ_VERBOSE
|
|
ClRtlLogPrint(LOG_NOISE, "[WTQ] Work queue %1!lx! destroyed\n", WorkQueue);
|
|
#endif // 0
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ClRtlPostItemWorkQueue(
|
|
IN PCLRTL_WORK_QUEUE WorkQueue,
|
|
IN PCLRTL_WORK_ITEM WorkItem,
|
|
IN DWORD BytesTransferred,
|
|
IN ULONG_PTR IoContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Posts a specified work item to a specified work queue.
|
|
|
|
Arguments:
|
|
|
|
WorkQueue - A pointer to the work queue to which to post the item.
|
|
|
|
WorkItem - A pointer to the item to post.
|
|
|
|
BytesTransferred - If the work item represents a completed I/O operation,
|
|
this parameter contains the number of bytes
|
|
transferred during the operation. For other work items,
|
|
the semantics of this parameter may be defined by
|
|
the caller.
|
|
|
|
IoContext - If the work item represents a completed I/O operation,
|
|
this parameter contains the context value associated
|
|
with the handle on which the operation was submitted.
|
|
Of other work items, the semantics of this parameter
|
|
may be defined by the caller.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if the item was posted successfully.
|
|
A Win32 error code if the post operation fails.
|
|
|
|
--*/
|
|
{
|
|
BOOL posted;
|
|
|
|
posted = PostQueuedCompletionStatus(
|
|
WorkQueue->IoCompletionPort,
|
|
BytesTransferred,
|
|
IoContext,
|
|
&(WorkItem->Overlapped)
|
|
);
|
|
|
|
if (posted) {
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
return(GetLastError());
|
|
}
|
|
|
|
|
|
DWORD
|
|
ClRtlAssociateIoHandleWorkQueue(
|
|
IN PCLRTL_WORK_QUEUE WorkQueue,
|
|
IN HANDLE IoHandle,
|
|
IN ULONG_PTR IoContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Associates a specified I/O handle, opened for overlapped I/O
|
|
completion, with a work queue. All pending I/O operations on
|
|
the specified handle will be posted to the work queue when
|
|
completed. An initialized CLRTL_WORK_ITEM must be used to supply
|
|
the OVERLAPPED structure whenever an I/O operation is submitted on
|
|
the specified handle.
|
|
|
|
Arguments:
|
|
|
|
WorkQueue - The work queue with which to associate the I/O handle.
|
|
|
|
IoHandle - The I/O handle to associate.
|
|
|
|
IoContext - A context value to associate with the specified handle.
|
|
This value will be supplied as a parameter to the
|
|
WorkRoutine which processes completions for this
|
|
handle.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if the association completes successfully.
|
|
A Win32 error code if the association fails.
|
|
|
|
--*/
|
|
{
|
|
HANDLE portHandle;
|
|
|
|
|
|
portHandle = CreateIoCompletionPort(
|
|
IoHandle,
|
|
WorkQueue->IoCompletionPort,
|
|
IoContext,
|
|
WorkQueue->ConcurrentThreads
|
|
);
|
|
|
|
if (portHandle != NULL) {
|
|
CL_ASSERT(portHandle == WorkQueue->IoCompletionPort);
|
|
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
return(GetLastError());
|
|
}
|
|
|
|
|
|
|