Windows2000/private/ntos/rtl/threads.c

3628 lines
126 KiB
C

/*++
Copyright (c) 1989-1998 Microsoft Corporation
Module Name:
threads.c
Abstract:
This module defines functions for thread pools.
Thread pools can be used for one time execution of tasks, for waits and for one shot or periodic timers.
Author:
Gurdeep Singh Pall (gurdeep) Nov 13, 1997
Environment:
These routines are statically linked in the caller's executable and are callable in only from user mode.
They make use of Nt system services.
Revision History:
Aug-19 lokeshs - modifications to thread pool apis.
--*/
// There are 3 types of thread pool functions supported
// 1. Wait Thread Pool
// 2. Worker Thread Pool
// 3. Timer Thread Pool
// Wait Thread Pool
// ----------------
// Clients can submit a waitable object with an optional timeout to wait on.
// One thread is created per 63 of such waitable objects.
// These threads are never killed.
// Worker Thread Pool
// ------------------
// Clients can submit functions to be executed by a worker thread.
// Threads are created if the work queue exceeds a threshold.
// Clients can request that the function be invoked in the context of a I/O thread.
// I/O worker threads can be used for initiating asynchronous I/O requests.
// They are not terminated if there are pending IO requests.
// Worker threads terminate if inactivity exceeds a threshold.
// Clients can also associate IO completion requests with the IO completion port waited upon by the non I/O worker threads.
// One should not post overlapped IO requests in worker threads.
// Timer Thread Pool
// -----------------
// Clients create one or more Timer Queues and insert one shot or periodic timers in them.
// All timers in a queue are kept in a "Delta List" with each timer's firing time relative to the timer before it.
// All Queues are also kept in a "Delta List" with each Queue's firing time (set to the firing time of the nearest firing timer) relative to the Queue before it.
// One NT Timer is used to service all timers in all queues.
#include <ntos.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "ntrtlp.h"
#include "threads.h"
ULONG DPRN = 0x0000000;
NTSTATUS RtlRegisterWait (
OUT PHANDLE WaitHandle,
IN HANDLE Handle,
IN WAITORTIMERCALLBACKFUNC Function,
IN PVOID Context,
IN ULONG Milliseconds,
IN ULONG Flags
)
/*++
Routine Description:
This routine adds a new wait request to the pool of objects being waited on.
Arguments:
WaitHandle - Handle returned on successful completion of this routine.
Handle - Handle to the object to be waited on
Function - Routine that is called when the wait completes or a timeout occurs
Context - Opaque pointer passed in as an argument to Function
Milliseconds - Timeout for the wait in milliseconds. 0xffffffff means dont timeout.
Flags - Can be one of:
WT_EXECUTEINWAITTHREAD - if WorkerProc should be invoked in the wait thread itself. This should only be used for small routines.
WT_EXECUTEINIOTHREAD - use only if the WorkerProc should be invoked in an IO Worker thread. Avoid using it.
If Flags is not WT_EXECUTEINWAITTHREAD, the following flag can also be set:
WT_EXECUTELONGFUNCTION - indicates that the callback might be blocked for a long duration.
Use only if the callback is being queued to a worker thread.
Return Value:
NTSTATUS - Result code from call. The following are returned
STATUS_SUCCESS - The registration was successful.
STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation.
or other NTSTATUS error code
--*/
{
PRTLP_WAIT Wait ;
NTSTATUS Status ;
PRTLP_EVENT Event ;
LARGE_INTEGER TimeOut ;
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB = NULL;
*WaitHandle = NULL ;
// Initialize thread pool if it isnt already done
if ( CompletedWaitInitialization != 1) {
Status = RtlpInitializeWaitThreadPool () ;
if (! NT_SUCCESS( Status ) )
return Status ;
}
// Initialize Wait request
Wait = (PRTLP_WAIT) RtlpAllocateTPHeap ( sizeof (RTLP_WAIT), HEAP_ZERO_MEMORY) ;
if (!Wait) {
return STATUS_NO_MEMORY ;
}
Wait->WaitHandle = Handle ;
Wait->Flags = Flags ;
Wait->Function = Function ;
Wait->Context = Context ;
Wait->Timeout = Milliseconds ;
SET_SIGNATURE(Wait) ;
// timer part of wait is initialized by wait thread in RtlpAddWait
// Get a wait thread that can accomodate another wait request.
Status = RtlpFindWaitThread (&ThreadCB) ;
if (Status != STATUS_SUCCESS) {
RtlpFreeTPHeap( Wait ) ;
return Status ;
}
Wait->ThreadCB = ThreadCB ;
#if DBG1
Wait->DbgId = ++NextWaitDbgId ;
Wait->ThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
if (DPRN0)
DbgPrint("<%d:%d> Wait %x created by thread:<%x:%x>\n\n",
Wait->DbgId,
1,
(ULONG_PTR)Wait,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
*WaitHandle = Wait ;// Set the wait handle
// Queue an APC to the Wait Thread
Status = NtQueueApcThread(ThreadCB->ThreadHandle, (PPS_APC_ROUTINE)RtlpAddWait, (PVOID)Wait, NULL, NULL);
if ( NT_SUCCESS(Status) ) {
Status = STATUS_SUCCESS ;
} else {
*WaitHandle = NULL ;
RtlpFreeTPHeap( Wait ) ;
}
return Status ;
}
NTSTATUS RtlDeregisterWait(IN HANDLE WaitHandle)
/*++
Routine Description:
This routine removes the specified wait from the pool of objects being waited on.
This routine is non-blocking.
Once this call returns, no new Callbacks are invoked.
However, Callbacks that might already have been queued to worker threads are not cancelled.
Arguments:
WaitHandle - Handle indentifying the wait.
Return Value:
STATUS_SUCCESS - The deregistration was successful.
STATUS_PENDING - Some callbacks associated with this Wait, are still executing.
--*/
{
return RtlDeregisterWaitEx( WaitHandle, NULL ) ;
}
NTSTATUS RtlDeregisterWaitEx(IN HANDLE WaitHandle, IN HANDLE Event)
/*++
Routine Description:
This routine removes the specified wait from the pool of objects being waited on.
Once this call returns, no new Callbacks will be invoked.
Depending on the value of Event, the call can be blocking or non-blocking.
Blocking calls MUST NOT be invoked inside the callback routines,
except when a callback being executed in the Wait thread context deregisters
its associated Wait (in this case there is no reason for making blocking calls),
or when a callback queued to a worker thread is deregistering some other wait item (be careful of deadlocks here).
Arguments:
WaitHandle - Handle indentifying the wait.
Event - Event to wait upon.
(HANDLE)-1: The function creates an event and waits on it.
Event : The caller passes an Event. The function removes the wait handle, but does not wait for all callbacks to complete.
The Event is released after all callbacks have completed.
NULL : The function is non-blocking. The function removes the wait handle, but does not wait for all callbacks to complete.
Return Value:
STATUS_SUCCESS - The deregistration was successful.
STATUS_PENDING - Some callback is still pending.
--*/
{
NTSTATUS Status, StatusAsync = STATUS_SUCCESS ;
PRTLP_WAIT Wait = (PRTLP_WAIT) WaitHandle ;
ULONG CurrentThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
PRTLP_EVENT CompletionEvent = NULL ;
HANDLE ThreadHandle ;
ULONG NonBlocking = ( Event != (HANDLE) -1 ) ; //The call returns non-blocking
if (!Wait) {
ASSERT( FALSE ) ;
return STATUS_INVALID_PARAMETER ;
}
ThreadHandle = Wait->ThreadCB->ThreadHandle ;
CHECK_DEL_SIGNATURE( Wait ) ;
SET_DEL_SIGNATURE( Wait ) ;
#if DBG1
Wait->ThreadId2 = CurrentThreadId ;
#endif
if (Event == (HANDLE)-1) {
// Get an event from the event cache
CompletionEvent = RtlpGetWaitEvent () ;
if (!CompletionEvent) {
return STATUS_NO_MEMORY ;
}
}
Wait = (PRTLP_WAIT) WaitHandle ;
#if DBG1
if (DPRN0)
DbgPrint("<%d:%d> Wait %x deregistering by thread:<%x:%x>\n\n",
Wait->DbgId,
Wait->RefCount,
(ULONG_PTR)Wait,
CurrentThreadId,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
Wait->CompletionEvent = CompletionEvent ? CompletionEvent->Handle : Event ;
// RtlDeregisterWaitEx is being called from within the Wait thread callback
if ( CurrentThreadId == Wait->ThreadCB->ThreadId ) {
Status = RtlpDeregisterWait ( Wait, NULL, NULL ) ;
// all callback functions run in the wait thread. So cannot return PENDING
ASSERT ( Status != STATUS_PENDING ) ;
} else {
PRTLP_EVENT PartialCompletionEvent = NULL ;
if (NonBlocking) {
PartialCompletionEvent = RtlpGetWaitEvent () ;
if (!PartialCompletionEvent) {
return STATUS_NO_MEMORY ;
}
}
// Queue an APC to the Wait Thread
Status = NtQueueApcThread(
Wait->ThreadCB->ThreadHandle,
(PPS_APC_ROUTINE)RtlpDeregisterWait,
(PVOID) Wait,
NonBlocking ? PartialCompletionEvent->Handle : NULL ,
NonBlocking ? (PVOID)&StatusAsync : NULL
);
if (! NT_SUCCESS(Status)) {
if (CompletionEvent) RtlpFreeWaitEvent( CompletionEvent ) ;
if (PartialCompletionEvent) RtlpFreeWaitEvent( PartialCompletionEvent ) ;
return Status ;
}
// block till the wait entry has been deactivated
if (NonBlocking) {
Status = RtlpWaitForEvent( PartialCompletionEvent->Handle, ThreadHandle ) ;
}
if (PartialCompletionEvent)
RtlpFreeWaitEvent( PartialCompletionEvent ) ;
}
if ( CompletionEvent ) {
// wait for Event to be fired. Return if the thread has been killed.
#if DBG1
if (DPRN0)
DbgPrint("Wait %x deregister waiting ThreadId<%x:%x>\n\n",
(ULONG_PTR)Wait,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
Status = RtlpWaitForEvent( CompletionEvent->Handle, ThreadHandle ) ;
#if DBG1
if (DPRN0)
DbgPrint("Wait %x deregister completed\n\n", (ULONG_PTR)Wait) ;
#endif
RtlpFreeWaitEvent( CompletionEvent ) ;
return NT_SUCCESS( Status ) ? STATUS_SUCCESS : Status ;
} else {
return StatusAsync ;
}
}
NTSTATUS RtlQueueWorkItem(IN WORKERCALLBACKFUNC Function, IN PVOID Context, IN ULONG Flags)
/*++
Routine Description:
This routine queues up the request to be executed in a worker thread.
Arguments:
Function - Routine that is called by the worker thread
Context - Opaque pointer passed in as an argument to WorkerProc
Flags - Can be:
WT_EXECUTEINIOTHREAD - Specifies that the WorkerProc should be invoked by a thread that is never destroyed when there are pending IO requests.
This can be used by threads that invoke I/O and/or schedule APCs.
The below flag can also be set:
WT_EXECUTELONGFUNCTION - Specifies that the function might block for a long duration.
Return Value:
STATUS_SUCCESS - Queued successfully.
STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation.
--*/
{
ULONG Threshold ;
ULONG CurrentTickCount ;
NTSTATUS Status = STATUS_SUCCESS ;
// Make sure the worker thread pool is initialized
if (CompletedWorkerInitialization != 1) {
Status = RtlpInitializeWorkerThreadPool () ;
if (! NT_SUCCESS(Status) )
return Status ;
}
RtlEnterCriticalSection (&WorkerCriticalSection) ;// Take lock for the global worker thread pool
if (Flags & (WT_EXECUTEINIOTHREAD |WT_EXECUTEINUITHREAD |WT_EXECUTEINPERSISTENTIOTHREAD) ){
// execute in IO Worker thread
ULONG NumEffIOWorkerThreads = NumIOWorkerThreads - NumLongIOWorkRequests ;
ULONG ThreadCreationDampingTime = NumIOWorkerThreads < NEW_THREAD_THRESHOLD ?
THREAD_CREATION_DAMPING_TIME1 :
THREAD_CREATION_DAMPING_TIME2 ;
if (PersistentIOTCB && (Flags&WT_EXECUTELONGFUNCTION))
NumEffIOWorkerThreads -- ;
// Check if we need to grow I/O worker thread pool
Threshold = (NumEffIOWorkerThreads < MAX_WORKER_THREADS ? NEW_THREAD_THRESHOLD * NumEffIOWorkerThreads : 0xffffffff) ;
if (LastThreadCreationTickCount > NtGetTickCount())
LastThreadCreationTickCount = NtGetTickCount() ;
if (NumEffIOWorkerThreads == 0 ||
((NumIOWorkRequests - NumLongIOWorkRequests > Threshold) && (LastThreadCreationTickCount + ThreadCreationDampingTime < NtGetTickCount())))
{
// Grow the IO worker thread pool
Status = RtlpStartIOWorkerThread () ;
}
if (Status == STATUS_SUCCESS) {
// Queue the work request
Status = RtlpQueueIOWorkerRequest (Function, Context, Flags) ;
}
} else {
// execute in regular worker thread
ULONG NumEffWorkerThreads = (NumWorkerThreads - NumLongWorkRequests) ;
ULONG ThreadCreationDampingTime = NumWorkerThreads < NEW_THREAD_THRESHOLD ?
THREAD_CREATION_DAMPING_TIME1 :
(NumWorkerThreads < 50 ? THREAD_CREATION_DAMPING_TIME2 : NumWorkerThreads << 7); // *100ms
// if io completion set, then have 1 more thread
if (NumMinWorkerThreads && NumEffWorkerThreads)
NumEffWorkerThreads -- ;
// Check if we need to grow worker thread pool
Threshold = (NumWorkerThreads < MAX_WORKER_THREADS ?
(NumEffWorkerThreads < 7 ? NumEffWorkerThreads*NumEffWorkerThreads : NEW_THREAD_THRESHOLD * NumEffWorkerThreads ) :
0xffffffff) ;
if (LastThreadCreationTickCount > NtGetTickCount())
LastThreadCreationTickCount = NtGetTickCount() ;
if (NumEffWorkerThreads == 0 || ( (NumWorkRequests - NumLongWorkRequests >= Threshold) && (LastThreadCreationTickCount + ThreadCreationDampingTime < NtGetTickCount())))
{
// Grow the worker thread pool
Status = RtlpStartWorkerThread () ;
}
// Queue the work request
if (Status == STATUS_SUCCESS) {
Status = RtlpQueueWorkerRequest (Function, Context, Flags) ;
}
}
// Release lock on the worker thread pool
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
return Status ;
}
NTSTATUS RtlSetIoCompletionCallback (IN HANDLE FileHandle, IN APC_CALLBACK_FUNCTION CompletionProc, IN ULONG Flags)
/*++
Routine Description:
This routine binds an Handle and an associated callback function to the IoCompletionPort which queues work items to worker threads.
Arguments:
Handle - handle to be bound to the IO completion port
CompletionProc - callback function to be executed when an IO request pending on the IO handle completes.
Flags - Reserved. pass 0.
--*/
{
IO_STATUS_BLOCK IoSb ;
FILE_COMPLETION_INFORMATION CompletionInfo ;
NTSTATUS Status;
// Make sure that the worker thread pool is initialized as the file handle
// is bound to IO completion port.
if (CompletedWorkerInitialization != 1) {
Status = RtlpInitializeWorkerThreadPool () ;
if (! NT_SUCCESS(Status) )
return Status ;
}
// from now on NumMinWorkerThreads should be 1. If there is only 1 worker thread create a new one.
if ( NumMinWorkerThreads == 0 ) {
// Take lock for the global worker thread pool
RtlEnterCriticalSection (&WorkerCriticalSection) ;
if (NumWorkerThreads == 0) {
Status = RtlpStartWorkerThread () ;
if ( ! NT_SUCCESS(Status) ) {
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
return Status ;
}
}
// from now on, there will be at least 1 worker thread
NumMinWorkerThreads = 1 ;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
}
// bind to IoCompletionPort, which queues work items to worker threads
CompletionInfo.Port = WorkerCompletionPort ;
CompletionInfo.Key = (PVOID) CompletionProc ;
Status = NtSetInformationFile (
FileHandle,
&IoSb, //not initialized
&CompletionInfo,
sizeof(CompletionInfo),
FileCompletionInformation //enum flag
) ;
return Status ;
}
NTSTATUS RtlCreateTimerQueue(OUT PHANDLE TimerQueueHandle)
/*++
Routine Description:
This routine creates a queue that can be used to queue time based tasks.
Arguments:
TimerQueueHandle - Returns back the Handle identifying the timer queue created.
Return Value:
NTSTATUS - Result code from call. The following are returned
STATUS_SUCCESS - Timer Queue created successfully.
STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation.
--*/
{
PRTLP_TIMER_QUEUE Queue ;
NTSTATUS Status;
// Initialize the timer component if it hasnt been done already
if (CompletedTimerInitialization != 1) {
Status = RtlpInitializeTimerThreadPool () ;
if ( !NT_SUCCESS(Status) )
return Status ;
}
InterlockedIncrement( &NumTimerQueues ) ;
// Allocate a Queue structure
Queue = (PRTLP_TIMER_QUEUE) RtlpAllocateTPHeap (sizeof (RTLP_TIMER_QUEUE), HEAP_ZERO_MEMORY) ;
if (Queue == NULL) {
InterlockedDecrement( &NumTimerQueues ) ;
return STATUS_NO_MEMORY ;
}
Queue->RefCount = 1 ;
// Initialize the allocated queue
InitializeListHead (&Queue->List) ;
InitializeListHead (&Queue->TimerList) ;
InitializeListHead (&Queue->UncancelledTimerList) ;
SET_SIGNATURE( Queue ) ;
Queue->DeltaFiringTime = 0 ;
#if DBG1
Queue->DbgId = ++NextTimerDbgId ;
Queue->ThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
if (DPRN0)
DbgPrint("<%d:%d> TimerQueue %x created by thread:<%x:%x>\n\n",
Queue->DbgId,
1,
(ULONG_PTR)Queue,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
*TimerQueueHandle = Queue ;
return STATUS_SUCCESS ;
}
NTSTATUS RtlDeleteTimerQueue(IN HANDLE TimerQueueHandle)
/*++
Routine Description:
This routine deletes a previously created queue.
This call is non-blocking and can be made from Callbacks.
Pending callbacks already queued to worker threads are not cancelled.
Arguments:
TimerQueueHandle - Handle identifying the timer queue created.
Return Value:
NTSTATUS - Result code from call.
STATUS_PENDING - Timer Queue created successfully.
--*/
{
return RtlDeleteTimerQueueEx( TimerQueueHandle, NULL ) ;
}
NTSTATUS RtlDeleteTimerQueueEx (HANDLE QueueHandle, HANDLE Event)
/*++
Routine Description:
This routine deletes the queue specified in the Request and frees all timers.
This call is blocking or non-blocking depending on the value passed for Event.
Blocking calls cannot be made from ANY Timer callbacks.
After this call returns, no new Callbacks will be fired for any timer associated with the queue.
Arguments:
QueueHandle - queue to delete
Event - Event to wait upon.
(HANDLE)-1: The function creates an event and waits on it.
Event : The caller passes an event. The function marks the queue for deletion, but does not wait for all callbacks to complete.
The event is signalled after all callbacks have completed.
NULL : The function is non-blocking. The function marks the queue for deletion, but does not wait for all callbacks to complete.
Return Value:
STATUS_SUCCESS - All timer callbacks have completed.
STATUS_PENDING - Non-Blocking call. Some timer callbacks associated with timers in this queue may not have completed.
--*/
{
NTSTATUS Status;
LARGE_INTEGER TimeOut ;
PRTLP_EVENT CompletionEvent = NULL ;
PRTLP_TIMER_QUEUE Queue = (PRTLP_TIMER_QUEUE)QueueHandle ;
if (!Queue) {
ASSERT( FALSE ) ;
return STATUS_INVALID_PARAMETER ;
}
CHECK_DEL_SIGNATURE( Queue ) ;
SET_DEL_SIGNATURE( Queue ) ;
#if DBG1
Queue->ThreadId2 = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
if (DPRN0)
DbgPrint("\n<%d:%d> Queue Delete(Queue:%x Event:%x by Thread:<%x:%x>)\n\n",
Queue->DbgId, Queue->RefCount, (ULONG_PTR)Queue, (ULONG_PTR)Event,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
if (Event == (HANDLE)-1 ) {
// Get an event from the event cache
CompletionEvent = RtlpGetWaitEvent () ;
if (!CompletionEvent) {
return STATUS_NO_MEMORY ;
}
}
Queue->CompletionEvent = CompletionEvent ? CompletionEvent->Handle : Event ;
// once this flag is set, no timer will be fired
ACQUIRE_GLOBAL_TIMER_LOCK();
Queue->State |= STATE_DONTFIRE;
RELEASE_GLOBAL_TIMER_LOCK();
// queue an APC
Status = NtQueueApcThread(TimerThreadHandle, (PPS_APC_ROUTINE)RtlpDeleteTimerQueue, (PVOID) QueueHandle, NULL, NULL);
if (! NT_SUCCESS(Status)) {
RtlpFreeWaitEvent( CompletionEvent ) ;
return Status ;
}
if (CompletionEvent) {
// wait for Event to be fired. Return if the thread has been killed.
#if DBG1
if (DPRN0)
DbgPrint("<%x> Queue delete waiting Thread<%d:%d>\n\n",
(ULONG_PTR)Queue,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
Status = RtlpWaitForEvent( CompletionEvent->Handle, TimerThreadHandle ) ;
#if DBG1
if (DPRN0)
DbgPrint("<%x> Queue delete completed\n\n", (ULONG_PTR) Queue) ;
#endif
RtlpFreeWaitEvent( CompletionEvent ) ;
return NT_SUCCESS( Status ) ? STATUS_SUCCESS : Status ;
} else {
return STATUS_PENDING ;
}
}
NTSTATUS RtlCreateTimer(
IN HANDLE TimerQueueHandle,
OUT HANDLE *Handle,
IN WAITORTIMERCALLBACKFUNC Function,
IN PVOID Context,
IN ULONG DueTime,
IN ULONG Period,
IN ULONG Flags
)
/*++
Routine Description:
This routine puts a timer request in the queue identified in by TimerQueueHandle.
The timer request can be one shot or periodic.
Arguments:
TimerQueueHandle - Handle identifying the timer queue in which to insert the timer request.
Handle - Specifies a location to return a handle to this timer request
Function - Routine that is called when the timer fires
Context - Opaque pointer passed in as an argument to WorkerProc
DueTime - Specifies the time in milliseconds after which the timer fires.
Period - Specifies the period of the timer in milliseconds. This should be 0 for one shot requests.
Flags - Can be one of:
WT_EXECUTEINTIMERTHREAD - if WorkerProc should be invoked in the wait thread it this should only be used for small routines.
WT_EXECUTELONGFUNCTION - if WorkerProc can possibly block for a long time.
WT_EXECUTEINIOTHREAD - if WorkerProc should be invoked in IO worker thread
Return Value:
NTSTATUS - Result code from call. The following are returned
STATUS_SUCCESS - Timer Queue created successfully.
STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation.
--*/
{
NTSTATUS Status;
PRTLP_TIMER Timer ;
Timer = (PRTLP_TIMER) RtlpAllocateTPHeap (sizeof (RTLP_TIMER), HEAP_ZERO_MEMORY) ;
if (Timer == NULL) {
return STATUS_NO_MEMORY ;
}
// Initialize the allocated timer
Timer->DeltaFiringTime = DueTime ;
Timer->Queue = (PRTLP_TIMER_QUEUE) TimerQueueHandle ;
Timer->RefCount = 1 ;
Timer->Flags = Flags ;
Timer->Function = Function ;
Timer->Context = Context ;
//todo:remove below
Timer->Period = (Period == -1) ? 0 : Period;
InitializeListHead( &Timer->TimersToFireList ) ;
SET_SIGNATURE( Timer ) ;
#if DBG1
Timer->DbgId = ++ Timer->Queue->NextDbgId ;
Timer->ThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
if (DPRN1)
DbgPrint("\n<%d:%d:%d> Timer: created by Thread:<%x:%x>\n\n",
Timer->Queue->DbgId,
Timer->DbgId,
1,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
*Handle = Timer ;
InterlockedIncrement( &((PRTLP_TIMER_QUEUE)TimerQueueHandle)->RefCount ) ;// Increment the total number of timers in the queue
Status = NtQueueApcThread(TimerThreadHandle, (PPS_APC_ROUTINE)RtlpAddTimer, (PVOID)Timer, NULL, NULL) ;// Queue APC to timer thread
return Status ;
}
NTSTATUS RtlUpdateTimer(IN HANDLE TimerQueueHandle, IN HANDLE Timer, IN ULONG DueTime, IN ULONG Period)
/*++
Routine Description:
This routine updates the timer
Arguments:
TimerQueueHandle - Handle identifying the queue in which the timer to be updated exists
Timer - Specifies a handle to the timer which needs to be updated
DueTime - Specifies the time in milliseconds after which the timer fires.
Period - Specifies the period of the timer in milliseconds. This should be 0 for one shot requests.
Return Value:
NTSTATUS - Result code from call. The following are returned
STATUS_SUCCESS - Timer updated successfully.
--*/
{
NTSTATUS Status;
PRTLP_TIMER TmpTimer ;
if (!TimerQueueHandle || !Timer) {
ASSERT( FALSE ) ;
return STATUS_INVALID_PARAMETER ;
}
CHECK_DEL_SIGNATURE( (PRTLP_TIMER)Timer ) ;
TmpTimer = (PRTLP_TIMER) RtlpAllocateTPHeap (sizeof (RTLP_TIMER), 0) ;
if (TmpTimer == NULL) {
return STATUS_NO_MEMORY ;
}
TmpTimer->DeltaFiringTime = DueTime;
//todo:remove below
if (Period==-1) Period = 0;
TmpTimer->Period = Period ;
#if DBG1
((PRTLP_TIMER)Timer)->ThreadId2 = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
#endif
#if DBG1
if (DPRN1)
DbgPrint("<%d:%d:%d> Timer: updated by Thread:<%x:%x>\n\n",
((PRTLP_TIMER)Timer)->Queue->DbgId,
((PRTLP_TIMER)Timer)->DbgId, ((PRTLP_TIMER)Timer)->RefCount,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
// queue APC to update timer
Status = NtQueueApcThread (
TimerThreadHandle,
(PPS_APC_ROUTINE)RtlpUpdateTimer,
(PVOID)Timer, //Actual timer
(PVOID)TmpTimer,
NULL
);
return Status ;
}
NTSTATUS RtlDeleteTimer (IN HANDLE TimerQueueHandle, IN HANDLE TimerToCancel, IN HANDLE Event)
/*++
Routine Description:
This routine cancels the timer
Arguments:
TimerQueueHandle - Handle identifying the queue from which to delete timer
TimerToCancel - Handle identifying the timer to cancel
Event - Event to be signalled when the timer is deleted
(HANDLE)-1: The function creates an event and waits on it.
Event : The caller passes an event. The function marks the timer for deletion, but does not wait for all callbacks to complete.
The event is signalled after all callbacks have completed.
NULL : The function is non-blocking. The function marks the timer for deletion, but does not wait for all callbacks to complete.
Return Value:
NTSTATUS - Result code from call. The following are returned
STATUS_SUCCESS - Timer cancelled. No pending callbacks.
STATUS_PENDING - Timer cancelled. Some callbacks still not completed.
--*/
{
NTSTATUS Status;
PRTLP_EVENT CompletionEvent = NULL ;
PRTLP_TIMER Timer = (PRTLP_TIMER) TimerToCancel ;
ULONG TimerRefCount ;
#if DBG1
ULONG QueueDbgId ;
#endif
if (!TimerQueueHandle || !TimerToCancel) {
ASSERT( FALSE ) ;
return STATUS_INVALID_PARAMETER ;
}
#if DBG1
QueueDbgId = Timer->Queue->DbgId ;
#endif
CHECK_DEL_SIGNATURE( Timer ) ;
SET_DEL_SIGNATURE( Timer ) ;
CHECK_DEL_SIGNATURE( (PRTLP_TIMER_QUEUE)TimerQueueHandle ) ;
if (Event == (HANDLE)-1 ) {
// Get an event from the event cache
CompletionEvent = RtlpGetWaitEvent () ;
if (!CompletionEvent) {
return STATUS_NO_MEMORY ;
}
}
#if DBG1
Timer->ThreadId2 = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
#endif
#if DBG1
if (DPRN0)
DbgPrint("\n<%d:%d:%d> Timer: Cancel:(Timer:%x, Event:%x)\n\n",
Timer->Queue->DbgId,
Timer->DbgId,
Timer->RefCount,
(ULONG_PTR)Timer,
(ULONG_PTR)Event) ;
#endif
Timer->CompletionEvent = CompletionEvent ? CompletionEvent->Handle : Event ;
ACQUIRE_GLOBAL_TIMER_LOCK();
Timer->State |= STATE_DONTFIRE ;
TimerRefCount = Timer->RefCount ;
RELEASE_GLOBAL_TIMER_LOCK();
Status = NtQueueApcThread(TimerThreadHandle, (PPS_APC_ROUTINE)RtlpCancelTimer, (PVOID)TimerToCancel, NULL, NULL);
if (! NT_SUCCESS(Status)) {
RtlpFreeWaitEvent( CompletionEvent ) ;
return Status ;
}
if ( CompletionEvent ) {
// wait for the event to be signalled
#if DBG1
if (DPRN0)
DbgPrint("<%d> Timer: %x: Cancel waiting Thread<%d:%d>\n\n",
QueueDbgId,
(ULONG_PTR)Timer,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
#endif
Status = RtlpWaitForEvent( CompletionEvent->Handle, TimerThreadHandle ) ;
#if DBG1
if (DPRN0)
DbgPrint("<%d> Timer: %x: Cancel waiting done\n\n", QueueDbgId, (ULONG_PTR)Timer) ;
#endif
RtlpFreeWaitEvent( CompletionEvent ) ;
return NT_SUCCESS(Status) ? STATUS_SUCCESS : Status ;
} else {
return (TimerRefCount > 1) ? STATUS_PENDING : STATUS_SUCCESS;
}
}
NTSTATUS NTAPI RtlSetThreadPoolStartFunc(PRTLP_START_THREAD StartFunc, PRTLP_EXIT_THREAD ExitFunc)
/*++
Routine Description:
This routine sets the thread pool's thread creation function.
This is not thread safe, because it is intended solely for kernel32 to call for processes that aren't csrss/smss.
Arguments:
StartFunc - Function to create a new thread
--*/
{
RtlpStartThreadFunc = StartFunc ;
RtlpExitThreadFunc = ExitFunc ;
return STATUS_SUCCESS ;
}
NTSTATUS RtlThreadPoolCleanup (ULONG Flags)
/*++
Routine Description:
This routine cleans up the thread pool.
Return Value:
STATUS_SUCCESS : if none of the components are in use.
STATUS_UNSUCCESSFUL : if some components are still in use.
--*/
{
BOOLEAN Cleanup ;
PLIST_ENTRY Node ;
ULONG i ;
HANDLE TmpHandle ;
return STATUS_UNSUCCESSFUL;
// cleanup timer thread
IS_COMPONENT_INITIALIZED(StartedTimerInitialization, CompletedTimerInitialization, Cleanup ) ;
if ( Cleanup ) {
ACQUIRE_GLOBAL_TIMER_LOCK() ;
if (NumTimerQueues != 0 ) {
ASSERTMSG( FALSE, "Trying to deinitialize ThreadPool when timers exist\n" ) ;
RELEASE_GLOBAL_TIMER_LOCK() ;
return STATUS_UNSUCCESSFUL ;
}
NtQueueApcThread(TimerThreadHandle, (PPS_APC_ROUTINE)RtlpThreadCleanup, NULL, NULL, NULL);
NtClose( TimerThreadHandle ) ;
TimerThreadHandle = NULL ;
RELEASE_GLOBAL_TIMER_LOCK() ;
}
// cleanup wait threads
IS_COMPONENT_INITIALIZED(StartedWaitInitialization, CompletedWaitInitialization, Cleanup ) ;
if ( Cleanup ) {
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB ;
ACQUIRE_GLOBAL_WAIT_LOCK() ;
// Queue an APC to all Wait Threads
for (Node = WaitThreads.Flink ; Node != &WaitThreads ; Node = Node->Flink)
{
ThreadCB = CONTAINING_RECORD(Node, RTLP_WAIT_THREAD_CONTROL_BLOCK, WaitThreadsList) ;
if ( ThreadCB->NumWaits != 0 ) {
ASSERTMSG( FALSE, "Cannot cleanup ThreadPool. Registered Wait events exist." ) ;
RELEASE_GLOBAL_WAIT_LOCK( ) ;
return STATUS_UNSUCCESSFUL ;
}
RemoveEntryList( &ThreadCB->WaitThreadsList ) ;
TmpHandle = ThreadCB->ThreadHandle ;
NtQueueApcThread(ThreadCB->ThreadHandle, (PPS_APC_ROUTINE)RtlpThreadCleanup, NULL, NULL, NULL);
NtClose( TmpHandle ) ;
}
RELEASE_GLOBAL_WAIT_LOCK( ) ;
}
// cleanup worker threads
IS_COMPONENT_INITIALIZED( StartedWorkerInitialization, CompletedWorkerInitialization, Cleanup ) ;
if ( Cleanup ) {
RtlEnterCriticalSection (&WorkerCriticalSection) ;
if ( (NumWorkRequests != 0) || (NumIOWorkRequests != 0) ) {
ASSERTMSG( FALSE, "Cannot cleanup ThreadPool. Work requests pending." ) ;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
return STATUS_UNSUCCESSFUL ;
}
// queue a cleanup for each worker thread
for (i = 0 ; i < NumWorkerThreads ; i ++ ) {
NtSetIoCompletion (WorkerCompletionPort, RtlpThreadCleanup, NULL, STATUS_SUCCESS, 0);
}
// queue an apc to cleanup all IO worker threads
for (Node = IOWorkerThreads.Flink ; Node != &IOWorkerThreads ; Node = Node->Flink )
{
PRTLP_IOWORKER_TCB ThreadCB ;
ThreadCB = CONTAINING_RECORD (Node, RTLP_IOWORKER_TCB, List) ;
RemoveEntryList( &ThreadCB->List) ;
TmpHandle = ThreadCB->ThreadHandle ;
NtQueueApcThread(ThreadCB->ThreadHandle, (PPS_APC_ROUTINE)RtlpThreadCleanup, NULL, NULL, NULL);
NtClose( TmpHandle ) ;
}
NumWorkerThreads = NumIOWorkerThreads = 0 ;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
}
return STATUS_SUCCESS ;
}
// Private Functions
// Worker functions
NTSTATUS RtlpQueueWorkerRequest (WORKERCALLBACKFUNC Function, PVOID Context, ULONG Flags)
/*++
Routine Description:
This routine queues up the request to be executed in a worker thread.
Arguments:
Function - Routine that is called by the worker thread
Context - Opaque pointer passed in as an argument to WorkerProc
Flags - flags passed to RtlQueueWorkItem
--*/
{
NTSTATUS Status ;
PRTLP_WORK WorkEntry ;
// Increment the outstanding work request counter
InterlockedIncrement (&NumWorkRequests) ;
if (Flags & WT_EXECUTELONGFUNCTION) {
InterlockedIncrement( & NumLongWorkRequests ) ;
}
WorkEntry = (PRTLP_WORK) RtlpForceAllocateTPHeap ( sizeof (RTLP_WORK), HEAP_ZERO_MEMORY) ;
WorkEntry->Function = Function ;
WorkEntry->Flags = Flags ;
if (Flags & WT_EXECUTEINPERSISTENTTHREAD) {
// Queue APC to timer thread
Status = NtQueueApcThread(TimerThreadHandle, (PPS_APC_ROUTINE)RtlpExecuteWorkerRequest, (PVOID) STATUS_SUCCESS, (PVOID) Context, (PVOID) WorkEntry) ;
} else {
Status = NtSetIoCompletion (WorkerCompletionPort, RtlpExecuteWorkerRequest, (PVOID) WorkEntry, STATUS_SUCCESS, (ULONG_PTR)Context);
}
if ( ! NT_SUCCESS(Status) ) {
InterlockedDecrement (&NumWorkRequests) ;
if (Flags && WT_EXECUTELONGFUNCTION) {
InterlockedDecrement( &NumLongWorkRequests ) ;
}
RtlpFreeTPHeap( WorkEntry ) ;
ASSERT(FALSE) ;
#if DBG
DbgPrint("ERROR!! Thread Pool (RtlQeueuWorkItem): could not queue work item\n");
#endif
}
return Status ;
}
VOID RtlpExecuteWorkerRequest (
NTSTATUS Status, //not used
PVOID Context,
PVOID WorkContext
)
/*++
Routine Description:
This routine executes a work item.
Arguments:
Context - contains context to be passed to the callback function.
WorkContext - contains callback function ptr and flags
Notes:
This function executes in a worker thread or a timer thread if WT_EXECUTEINTIMERTHREAD flag is set.
--*/
{
PRTLP_WORK WorkEntry = (PRTLP_WORK) WorkContext;
#if (DBG1)
DBG_SET_FUNCTION( WorkEntry->Function, Context ) ;
#endif
try {
((WORKERCALLBACKFUNC) WorkEntry->Function) ( Context ) ;
} except (EXCEPTION_EXECUTE_HANDLER) {
// ASSERT(FALSE);
}
InterlockedDecrement( &NumWorkRequests ) ;
if (WorkEntry->Flags & WT_EXECUTELONGFUNCTION) {
InterlockedDecrement( &NumLongWorkRequests ) ;
}
RtlpFreeTPHeap( WorkEntry ) ;
}
NTSTATUS RtlpQueueIOWorkerRequest (WORKERCALLBACKFUNC Function, PVOID Context, ULONG Flags)
/*++
Routine Description:
This routine queues up the request to be executed in an IO worker thread.
Arguments:
Function - Routine that is called by the worker thread
Context - Opaque pointer passed in as an argument to WorkerProc
--*/
{
NTSTATUS Status ;
PRTLP_IOWORKER_TCB TCB ;
BOOLEAN LongFunction = (Flags & WT_EXECUTELONGFUNCTION) ? TRUE : FALSE ;
PLIST_ENTRY ple ;
if (Flags & WT_EXECUTEINPERSISTENTIOTHREAD) {
if (!PersistentIOTCB) {
for (ple=IOWorkerThreads.Flink; ple!=&IOWorkerThreads; ple=ple->Flink) {
TCB = CONTAINING_RECORD (ple, RTLP_IOWORKER_TCB, List) ;
if (! TCB->LongFunctionFlag)
break;
}
if (ple == &IOWorkerThreads) {
// ASSERT(FALSE);
return STATUS_NO_MEMORY;
}
PersistentIOTCB = TCB ;
TCB->Flags |= WT_EXECUTEINPERSISTENTIOTHREAD ;
} else {
TCB = PersistentIOTCB ;
}
} else {
for (ple=IOWorkerThreads.Flink; ple!=&IOWorkerThreads; ple=ple->Flink) {
TCB = CONTAINING_RECORD (ple, RTLP_IOWORKER_TCB, List) ;
// do not queue to the thread if it is executing a long function, or
// if you are queueing a long function and the thread is a persistent thread
if (! TCB->LongFunctionFlag && (! ((TCB->Flags&WT_EXECUTEINPERSISTENTIOTHREAD) && (Flags&WT_EXECUTELONGFUNCTION)))) {
break ;
}
}
if ((ple == &IOWorkerThreads) && (NumIOWorkerThreads<1)) {
#if DBG
DbgPrint("ThreadPool:ntdll.dll: Out of memory. "
"Could not execute IOWorkItem(%x)\n", (ULONG_PTR)Function);
#endif
return STATUS_NO_MEMORY;
}
else {
ple = IOWorkerThreads.Flink;
TCB = CONTAINING_RECORD (ple, RTLP_IOWORKER_TCB, List) ;
// treat it as a short function so that counters work fine.
LongFunction = FALSE;
}
// In order to implement "fair" assignment of work items between IO worker threads
// each time remove the entry and reinsert at back.
RemoveEntryList (&TCB->List) ;
InsertTailList (&IOWorkerThreads, &TCB->List) ;
}
// Increment the outstanding work request counter
InterlockedIncrement (&NumIOWorkRequests) ;
if (LongFunction) {
InterlockedIncrement( &NumLongIOWorkRequests ) ;
TCB->LongFunctionFlag = TRUE ;
}
// Queue an APC to the IoWorker Thread
Status = NtQueueApcThread(
TCB->ThreadHandle,
LongFunction? (PPS_APC_ROUTINE)RtlpExecuteLongIOWorkItem: (PPS_APC_ROUTINE)RtlpExecuteIOWorkItem,
(PVOID)Function,
Context,
TCB
);
if (! NT_SUCCESS( Status ) ) {
InterlockedDecrement( &NumIOWorkRequests ) ;
if (LongFunction)
InterlockedDecrement( &NumLongIOWorkRequests ) ;
}
return Status ;
}
NTSTATUS RtlpStartWorkerThread ()
/*++
Routine Description:
This routine starts a regular worker thread
Return Value:
NTSTATUS error codes resulting from attempts to create a thread STATUS_SUCCESS
--*/
{
HANDLE ThreadHandle ;
ULONG CurrentTickCount ;
NTSTATUS Status ;
Status = RtlpStartThreadFunc (RtlpWorkerThread, &ThreadHandle) ;// Create worker thread
if (Status == STATUS_SUCCESS ) {
LastThreadCreationTickCount = NtGetTickCount() ;// Update the time at which the current thread was created
InterlockedIncrement(&NumWorkerThreads) ;// Increment the count of the thread type created
NtClose (ThreadHandle) ;// Close Thread handle, we dont need it.
} else {// Thread creation failed.
if (NumWorkerThreads == 0) {// If there is even one thread present do not return failure - else queue the request anyway.
return Status ;
}
}
return STATUS_SUCCESS ;
}
NTSTATUS RtlpStartIOWorkerThread ()
/*++
Routine Description:
This routine starts an I/O worker thread
Return Value:
NTSTATUS error codes resulting from attempts to create a thread STATUS_SUCCESS
--*/
{
HANDLE ThreadHandle ;
ULONG CurrentTickCount ;
NTSTATUS Status ;
Status = RtlpStartThreadFunc (RtlpIOWorkerThread, &ThreadHandle) ;// Create worker thread
if (Status == STATUS_SUCCESS ) {
NtClose( ThreadHandle ) ;
LastThreadCreationTickCount = NtGetTickCount() ;// Update the time at which the current thread was created
} else {// Thread creation failed. If there is even one thread present do not return failure since we can still service the work request.
if (NumIOWorkerThreads == 0) {
return Status ;
}
}
return STATUS_SUCCESS ;
}
VOID RtlpWorkerThreadTimerCallback(PVOID Context, BOOLEAN NotUsed)
/*++
Routine Description:
This routine checks if new worker thread has to be created
Arguments:
None
--*/
{
IO_COMPLETION_BASIC_INFORMATION Info ;
BOOLEAN bCreateThread = FALSE ;
NTSTATUS Status ;
ULONG QueueLength, Threshold, ShortWorkRequests ;
Status = NtQueryIoCompletion(WorkerCompletionPort, IoCompletionBasicInformation, &Info, sizeof(Info), NULL) ;
if (!NT_SUCCESS(Status))
return ;
QueueLength = Info.Depth ;
if (!QueueLength) {
OldTotalExecutedWorkRequests = TotalExecutedWorkRequests ;
return ;
}
RtlEnterCriticalSection (&WorkerCriticalSection) ;
// if there are queued work items and no new work items have been scheduled in the last 30 seconds then create a new thread.
// this will take care of deadlocks.
// this will create a problem only if some thread is running for a long time
if (TotalExecutedWorkRequests == OldTotalExecutedWorkRequests) {
bCreateThread = TRUE ;
}
// if there are a lot of queued work items, then create a new thread
{
ULONG NumEffWorkerThreads = (NumWorkerThreads - NumLongWorkRequests) ;
ULONG ShortWorkRequests ;
Threshold = (NumWorkerThreads < MAX_WORKER_THREADS ?
(NumEffWorkerThreads < 7 ? NumEffWorkerThreads*NumEffWorkerThreads : NEW_THREAD_THRESHOLD * NumEffWorkerThreads ) :
0xffffffff) ;
ShortWorkRequests = QueueLength + NumExecutingWorkerThreads - NumLongWorkRequests ;
if (ShortWorkRequests > Threshold)
{
bCreateThread = TRUE ;
}
}
if (bCreateThread) {
RtlpStartWorkerThread () ;
}
OldTotalExecutedWorkRequests = TotalExecutedWorkRequests ;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
}
NTSTATUS RtlpInitializeWorkerThreadPool ()
/*++
Routine Description:
This routine initializes all aspects of the thread pool.
Return Value:
None
--*/
{
NTSTATUS Status = STATUS_SUCCESS ;
LARGE_INTEGER TimeOut ;
// Initialize the timer component if it hasnt been done already
if (CompletedTimerInitialization != 1) {
Status = RtlpInitializeTimerThreadPool () ;
if ( !NT_SUCCESS(Status) )
return Status ;
}
// In order to avoid an explicit RtlInitialize() function to initialize the thread pool
// we use StartedInitialization and CompletedInitialization to provide us the necessary
// synchronization to avoid multiple threads from initializing the thread pool.
// This scheme does not work if RtlInitializeCriticalSection() fails - but in this case the
// caller has no choices left.
if (!InterlockedExchange(&StartedWorkerInitialization, 1L)) {
if (CompletedWorkerInitialization)
InterlockedExchange( &CompletedWorkerInitialization, 0 ) ;
do {
// Initialize Critical Sections
Status = RtlInitializeCriticalSection( &WorkerCriticalSection );
if (!NT_SUCCESS(Status))
break ;
InitializeListHead (&IOWorkerThreads) ;
{
SYSTEM_BASIC_INFORMATION BasicInfo;
// get number of processors
Status = NtQuerySystemInformation (SystemBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL) ;
if ( !NT_SUCCESS(Status) ) {
BasicInfo.NumberOfProcessors = 1 ;
}
// Create completion port used by worker threads
Status = NtCreateIoCompletion (&WorkerCompletionPort, IO_COMPLETION_ALL_ACCESS, NULL, BasicInfo.NumberOfProcessors);
if (!NT_SUCCESS(Status))
break ;
}
} while ( FALSE ) ;
if (!NT_SUCCESS(Status) ) {
ASSERT ( Status == STATUS_SUCCESS ) ;
StartedWorkerInitialization = 0 ;
InterlockedExchange( &CompletedWorkerInitialization, ~0 ) ;
return Status ;
}
InterlockedExchange (&CompletedWorkerInitialization, 1L) ;// Signal that initialization has completed
} else {
LARGE_INTEGER Timeout ;
// Sleep 1 ms and see if the other thread has completed initialization
ONE_MILLISECOND_TIMEOUT(TimeOut) ;
while (!(volatile ULONG) CompletedWorkerInitialization) {
NtDelayExecution (FALSE, &TimeOut) ;
}
if (CompletedWorkerInitialization != 1)
return STATUS_NO_MEMORY ;
}
// create timer for worker thread. it should be created outside any worker
// lock and the above wait loop. It should not make the RtlpInitializeWorkerThread
// call blocking on a timer thread. so queue a work item.
if (NT_SUCCESS(Status) && (InterlockedIncrement(&WorkerThreadTimerQueueInit) == 1) )
{
RtlQueueWorkItem(RtlpWorkerThreadInitializeTimers, NULL, 0);
}
return NT_SUCCESS(Status) ? STATUS_SUCCESS : Status ;
}
VOID RtlpWorkerThreadInitializeTimers(PVOID Context)
{
NTSTATUS Status;
// create a timer that will fire every 30 sec and check if new thread
// should be created
Status = RtlCreateTimerQueue(&WorkerThreadTimerQueue) ;
if (!NT_SUCCESS(Status))
return ;
Status = RtlCreateTimer(WorkerThreadTimerQueue, &WorkerThreadTimer, RtlpWorkerThreadTimerCallback, NULL, 30000, 30000, WT_EXECUTEINTIMERTHREAD) ;
}
LONG RtlpWorkerThread (PVOID Initialized)
/*++
Routine Description:
All non I/O worker threads execute in this routine.
Worker thread will try to terminate when it has not serviced a request for
STARTING_WORKER_SLEEP_TIME +
STARTING_WORKER_SLEEP_TIME << 1 +
...
STARTING_WORKER_SLEEP_TIME << MAX_WORKER_SLEEP_TIME_EXPONENT
Arguments:
Initialized - Set to 1 when we are initialized
--*/
{
NTSTATUS Status ;
PVOID WorkerProc ;
PVOID Context ;
IO_STATUS_BLOCK IoSb ;
ULONG SleepTime ;
LARGE_INTEGER TimeOut ;
ULONG Terminate ;
PVOID Overlapped ;
// We are all initialized now. Notify the starter to queue the task.
InterlockedExchange ((ULONG *)Initialized, 1L) ;
// Set default sleep time for 40 seconds. This time is doubled each time a timeout
// occurs after which the thread terminates
#define WORKER_IDLE_TIMEOUT 40000 // In Milliseconds
#define MAX_WORKER_SLEEP_TIME_EXPONENT 4
SleepTime = WORKER_IDLE_TIMEOUT ;
// Loop servicing I/O completion requests
for ( ; ; ) {
TimeOut.QuadPart = Int32x32To64( SleepTime, -10000 ) ;
Status = NtRemoveIoCompletion(WorkerCompletionPort, (PVOID) &WorkerProc, &Overlapped, &IoSb, &TimeOut) ;
if (Status == STATUS_SUCCESS) {
TotalExecutedWorkRequests ++ ;//interlocked op not req
InterlockedIncrement(&NumExecutingWorkerThreads) ;
// Call the work item.
// If IO APC, context1 contains number of IO bytes transferred, and context2
// contains the overlapped structure.
// If (IO)WorkerFunction, context1 contains the actual WorkerFunction to be
// executed and context2 contains the actual context
Context = (PVOID) IoSb.Information ;
try {
((APC_CALLBACK_FUNCTION)WorkerProc) (
IoSb.Status,
Context, // Number of IO bytes transferred
Overlapped // Overlapped structure
) ;
} except (EXCEPTION_EXECUTE_HANDLER) {
ASSERT(FALSE);
}
SleepTime = WORKER_IDLE_TIMEOUT ;
InterlockedDecrement(&NumExecutingWorkerThreads) ;
} else if (Status == STATUS_TIMEOUT) {
// NtRemoveIoCompletion timed out. Check to see if have hit our limit on waiting. If so terminate.
Terminate = FALSE ;
RtlEnterCriticalSection (&WorkerCriticalSection) ;
// The thread terminates if there are > 1 threads and the queue is small
// OR if there is only 1 thread and there is no request pending
if (NumWorkerThreads > 1) {
ULONG NumEffWorkerThreads = (NumWorkerThreads - NumLongWorkRequests) ;
if (NumEffWorkerThreads == 0) {
Terminate = FALSE ;
} else if (SleepTime >= (WORKER_IDLE_TIMEOUT << MAX_WORKER_SLEEP_TIME_EXPONENT)) {
// have been idle for very long time. terminate irrespective of number of
// work items. (This is useful when the set of runnable threads is taking
// care of all the work items being queued). dont terminate if
// (NumEffWorkerThreads == 1)
if (NumEffWorkerThreads > 1)
Terminate = TRUE ;
} else {
ULONG Threshold ;
// Check if we need to shrink worker thread pool
Threshold = NumEffWorkerThreads < 7 ?
NumEffWorkerThreads*(NumEffWorkerThreads-1) :
NEW_THREAD_THRESHOLD * (NumEffWorkerThreads-1);
if (NumWorkRequests-NumLongWorkRequests < Threshold) {
Terminate = TRUE ;
} else {
Terminate = FALSE ;
SleepTime <<= 1 ;
}
}
} else {
if ( (NumMinWorkerThreads == 0) && (NumWorkRequests == 0) ) {
// delay termination of last thread
if (SleepTime < (WORKER_IDLE_TIMEOUT << MAX_WORKER_SLEEP_TIME_EXPONENT)) {
SleepTime <<= 1 ;
Terminate = FALSE ;
}
else {
Terminate = TRUE ;
}
} else {
Terminate = FALSE ;
}
}
if (Terminate) {
THREAD_BASIC_INFORMATION ThreadInfo;
ULONG IsIoPending ;
HANDLE CurThreadHandle ;
Status = NtDuplicateObject(NtCurrentProcess(),
NtCurrentThread(),
NtCurrentProcess(),
&CurThreadHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS) ;
ASSERT (Status == STATUS_SUCCESS) ;
Terminate = FALSE ;
Status = NtQueryInformationThread( CurThreadHandle, ThreadIsIoPending, &IsIoPending, sizeof( IsIoPending ), NULL);
if (NT_SUCCESS( Status )) {
if (! IsIoPending )
Terminate = TRUE ;
}
NtClose( CurThreadHandle ) ;
}
if (Terminate) {
InterlockedDecrement (&NumWorkerThreads) ;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
RtlpExitThreadFunc( 0 );
} else {
// This is the condition where a request was queued *after* the
// thread woke up - ready to terminate because of inactivity.
// In this case dont terminate - service the completion port.
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
}
} else {
ASSERT (FALSE) ;
}
}
return 1 ;
}
LONG RtlpIOWorkerThread (PVOID Initialized)
/*++
Routine Description:
All I/O worker threads execute in this routine. All the work requests execute as APCs in this thread.
Arguments:
Initialized - set to 1 when the initialization has completed
--*/
{
#define IOWORKER_IDLE_TIMEOUT 40000 // In Milliseconds
LARGE_INTEGER TimeOut ;
ULONG SleepTime = IOWORKER_IDLE_TIMEOUT ;
RTLP_IOWORKER_TCB ThreadCB ; // Control Block allocated on the stack
NTSTATUS Status ;
BOOLEAN Terminate ;
// Initialize thread control block and insert it into list of IOWorker Threads
Status = NtDuplicateObject(NtCurrentProcess(), NtCurrentThread(), NtCurrentProcess(), &ThreadCB.ThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS) ;
if (!NT_SUCCESS(Status)) {
ASSERT (FALSE) ;
InterlockedExchange ((ULONG *)Initialized, (ULONG)~0) ;
return STATUS_NO_MEMORY;
}
InsertHeadList (&IOWorkerThreads, &ThreadCB.List) ;
ThreadCB.Flags = 0 ;
ThreadCB.LongFunctionFlag = FALSE ;
InterlockedIncrement(&NumIOWorkerThreads) ;
// We are all initialized now. Notify the starter to queue the task.
InterlockedExchange ((ULONG *)Initialized, 1L) ;
// Sleep alertably so that all the activity can take place in APCs
for ( ; ; ) {
// Set timeout for IdleTimeout
TimeOut.QuadPart = Int32x32To64( SleepTime, -10000 ) ;
Status = NtDelayExecution (TRUE, &TimeOut) ;
if (Status != STATUS_SUCCESS) {// Status is STATUS_SUCCESS only when it has timed out
continue ;
}
// idle timeout. check if you can terminate the thread
Terminate = FALSE ;
RtlEnterCriticalSection (&WorkerCriticalSection) ;
// dont terminate if it is a persistent thread
if (ThreadCB.Flags & WT_EXECUTEINPERSISTENTIOTHREAD) {
TimeOut.LowPart = 0x0;
TimeOut.HighPart = 0x80000000;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
continue ;
}
// The thread terminates if there are > 1 threads and the queue is small
// OR if there is only 1 thread and there is no request pending
if (NumIOWorkerThreads > 1) {
ULONG NumEffIOWorkerThreads = NumIOWorkerThreads - NumLongIOWorkRequests ;
ULONG Threshold ;
if (NumEffIOWorkerThreads == 0) {
Terminate = FALSE ;
} else {
// Check if we need to shrink worker thread pool
Threshold = NEW_THREAD_THRESHOLD * (NumEffIOWorkerThreads-1);
if (NumIOWorkRequests-NumLongIOWorkRequests < Threshold) {
Terminate = TRUE ;
} else {
Terminate = FALSE ;
SleepTime <<= 1 ;
}
}
} else {
if (NumIOWorkRequests == 0) {
// delay termination of last thread
if (SleepTime < 4*IOWORKER_IDLE_TIMEOUT) {
SleepTime <<= 1 ;
Terminate = FALSE ;
} else {
Terminate = TRUE ;
}
} else {
Terminate = FALSE ;
}
}
// terminate only if no io is pending
if (Terminate) {
NTSTATUS Status;
THREAD_BASIC_INFORMATION ThreadInfo;
ULONG IsIoPending ;
Terminate = FALSE ;
Status = NtQueryInformationThread( ThreadCB.ThreadHandle, ThreadIsIoPending, &IsIoPending, sizeof( IsIoPending ), NULL);
if (NT_SUCCESS( Status )) {
if (! IsIoPending )
Terminate = TRUE ;
}
}
if (Terminate) {
InterlockedDecrement (&NumIOWorkerThreads) ;
RemoveEntryList (&ThreadCB.List) ;
NtClose( ThreadCB.ThreadHandle ) ;
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
RtlpExitThreadFunc( 0 );
} else {
// This is the condition where a request was queued *after* the
// thread woke up - ready to terminate because of inactivity.
// In this case dont terminate - service the completion port.
RtlLeaveCriticalSection (&WorkerCriticalSection) ;
}
}
return 0 ; // Keep compiler happy
}
VOID RtlpExecuteLongIOWorkItem (PVOID Function, PVOID Context, PVOID ThreadCB)
/*++
Routine Description:
Executes an IO Work function. RUNs in a APC in the IO Worker thread.
Arguments:
Function - Worker function to call
Context - Argument for the worker function.
NotUsed - Argument is not used in this function.
--*/
{
#if (DBG1)
DBG_SET_FUNCTION( Function, Context ) ;
#endif
// Invoke the function
try {
((WORKERCALLBACKFUNC) Function)((PVOID)Context) ;
} except (EXCEPTION_EXECUTE_HANDLER) {
ASSERT(FALSE);
}
((PRTLP_IOWORKER_TCB)ThreadCB)->LongFunctionFlag = FALSE ;
InterlockedDecrement (&NumIOWorkRequests) ;// Decrement pending IO requests count
InterlockedDecrement (&NumLongIOWorkRequests ) ;// decrement pending long funcitons
}
VOID RtlpExecuteIOWorkItem (PVOID Function, PVOID Context, PVOID NotUsed)
/*++
Routine Description:
Executes an IO Work function. RUNs in a APC in the IO Worker thread.
Arguments:
Function - Worker function to call
Context - Argument for the worker function.
NotUsed - Argument is not used in this function.
--*/
{
#if (DBG1)
DBG_SET_FUNCTION( Function, Context ) ;
#endif
// Invoke the function
try {
((WORKERCALLBACKFUNC) Function)((PVOID)Context) ;
} except (EXCEPTION_EXECUTE_HANDLER) {
ASSERT(FALSE);
}
InterlockedDecrement (&NumIOWorkRequests) ;// Decrement pending IO requests count
}
NTSTATUS NTAPI RtlpStartThread (PUSER_THREAD_START_ROUTINE Function, HANDLE *ThreadHandle)
/*++
Routine Description:
This routine is used start a new wait thread in the pool.
Arguments:
None
Return Value:
STATUS_SUCCESS - Timer Queue created successfully.
STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation.
--*/
{
NTSTATUS Status ;
ULONG Initialized ;
LARGE_INTEGER TimeOut ;
Initialized = FALSE ;
// Create the first thread. This thread never dies until the process exits
Status = RtlCreateUserThread(
NtCurrentProcess(), // process handle
NULL, // security descriptor
FALSE, // Create suspended?
0L, // ZeroBits: default
0L, // Max stack size: default
0L, // Committed stack size: default
Function, // Function to start in
&Initialized, // Event the thread signals when the thread is ready
ThreadHandle, // Thread handle
NULL // Thread id
);
if ( Status == STATUS_SUCCESS ) {
// Sleep 1 ms and see if the other thread has completed initialization
ONE_MILLISECOND_TIMEOUT(TimeOut) ;
while (!(volatile ULONG) Initialized) {
NtDelayExecution (FALSE, &TimeOut) ;
}
}
return Status ;
}
NTSTATUS RtlpExitThread(NTSTATUS Status)
{
return NtTerminateThread( NtCurrentThread(), Status );
}
// Wait functions
NTSTATUS RtlpInitializeWaitThreadPool ()
/*++
Routine Description:
This routine initializes all aspects of the thread pool.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
LARGE_INTEGER TimeOut ;
// In order to avoid an explicit RtlInitialize() function to initialize the wait thread pool
// we use StartedWaitInitialization and CompletedWait Initialization to provide us the
// necessary synchronization to avoid multiple threads from initializing the thread pool.
// This scheme does not work if RtlInitializeCriticalSection() or NtCreateEvent fails - but in this case the
// caller has not choices left.
if (!InterlockedExchange(&StartedWaitInitialization, 1L)) {
if (CompletedWaitInitialization)
InterlockedExchange (&CompletedWaitInitialization, 0L) ;
// Initialize Critical Section
Status = RtlInitializeCriticalSection( &WaitCriticalSection ) ;
if (! NT_SUCCESS( Status ) ) {
ASSERT ( NT_SUCCESS( Status ) ) ;
StartedWaitInitialization = 0 ;
InterlockedExchange (&CompletedWaitInitialization, ~0) ;
return Status ;
}
InitializeListHead (&WaitThreads); // Initialize global wait threads list
InterlockedExchange (&CompletedWaitInitialization, 1L) ;
} else {
// Sleep 1 ms and see if the other thread has completed initialization
ONE_MILLISECOND_TIMEOUT(TimeOut) ;
while (!(volatile ULONG) CompletedWaitInitialization) {
NtDelayExecution (FALSE, &TimeOut) ;
}
if (CompletedWaitInitialization != 1) {
Status = STATUS_NO_MEMORY ;
}
}
return Status ;
}
LONG RtlpWaitThread (PVOID Initialized)
/*++
Routine Description:
This routine is used for all waits in the wait thread pool
Arguments:
Initialized - This is set to 1 when the thread has initialized
Return Value:
Nothing. The thread never terminates.
--*/
{
ULONG i ; // Used as an index
NTSTATUS Status ;
LARGE_INTEGER TimeOut; // Timeout used for waits
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB ; // Control Block allocated on the stack
#define WAIT_IDLE_TIMEOUT 400000
try {
ThreadCB = (PRTLP_WAIT_THREAD_CONTROL_BLOCK) _alloca(sizeof(RTLP_WAIT_THREAD_CONTROL_BLOCK));
} except (EXCEPTION_EXECUTE_HANDLER) {
ASSERT(FALSE);
InterlockedExchange ((ULONG *)Initialized, (ULONG)~0) ;
return STATUS_NO_MEMORY;
}
// Initialize thread control block
InitializeListHead (&ThreadCB->WaitThreadsList) ;
Status = NtDuplicateObject(NtCurrentProcess(), NtCurrentThread(), NtCurrentProcess(), &ThreadCB->ThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS) ;
if (!NT_SUCCESS(Status)) {
ASSERT (FALSE) ;
InterlockedExchange ((ULONG *)Initialized, (ULONG)~0) ;
return STATUS_NO_MEMORY;
}
ThreadCB->ThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
RtlZeroMemory (&ThreadCB->ActiveWaitArray[0], sizeof (HANDLE) * 64) ;
RtlZeroMemory (&ThreadCB->ActiveWaitPointers[0], sizeof (HANDLE) * 64) ;
// Initialize the timer related fields.
Status = NtCreateTimer(&ThreadCB->TimerHandle, TIMER_ALL_ACCESS, NULL, NotificationTimer) ;
if (! NT_SUCCESS( Status )) {
ASSERT (FALSE);
NtClose(ThreadCB->ThreadHandle) ;
InterlockedExchange ((ULONG *)Initialized, (ULONG)~0) ;
return STATUS_NO_MEMORY;
}
ThreadCB->Firing64BitTickCount = 0 ;
ThreadCB->Current64BitTickCount.QuadPart = NtGetTickCount() ;
// Reset the NT Timer to never fire initially
RtlpResetTimer (ThreadCB->TimerHandle, -1, ThreadCB) ;
InitializeListHead (&ThreadCB->TimerQueue.TimerList) ;
InitializeListHead (&ThreadCB->TimerQueue.UncancelledTimerList) ;
// Initialize the timer blocks
RtlZeroMemory (&ThreadCB->TimerBlocks[0], sizeof (RTLP_TIMER) * 63) ;
InitializeListHead (&ThreadCB->FreeTimerBlocks) ;
for (i = 0 ; i < 63 ; i++) {
InitializeListHead (&(&ThreadCB->TimerBlocks[i])->List) ;
InsertHeadList (&ThreadCB->FreeTimerBlocks, &(&ThreadCB->TimerBlocks[i])->List) ;
}
// Insert this new wait thread in the WaitThreads list. Insert at the head so that the request that caused this thread to be created can find it right away.
InsertHeadList (&WaitThreads, &ThreadCB->WaitThreadsList) ;
// The first wait element is the timer object
ThreadCB->ActiveWaitArray[0] = ThreadCB->TimerHandle ;
ThreadCB->NumActiveWaits = ThreadCB->NumWaits = 1 ;
// till here, the function is running under the global wait lock
// We are all initialized now. Notify the starter to queue the task.
InterlockedExchange ((ULONG *)Initialized, 1) ;
// Loop forever - wait threads never, never die.
for ( ; ; )
{
if (ThreadCB->NumActiveWaits == 1)
TimeOut.QuadPart = Int32x32To64( WAIT_IDLE_TIMEOUT, -10000 ) ;
Status = NtWaitForMultipleObjects (
(CHAR) ThreadCB->NumActiveWaits,
ThreadCB->ActiveWaitArray,
WaitAny,
TRUE, // Wait Alertably
ThreadCB->NumWaits!=1 ? NULL : &TimeOut // Wait forever
) ;
if (Status == STATUS_ALERTED || Status == STATUS_USER_APC) {
continue ;
} else if (Status >= STATUS_WAIT_0 && Status <= STATUS_WAIT_63) {
if (Status == STATUS_WAIT_0) {
RtlpProcessTimeouts (ThreadCB) ;
} else {
// Wait completed call Callback function
RtlpProcessWaitCompletion (ThreadCB->ActiveWaitPointers[Status], Status) ;
}
} else if (Status >= STATUS_ABANDONED_WAIT_0 && Status <= STATUS_ABANDONED_WAIT_63) {
#if DBG
DbgPrint ("RTL ThreadPool Wait Thread: Abandoned wait: %d\n", Status - STATUS_ABANDONED_WAIT_0 ) ;
#endif
// Abandoned wait
ASSERT (FALSE) ;
} else if (Status == STATUS_TIMEOUT) {
// remove this thread from the wait list and terminate
{
ULONG NumWaits;
ACQUIRE_GLOBAL_WAIT_LOCK() ;
NumWaits = ThreadCB->NumWaits;
if (ThreadCB->NumWaits <= 1) {
RemoveEntryList(&ThreadCB->WaitThreadsList) ;
NtClose(ThreadCB->ThreadHandle) ;
NtClose(ThreadCB->TimerHandle) ;
}
RELEASE_GLOBAL_WAIT_LOCK() ;
if (NumWaits <= 1) {
RtlpExitThreadFunc( 0 );
}
}
} else {
// Some other error: fatal condition
ULONG i ;
// ASSERTMSG( "Press 'i', and note the dbgprint\n", FALSE ) ;
#if DBG
DbgPrint ("RTL Thread Pool: Application closed an object handle "
"that the wait thread was waiting on: Code:%x ThreadId:<%x:%x>\n",
Status, HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)) ;
TimeOut.QuadPart = 0 ;
for (i=0; i<ThreadCB->NumActiveWaits; i++)
{
Status = NtWaitForMultipleObjects(
(CHAR) 1,
&ThreadCB->ActiveWaitArray[i],
WaitAny,
TRUE, // Wait Alertably
&TimeOut // Dont 0
) ;
if (Status == STATUS_INVALID_HANDLE) {
DbgPrint("Bad Handle index:%d WaitEntry Ptr:%x\n", i, ThreadCB->ActiveWaitPointers[i]) ;
}
}
#endif
ASSERT( FALSE ) ;
// Set timeout for the largest timeout possible
TimeOut.LowPart = 0 ;
TimeOut.HighPart = 0x80000000 ;
NtDelayExecution (TRUE, &TimeOut) ;
}
} // forever
return 0 ; // Keep compiler happy
}
VOID RtlpAsyncCallbackCompletion(PVOID Context)
/*++
Routine Description:
This routine is called in a (IO)worker thread and is used to decrement the RefCount at the end and call RtlpDelete(Wait/Timer) if required
Arguments:
Context - AsyncCallback: containing pointer to Wait/Timer object,
--*/
{
PRTLP_ASYNC_CALLBACK AsyncCallback ;
AsyncCallback = (PRTLP_ASYNC_CALLBACK) Context ;
// callback queued by WaitThread (event or timer)
if ( AsyncCallback->WaitThreadCallback ) {
PRTLP_WAIT Wait = AsyncCallback->Wait ;
//DPRN5
if (DPRN4)
DbgPrint("Calling WaitOrTimer: fn:%x context:%x bool:%d Thread<%d:%d>\n",
(ULONG_PTR)Wait->Function, (ULONG_PTR)Wait->Context,
(ULONG_PTR)AsyncCallback->TimerCondition,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)
) ;
#if (DBG1)
DBG_SET_FUNCTION( Wait->Function, Wait->Context ) ;
#endif
((WAITORTIMERCALLBACKFUNC) Wait->Function) ( Wait->Context, AsyncCallback->TimerCondition ) ;
if ( InterlockedDecrement( &Wait->RefCount ) == 0 ) {
RtlpDeleteWait( Wait ) ;
}
}
// callback queued by TimerThread
else {
PRTLP_TIMER Timer = AsyncCallback->Timer ;
//DPRN5
if (DPRN4)
DbgPrint("Calling WaitOrTimer:Timer: fn:%x context:%x bool:%d Thread<%d:%d>\n",
(ULONG_PTR)Timer->Function, (ULONG_PTR)Timer->Context,
TRUE,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)
) ;
#if (DBG1)
DBG_SET_FUNCTION( Timer->Function, Timer->Context ) ;
#endif
((WAITORTIMERCALLBACKFUNC) Timer->Function) ( Timer->Context , TRUE) ;
// decrement RefCount after function is executed so that the context is not deleted
if ( InterlockedDecrement( &Timer->RefCount ) == 0 ) {
RtlpDeleteTimer( Timer ) ;
}
}
RtlpFreeTPHeap( AsyncCallback );
}
VOID RtlpProcessWaitCompletion (PRTLP_WAIT Wait, ULONG ArrayIndex)
/*++
Routine Description:
This routine is used for processing a completed wait
Arguments:
Wait - Wait that completed
--*/
{
ULONG TimeRemaining ;
ULONG NewFiringTime ;
LARGE_INTEGER DueTime ;
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB ;
PRTLP_ASYNC_CALLBACK AsyncCallback ;
ThreadCB = Wait->ThreadCB ;
// deactivate wait if it is meant for single execution
if ( Wait->Flags & WT_EXECUTEONLYONCE ) {
RtlpDeactivateWait (Wait) ;
}
else {
// if wait being reactivated, then reset the timer now itself as
// it can be deleted in the callback function
if ( Wait->Timer ) {
TimeRemaining = RtlpGetTimeRemaining (ThreadCB->TimerHandle) ;
if (RtlpReOrderDeltaList (&ThreadCB->TimerQueue.TimerList, Wait->Timer, TimeRemaining, &NewFiringTime, Wait->Timer->Period)) {
// There is a new element at the head of the queue we need to reset the NT timer to fire later
RtlpResetTimer (ThreadCB->TimerHandle, NewFiringTime, ThreadCB) ;
}
}
// move the wait entry to the end, and shift elements to its right one pos towards left
{
HANDLE HandlePtr = ThreadCB->ActiveWaitArray[ArrayIndex];
PRTLP_WAIT WaitPtr = ThreadCB->ActiveWaitPointers[ArrayIndex];
RtlpShiftWaitArray(ThreadCB, ArrayIndex+1, ArrayIndex, ThreadCB->NumActiveWaits -1 - ArrayIndex)
ThreadCB->ActiveWaitArray[ThreadCB->NumActiveWaits-1] = HandlePtr ;
ThreadCB->ActiveWaitPointers[ThreadCB->NumActiveWaits-1] = WaitPtr ;
}
}
// call callback function (FALSE since this isnt a timeout related callback)
if ( Wait->Flags & WT_EXECUTEINWAITTHREAD ) {
// executing callback after RtlpDeactivateWait allows the Callback to call
// RtlDeregisterWait Wait->RefCount is not incremented so that RtlDeregisterWait
// will work on this Wait. Though Wait->RefCount is not incremented, others cannot
// deregister this Wait as it has to be queued as an APC.
if (DPRN4)
DbgPrint("Calling WaitOrTimer(wait): fn:%x context:%x bool:%d Thread<%d:%d>\n",
(ULONG_PTR)Wait->Function, (ULONG_PTR)Wait->Context,
FALSE,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)
) ;
#if (DBG1)
DBG_SET_FUNCTION( Wait->Function, Wait->Context ) ;
#endif
((WAITORTIMERCALLBACKFUNC)(Wait->Function))(Wait->Context, FALSE) ;
// Wait object could have been deleted in the above callback
return ;
} else {
AsyncCallback = RtlpForceAllocateTPHeap( sizeof( RTLP_ASYNC_CALLBACK ), 0 );
if ( AsyncCallback ) {
NTSTATUS Status;
AsyncCallback->Wait = Wait ;
AsyncCallback->WaitThreadCallback = TRUE ;
AsyncCallback->TimerCondition = FALSE ;
InterlockedIncrement( &Wait->RefCount ) ;
Status = RtlQueueWorkItem( RtlpAsyncCallbackCompletion, AsyncCallback, Wait->Flags );
if (!NT_SUCCESS(Status)) {
RtlpFreeTPHeap( AsyncCallback );
if ( InterlockedDecrement( &Wait->RefCount ) == 0 ) {
RtlpDeleteWait( Wait ) ;
}
}
}
}
}
VOID RtlpAddWait (PRTLP_WAIT Wait)
/*++
Routine Description:
This routine is used for adding waits to the wait thread. It is executed in an APC.
Arguments:
Wait - The wait to add
--*/
{
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB = Wait->ThreadCB;
// if the state is deleted, it implies that RtlDeregister was called in a
// WaitThreadCallback for a Wait other than that which was fired. This is an application bug. I Assert, but also handle it.
if ( Wait->State & STATE_DELETE ) {
ASSERT(FALSE) ;
InterlockedDecrement( &ThreadCB->NumWaits ) ;
RtlpDeleteWait (Wait) ;
return ;
}
// activate Wait
ThreadCB->ActiveWaitArray [ThreadCB->NumActiveWaits] = Wait->WaitHandle ;
ThreadCB->ActiveWaitPointers[ThreadCB->NumActiveWaits] = Wait ;
ThreadCB->NumActiveWaits ++ ;
Wait->State |= (STATE_REGISTERED | STATE_ACTIVE) ;
Wait->RefCount = 1 ;
// Fill in the wait timer
if (Wait->Timeout != INFINITE_TIME) {
ULONG TimeRemaining ;
ULONG NewFiringTime ;
// Initialize timer related fields and insert the timer in the timer queue for this wait thread
Wait->Timer = (PRTLP_TIMER) RemoveHeadList(&ThreadCB->FreeTimerBlocks);
Wait->Timer->Function = Wait->Function ;
Wait->Timer->Context = Wait->Context ;
Wait->Timer->Flags = Wait->Flags ;
Wait->Timer->DeltaFiringTime = Wait->Timeout ;
Wait->Timer->Period = ( Wait->Flags & WT_EXECUTEONLYONCE ) ? 0 : Wait->Timeout == INFINITE_TIME ? 0 : Wait->Timeout ;
Wait->Timer->State = ( STATE_REGISTERED | STATE_ACTIVE ) ; ;
Wait->Timer->Wait = Wait ;
Wait->Timer->RefCountPtr = &Wait->RefCount ;
Wait->Timer->Queue = &ThreadCB->TimerQueue ;
TimeRemaining = RtlpGetTimeRemaining (ThreadCB->TimerHandle) ;
if (RtlpInsertInDeltaList (&ThreadCB->TimerQueue.TimerList, Wait->Timer, TimeRemaining, &NewFiringTime))
{
// If the element was inserted at head of list then reset the timers
RtlpResetTimer (ThreadCB->TimerHandle, NewFiringTime, ThreadCB) ;
}
} else {// No timer with this wait
Wait->Timer = NULL ;
}
}
NTSTATUS RtlpDeregisterWait (PRTLP_WAIT Wait, HANDLE PartialCompletionEvent, PULONG RetStatusPtr)
/*++
Routine Description:
This routine is used for deregistering the specified wait.
Arguments:
Wait - The wait to deregister
--*/
{
ULONG Status = STATUS_SUCCESS ;
ULONG DontUse ;
PULONG RetStatus = RetStatusPtr ? RetStatusPtr : &DontUse;
CHECK_SIGNATURE(Wait) ;
// RtlpDeregisterWait can be called on a wait that has not yet been
// registered. This indicates that someone calls a RtlDeregisterWait
// inside a WaitThreadCallback for a Wait other than that was fired.
// Application bug!! I assert but handle it
if ( ! (Wait->State & STATE_REGISTERED) ) {
// set state to deleted, so that it does not get registered
Wait->State |= STATE_DELETE ;
InterlockedDecrement( &Wait->RefCount ) ;
if ( PartialCompletionEvent ) {
NtSetEvent( PartialCompletionEvent, NULL ) ;
}
*RetStatus = STATUS_SUCCESS ;
return STATUS_SUCCESS ;
}
// deactivate wait.
if ( Wait->State & STATE_ACTIVE ) {
if ( ! NT_SUCCESS( RtlpDeactivateWait ( Wait ) ) ) {
*RetStatus = STATUS_NOT_FOUND ;
return STATUS_NOT_FOUND ;
}
}
// delete wait if RefCount == 0
Wait->State |= STATE_DELETE ;
if ( InterlockedDecrement (&Wait->RefCount) == 0 ) {
RtlpDeleteWait ( Wait ) ;
Status = *RetStatus = STATUS_SUCCESS ;
} else {
Status = *RetStatus = STATUS_PENDING ;
}
if ( PartialCompletionEvent ) {
NtSetEvent( PartialCompletionEvent, NULL ) ;
}
return Status ;
}
NTSTATUS RtlpDeactivateWait (PRTLP_WAIT Wait)
/*++
Routine Description:
This routine is used for deactivating the specified wait. It is executed in a APC.
Arguments:
Wait - The wait to deactivate
--*/
{
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB = Wait->ThreadCB ;
ULONG ArrayIndex ; //Index in ActiveWaitArray where the Wait object is placed
ULONG EndIndex = ThreadCB->NumActiveWaits -1;
// get the index in ActiveWaitArray
for (ArrayIndex = 0; ArrayIndex <= EndIndex; ArrayIndex++) {
if (ThreadCB->ActiveWaitPointers[ArrayIndex] == Wait)
break ;
}
if ( ArrayIndex > EndIndex ) {
ASSERT (FALSE) ;
return STATUS_NOT_FOUND;
}
// Move the remaining ActiveWaitArray left.
RtlpShiftWaitArray( ThreadCB, ArrayIndex+1, ArrayIndex, EndIndex - ArrayIndex ) ;
// delete timer if associated with this wait
// Though timer is being "freed" here, if it is in the timersToBeFired list, some of its fields will be used later
if ( Wait->Timer ) {
ULONG TimeRemaining ;
ULONG NewFiringTime ;
if (! (Wait->Timer->State & STATE_ACTIVE) ) {
RemoveEntryList( &Wait->Timer->List ) ;
} else {
TimeRemaining = RtlpGetTimeRemaining (ThreadCB->TimerHandle) ;
if (RtlpRemoveFromDeltaList (&ThreadCB->TimerQueue.TimerList, Wait->Timer, TimeRemaining, &NewFiringTime))
{
RtlpResetTimer (ThreadCB->TimerHandle, NewFiringTime, ThreadCB) ;
}
}
InsertTailList (&ThreadCB->FreeTimerBlocks, &Wait->Timer->List) ;
Wait->Timer = NULL ;
}
// Decrement the (active) wait count
ThreadCB->NumActiveWaits-- ;
InterlockedDecrement( &ThreadCB->NumWaits ) ;
Wait->State &= ~STATE_ACTIVE ;
return STATUS_SUCCESS;
}
VOID RtlpDeleteWait (PRTLP_WAIT Wait)
/*++
Routine Description:
This routine is used for deleting the specified wait.
It can be executed outside the context of the wait thread. So structure except the WaitEntry can be changed. It also sets the event.
Arguments:
Wait - The wait to delete
--*/
{
CHECK_SIGNATURE( Wait ) ;
CLEAR_SIGNATURE( Wait ) ;
#if DBG1
if (DPRN1)
DbgPrint("<%d> Wait %x deleted in thread:%d\n\n", Wait->DbgId, (ULONG_PTR)Wait, HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread)) ;
#endif
if ( Wait->CompletionEvent ) {
NtSetEvent( Wait->CompletionEvent, NULL ) ;
}
RtlpFreeTPHeap( Wait) ;
}
VOID RtlpDoNothing (PVOID NotUsed1, PVOID NotUsed2, PVOID NotUsed3)
/*++
Routine Description:
This routine is used to see if the thread is alive
Arguments:
NotUsed1, NotUsed2 and NotUsed 3 - not used
--*/
{
}
__inline LONGLONG RtlpGet64BitTickCount(LARGE_INTEGER *Last64BitTickCount)
/*++
Routine Description:
This routine is used for getting the latest 64bit tick count.
Return Value: 64bit tick count
--*/
{
LARGE_INTEGER liCurTime ;
liCurTime.QuadPart = NtGetTickCount() + Last64BitTickCount->HighPart ;
// see if timer has wrapped.
if (liCurTime.LowPart < Last64BitTickCount->LowPart) {
liCurTime.HighPart++ ;
}
return (Last64BitTickCount->QuadPart = liCurTime.QuadPart) ;
}
__inline LONGLONG RtlpResync64BitTickCount()
/*++
Routine Description:
This routine is used for getting the latest 64bit tick count.
Return Value: 64bit tick count
Remarks: This call should be made in the first line of any APC queued to the timer thread and nowhere else. It is used to reduce the drift
--*/
{
return Resync64BitTickCount.QuadPart = RtlpGet64BitTickCount(&Last64BitTickCount);
}
VOID RtlpProcessTimeouts (PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB)
/*++
Routine Description:
This routine processes timeouts for the wait thread
Arguments:
ThreadCB - The wait thread to add the wait to
--*/
{
ULONG NewFiringTime, TimeRemaining ;
LIST_ENTRY TimersToFireList ;
// check if incorrect timer fired
if (ThreadCB->Firing64BitTickCount > RtlpGet64BitTickCount(&ThreadCB->Current64BitTickCount) + 200 )
{
RtlpResetTimer (ThreadCB->TimerHandle, RtlpGetTimeRemaining (ThreadCB->TimerHandle), ThreadCB) ;
return ;
}
InitializeListHead( &TimersToFireList ) ;
RtlpFireTimersAndReorder (&ThreadCB->TimerQueue, &NewFiringTime, &TimersToFireList) ;// Walk thru the timer list and fire all waits with DeltaFiringTime == 0
RtlpResetTimer (ThreadCB->TimerHandle, NewFiringTime, ThreadCB) ;// Reset the NT timer
RtlpFireTimers( &TimersToFireList ) ;
}
VOID RtlpFireTimers (PLIST_ENTRY TimersToFireList)
/*++
Routine Description:
Finally all the timers are fired here.
Arguments:
TimersToFireList: List of timers to fire
--*/
{
PLIST_ENTRY Node ;
PRTLP_TIMER Timer ;
NTSTATUS Status;
for (Node = TimersToFireList->Flink; Node != TimersToFireList; Node = TimersToFireList->Flink)
{
Timer = CONTAINING_RECORD (Node, RTLP_TIMER, TimersToFireList) ;
RemoveEntryList( Node ) ;
InitializeListHead( Node ) ;
if ( (Timer->State & STATE_DONTFIRE) || (Timer->Queue->State & STATE_DONTFIRE) )
{
;
} else if ( Timer->Flags & (WT_EXECUTEINTIMERTHREAD | WT_EXECUTEINWAITTHREAD ) ) {
//DPRN5
if (DPRN4)
DbgPrint("Calling WaitOrTimer(Timer): fn:%x context:%x bool:%d Thread<%d:%d>\n",
(ULONG_PTR)Timer->Function, (ULONG_PTR)Timer->Context,
TRUE,
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread),
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess)
) ;
#if (DBG1)
DBG_SET_FUNCTION( Timer->Function, Timer->Context ) ;
#endif
((WAITORTIMERCALLBACKFUNC) Timer->Function) (Timer->Context, TRUE) ;
} else {
// create context for Callback and queue it appropriately
PRTLP_ASYNC_CALLBACK AsyncCallback ;
AsyncCallback = RtlpForceAllocateTPHeap(sizeof( RTLP_ASYNC_CALLBACK ), 0 );
AsyncCallback->TimerCondition = TRUE ;
// timer associated with WaitEvents should be treated differently
if ( Timer->Wait != NULL ) {
AsyncCallback->Wait = Timer->Wait ;
AsyncCallback->WaitThreadCallback = TRUE ;
InterlockedIncrement( Timer->RefCountPtr ) ;
} else {
AsyncCallback->Timer = Timer ;
AsyncCallback->WaitThreadCallback = FALSE ;
InterlockedIncrement( &Timer->RefCount ) ;
}
// queue work item
Status = RtlQueueWorkItem( RtlpAsyncCallbackCompletion, AsyncCallback, Timer->Flags );
if (!NT_SUCCESS(Status)) {
RtlpFreeTPHeap( AsyncCallback );
if ( Timer->Wait != NULL ) {
InterlockedDecrement( Timer->RefCountPtr ) ;
} else {
InterlockedDecrement( &Timer->RefCount ) ;
}
}
}
}
}
NTSTATUS RtlpFindWaitThread (PRTLP_WAIT_THREAD_CONTROL_BLOCK *ThreadCB)
/*++
Routine Description:
Walks thru the list of wait threads and finds one which can accomodate another wait.
If one is not found then a new thread is created.
This routine assumes that the caller has the GlobalWaitLock.
Arguments:
ThreadCB: returns the ThreadCB of the wait thread that will service the wait request.
Return Value:
STATUS_SUCCESS if a wait thread was allocated,
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PLIST_ENTRY Node ;
HANDLE ThreadHandle ;
PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCBTmp;
ACQUIRE_GLOBAL_WAIT_LOCK() ;
do {
// Walk thru the list of Wait Threads and find a Wait thread that can accomodate a new wait request.
// *Consider* finding a wait thread with least # of waits to facilitate better load balancing of waits.
for (Node = WaitThreads.Flink ; Node != &WaitThreads ; Node = Node->Flink) {
ThreadCBTmp = CONTAINING_RECORD (Node, RTLP_WAIT_THREAD_CONTROL_BLOCK, WaitThreadsList) ;
if ((ThreadCBTmp)->NumWaits < 64) {// Wait Threads can accomodate upto 64 waits (NtWaitForMultipleObject limit)
// Found a thread with some wait slots available.
InterlockedIncrement ( &(ThreadCBTmp)->NumWaits) ;
*ThreadCB = ThreadCBTmp;
RELEASE_GLOBAL_WAIT_LOCK() ;
return STATUS_SUCCESS ;
}
}
// If we reach here, we dont have any more wait threads. so create a new wait thread.
Status = RtlpStartThreadFunc (RtlpWaitThread, &ThreadHandle) ;
if (Status != STATUS_SUCCESS ) {// If thread creation fails then return the failure to caller
#if DBG
DbgPrint("ERROR!! ThreadPool: could not create wait thread\n");
#endif
RELEASE_GLOBAL_WAIT_LOCK() ;
return Status ;
} else {
NtClose (ThreadHandle) ;// Close Thread handle, we dont need it.
}
// Loop back now that we have created another thread
} while (TRUE) ; // Loop back to top and put new wait request in the newly created thread
RELEASE_GLOBAL_WAIT_LOCK() ;
return Status ;
}
// Timer Functions
VOID RtlpAddTimer (PRTLP_TIMER Timer)
/*++
Routine Description:
This routine runs as an APC into the Timer thread. It adds a new timer to the specified queue.
Arguments:
Timer - Pointer to the timer to add
--*/
{
PRTLP_TIMER_QUEUE Queue ;
ULONG TimeRemaining, QueueRelTimeRemaining ;
ULONG NewFiringTime ;
RtlpResync64BitTickCount() ;
// the timer was set to be deleted in a callback function.
if (Timer->State & STATE_DELETE ) {
RtlpDeleteTimer( Timer ) ;
return ;
}
Queue = Timer->Queue ;
// TimeRemaining is the time left in the current timer + the relative time of the queue it is being inserted into
TimeRemaining = RtlpGetTimeRemaining (TimerHandle) ;
QueueRelTimeRemaining = TimeRemaining + RtlpGetQueueRelativeTime (Queue) ;
if (RtlpInsertInDeltaList (&Queue->TimerList, Timer, QueueRelTimeRemaining, &NewFiringTime))
{
// If the Queue is not attached to TimerQueues since it had no timers
// previously then insert the queue into the TimerQueues list, else just reorder its existing position.
if (IsListEmpty (&Queue->List)) {
Queue->DeltaFiringTime = NewFiringTime ;
if (RtlpInsertInDeltaList (&TimerQueues, Queue, TimeRemaining, &NewFiringTime))
{
// There is a new element at the head of the queue we need to reset the NT timer to fire sooner.
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
}
} else {
// If we insert at the head of the timer delta list we will need to make sure the queue delta list is readjusted
if (RtlpReOrderDeltaList(&TimerQueues, Queue, TimeRemaining, &NewFiringTime, NewFiringTime)){
// There is a new element at the head of the queue we need to reset the NT timer to fire sooner.
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
}
}
}
Timer->State |= ( STATE_REGISTERED | STATE_ACTIVE ) ;
}
VOID RtlpUpdateTimer (PRTLP_TIMER Timer, PRTLP_TIMER UpdatedTimer)
/*++
Routine Description:
This routine executes in an APC and updates the specified timer if it exists
Arguments:
Timer - Timer that is actually updated
UpdatedTimer - Specifies pointer to a timer structure that contains Queue and Timer information
--*/
{
PRTLP_TIMER_QUEUE Queue ;
ULONG TimeRemaining, QueueRelTimeRemaining ;
ULONG NewFiringTime ;
RtlpResync64BitTickCount( ) ;
CHECK_SIGNATURE(Timer) ;
Queue = Timer->Queue ;
// Update the periodic time on the timer
Timer->Period = UpdatedTimer->Period ;
// if timer is not in active state, then dont update it
if ( ! ( Timer->State & STATE_ACTIVE ) ) {
return ;
}
// Get the time remaining on the NT timer
TimeRemaining = RtlpGetTimeRemaining (TimerHandle) ;
QueueRelTimeRemaining = TimeRemaining + RtlpGetQueueRelativeTime (Queue) ;
// Update the timer based on the due time
if (RtlpReOrderDeltaList (&Queue->TimerList, Timer, QueueRelTimeRemaining, &NewFiringTime, UpdatedTimer->DeltaFiringTime))
{
// If this update caused the timer at the head of the queue to change, then reinsert this queue in the list of queues.
if (RtlpReOrderDeltaList (&TimerQueues, Queue, TimeRemaining, &NewFiringTime, NewFiringTime)) {
// NT timer needs to be updated since the change caused the queue at the head of the TimerQueues to change.
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
}
}
RtlpFreeTPHeap( UpdatedTimer ) ;
}
VOID RtlpCancelTimer (PRTLP_TIMER Timer)
/*++
Routine Description:
This routine executes in an APC and cancels the specified timer if it exists
Arguments:
Timer - Specifies pointer to a timer structure that contains Queue and Timer information
--*/
{
RtlpCancelTimerEx( Timer, FALSE ) ; // queue not being deleted
}
VOID RtlpCancelTimerEx (PRTLP_TIMER Timer, BOOLEAN DeletingQueue)
/*++
Routine Description:
This routine cancels the specified timer.
Arguments:
Timer - Specifies pointer to a timer structure that contains Queue and Timer information
DeletingQueue - FALSE: routine executing in an APC. Delete timer only.
TRUE : routine called by timer queue which is being deleted. So dont reset the queue's position
--*/
{
PRTLP_TIMER_QUEUE Queue ;
RtlpResync64BitTickCount() ;
CHECK_SIGNATURE( Timer ) ;
Queue = Timer->Queue ;
if ( Timer->State & STATE_ACTIVE ) {
// if queue is being deleted, then the timer should not be reset
if ( ! DeletingQueue )
RtlpDeactivateTimer( Queue, Timer ) ;
} else {
// remove one shot Inactive timer from Queue->UncancelledTimerList
// called only when the time queue is being deleted
RemoveEntryList( &Timer->List ) ;
}
// Set the State to deleted
Timer->State |= STATE_DELETE ;
// delete timer if refcount == 0
if ( InterlockedDecrement( &Timer->RefCount ) == 0 ) {
RtlpDeleteTimer( Timer ) ;
}
}
VOID RtlpDeactivateTimer (PRTLP_TIMER_QUEUE Queue, PRTLP_TIMER Timer)
/*++
Routine Description:
This routine executes in an APC and cancels the specified timer if it exists
Arguments:
Timer - Specifies pointer to a timer structure that contains Queue and Timer information
--*/
{
ULONG TimeRemaining, QueueRelTimeRemaining ;
ULONG NewFiringTime ;
// Remove the timer from the appropriate queue
TimeRemaining = RtlpGetTimeRemaining (TimerHandle) ;
QueueRelTimeRemaining = TimeRemaining + RtlpGetQueueRelativeTime (Queue) ;
if (RtlpRemoveFromDeltaList (&Queue->TimerList, Timer, QueueRelTimeRemaining, &NewFiringTime)) {
// If we removed the last timer from the queue then we should remove the queue
// from TimerQueues, else we should readjust its position based on the delta time change
if (IsListEmpty (&Queue->TimerList)) {
// Remove the queue from TimerQueues
if (RtlpRemoveFromDeltaList (&TimerQueues, Queue, TimeRemaining, &NewFiringTime)) {
// There is a new element at the head of the queue we need to reset the NT timer to fire later
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
}
InitializeListHead (&Queue->List) ;
} else {
// If we remove from the head of the timer delta list we will need to make sure the queue delta list is readjusted
if (RtlpReOrderDeltaList (&TimerQueues, Queue, TimeRemaining, &NewFiringTime, NewFiringTime)) {
// There is a new element at the head of the queue we need to reset the NT timer to fire later
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
}
}
}
}
VOID RtlpDeleteTimer (PRTLP_TIMER Timer)
/*++
Routine Description:
This routine executes in worker or timer thread and deletes the timer whose RefCount == 0.
The function can be called outside timer thread, so no structure outside Timer can be touched (no list etc).
Arguments:
Timer - Specifies pointer to a timer structure that contains Queue and Timer information
--*/
{
PRTLP_TIMER_QUEUE Queue = Timer->Queue ;
CHECK_SIGNATURE( Timer ) ;
CLEAR_SIGNATURE( Timer ) ;
#if DBG1
if (DPRN1)
DbgPrint("<%d> Timer: %x: deleted\n\n", Timer->Queue->DbgId, (ULONG_PTR)Timer) ;
#endif
// safe to call this. Either the timer is in the TimersToFireList and the function is being called in time context or else it is not in the list
RemoveEntryList( &Timer->TimersToFireList ) ;
if ( Timer->CompletionEvent )
NtSetEvent( Timer->CompletionEvent, NULL ) ;
// decrement the total number of timers in the queue
if ( InterlockedDecrement( &Queue->RefCount ) == 0 )
RtlpDeleteTimerQueueComplete( Queue ) ;
RtlpFreeTPHeap( Timer ) ;
}
ULONG RtlpGetQueueRelativeTime (PRTLP_TIMER_QUEUE Queue)
/*++
Routine Description:
Walks the list of queues and returns the relative firing time by adding all the DeltaFiringTimes for all queues before it.
Arguments:
Queue - Queue for which to find the relative firing time
Return Value:
Time in milliseconds
--*/
{
PLIST_ENTRY Node ;
ULONG RelativeTime ;
PRTLP_TIMER_QUEUE CurrentQueue ;
RelativeTime = 0 ;
// It the Queue is not attached to TimerQueues List because it has no timer
// associated with it simply returns 0 as the relative time. Else run thru
// all queues before it in the list and compute the relative firing time
if (!IsListEmpty (&Queue->List)) {
for (Node = TimerQueues.Flink; Node != &Queue->List; Node=Node->Flink) {
CurrentQueue = CONTAINING_RECORD (Node, RTLP_TIMER_QUEUE, List) ;
RelativeTime += CurrentQueue->DeltaFiringTime ;
}
// Add the queue's delta firing time as well
RelativeTime += Queue->DeltaFiringTime ;
}
return RelativeTime ;
}
ULONG RtlpGetTimeRemaining (HANDLE TimerHandle)
/*++
Routine Description:
Gets the time remaining on the specified NT timer
Arguments:
TimerHandle - Handle to the NT timer
Return Value:
Time remaining on the timer
--*/
{
ULONG InfoLen ;
TIMER_BASIC_INFORMATION Info ;
NTSTATUS Status ;
Status = NtQueryTimer (TimerHandle, TimerBasicInformation, &Info, sizeof(Info), &InfoLen) ;
ASSERT (Status == STATUS_SUCCESS) ;
// Return 0 if
// - the timer has already fired then return
// OR
// - the timer is has more than 0x7f0000000 in its high part
// (which indicates that the timer was (probably) programmed for -1)
if (Info.TimerState || ((ULONG)Info.RemainingTime.HighPart > 0x7f000000) ) {
return 0 ;
} else {
return (ULONG) (Info.RemainingTime.QuadPart / 10000) ;
}
}
VOID RtlpResetTimer (HANDLE TimerHandle, ULONG DueTime, PRTLP_WAIT_THREAD_CONTROL_BLOCK ThreadCB)
/*++
Routine Description:
This routine resets the timer object with the new due time.
Arguments:
TimerHandle - Handle to the timer object
DueTime - Relative timer due time in Milliseconds
--*/
{
LARGE_INTEGER LongDueTime ;
NtCancelTimer (TimerHandle, NULL) ;
// If the DueTime is INFINITE_TIME then set the timer to the largest integer possible
if (DueTime == INFINITE_TIME) {
LongDueTime.LowPart = 0x0 ;
LongDueTime.HighPart = 0x80000000 ;
} else {
// set the absolute time when timer is to be fired
if (ThreadCB) {
ThreadCB->Firing64BitTickCount = DueTime + RtlpGet64BitTickCount(&ThreadCB->Current64BitTickCount) ;
} else {
// adjust for drift only if it is a global timer
ULONG Drift ;
LONGLONG llCurrentTick ;
llCurrentTick = RtlpGet64BitTickCount(&Last64BitTickCount) ;
Drift = (ULONG) (llCurrentTick - RtlpGetResync64BitTickCount()) ;
DueTime = (DueTime > Drift) ? DueTime-Drift : 1 ;
RtlpSetFiring64BitTickCount(llCurrentTick + DueTime) ;
}
LongDueTime.QuadPart = Int32x32To64( DueTime, -10000 );
}
NtSetTimer (TimerHandle, &LongDueTime, ThreadCB ? NULL : RtlpServiceTimer, NULL, FALSE, 0, NULL) ;
}
BOOLEAN RtlpInsertInDeltaList (PLIST_ENTRY DeltaList, PRTLP_GENERIC_TIMER NewTimer, ULONG TimeRemaining, ULONG *NewFiringTime)
/*++
Routine Description:
Inserts the timer element in the appropriate place in the delta list.
Arguments:
DeltaList - Delta list to insert into
NewTimer - Timer element to insert into list
TimeRemaining - This time must be added to the head of the list to get "real" relative time.
NewFiringTime - If the new element was inserted at the head of the list - this will contain the new firing time in milliseconds.
The caller can use this time to re-program the NT timer.
This MUST NOT be changed if the function returns FALSE.
Return Value:
TRUE - If the timer was inserted at head of delta list
FALSE - otherwise
--*/
{
PLIST_ENTRY Node ;
PRTLP_GENERIC_TIMER Temp ;
PRTLP_GENERIC_TIMER Head ;
if (IsListEmpty (DeltaList)) {
InsertHeadList (DeltaList, &NewTimer->List) ;
*NewFiringTime = NewTimer->DeltaFiringTime ;
NewTimer->DeltaFiringTime = 0 ;
return TRUE ;
}
// Adjust the head of the list to reflect the time remaining on the NT timer
Head = CONTAINING_RECORD (DeltaList->Flink, RTLP_GENERIC_TIMER, List) ;
Head->DeltaFiringTime += TimeRemaining ;
// Find the appropriate location to insert this element in
for (Node = DeltaList->Flink ; Node != DeltaList ; Node = Node->Flink) {
Temp = CONTAINING_RECORD (Node, RTLP_GENERIC_TIMER, List) ;
if (Temp->DeltaFiringTime <= NewTimer->DeltaFiringTime) {
NewTimer->DeltaFiringTime -= Temp->DeltaFiringTime ;
} else {
// found appropriate place to insert this timer
break ;
}
}
// Either we have found the appopriate node to insert before in terms of deltas.
// OR we have come to the end of the list. Insert this timer here.
InsertHeadList (Node->Blink, &NewTimer->List) ;
// If this isnt the last element in the list - adjust the delta of the next element
if (Node != DeltaList) {
Temp->DeltaFiringTime -= NewTimer->DeltaFiringTime ;
}
// Check if element was inserted at head of list
if (DeltaList->Flink == &NewTimer->List) {
// Set NewFiringTime to the time in milliseconds when the new head of list should be serviced.
*NewFiringTime = NewTimer->DeltaFiringTime ;
// This means the timer must be programmed to service this request
NewTimer->DeltaFiringTime = 0 ;
return TRUE ;
} else {
// No change to the head of the list, set the delta time back
Head->DeltaFiringTime -= TimeRemaining ;
return FALSE ;
}
}
BOOLEAN RtlpRemoveFromDeltaList (PLIST_ENTRY DeltaList, PRTLP_GENERIC_TIMER Timer, ULONG TimeRemaining, ULONG* NewFiringTime)
/*++
Routine Description:
Removes the specified timer from the delta list
Arguments:
DeltaList - Delta list to insert into
Timer - Timer element to insert into list
TimerHandle - Handle of the NT Timer object
TimeRemaining - This time must be added to the head of the list to get "real" relative time.
Return Value:
TRUE if the timer was removed from head of timer list
FALSE otherwise
--*/
{
PLIST_ENTRY Next ;
PRTLP_GENERIC_TIMER Temp ;
Next = Timer->List.Flink ;
RemoveEntryList (&Timer->List) ;
if (IsListEmpty (DeltaList)) {
*NewFiringTime = INFINITE_TIME ;
return TRUE ;
}
if (Next == DeltaList) {
// If we removed the last element in the list nothing to do either
return FALSE ;
} else {
Temp = CONTAINING_RECORD ( Next, RTLP_GENERIC_TIMER, List) ;
Temp->DeltaFiringTime += Timer->DeltaFiringTime ;
// Check if element was removed from head of list
if (DeltaList->Flink == Next) {
*NewFiringTime = Temp->DeltaFiringTime + TimeRemaining ;
Temp->DeltaFiringTime = 0 ;
return TRUE ;
} else {
return FALSE ;
}
}
}
BOOLEAN RtlpReOrderDeltaList (PLIST_ENTRY DeltaList, PRTLP_GENERIC_TIMER Timer, ULONG TimeRemaining, ULONG *NewFiringTime, ULONG ChangedFiringTime)
/*++
Routine Description:
Called when a timer in the delta list needs to be re-inserted because the firing time has changed.
Arguments:
DeltaList - List in which to re-insert
Timer - Timer for which the firing time has changed
TimeRemaining - Time before the head of the delta list is fired
NewFiringTime - If the new element was inserted at the head of the list - this
will contain the new firing time in milliseconds.
The caller can use this time to re-program the NT timer.
ChangedFiringTime - Changed Time for the specified timer.
Return Value:
TRUE if the timer was removed from head of timer list
FALSE otherwise
--*/
{
ULONG NewTimeRemaining ;
PRTLP_GENERIC_TIMER Temp ;
// Remove the timer from the list
if (RtlpRemoveFromDeltaList (DeltaList, Timer, TimeRemaining, NewFiringTime)) {
// If element was removed from the head of the list we should record that
NewTimeRemaining = *NewFiringTime ;
} else {
// Element was not removed from head of delta list, the current TimeRemaining is valid
NewTimeRemaining = TimeRemaining ;
}
// Before inserting Timer, set its delta time to the ChangedFiringTime
Timer->DeltaFiringTime = ChangedFiringTime ;
// Reinsert this element back in the list
if (!RtlpInsertInDeltaList (DeltaList, Timer, NewTimeRemaining, NewFiringTime)) {
// If we did not add at the head of the list, then we should return TRUE if
// RtlpRemoveFromDeltaList() had returned TRUE. We also update the NewFiringTime to
// the reflect the new firing time returned by RtlpRemoveFromDeltaList()
*NewFiringTime = NewTimeRemaining ;
return (NewTimeRemaining != TimeRemaining) ;
} else {
// NewFiringTime contains the time the NT timer must be programmed for
return TRUE ;
}
}
VOID RtlpAddTimerQueue (PVOID Queue)
/*++
Routine Description:
This routine runs as an APC into the Timer thread. It does whatever necessary to create a new timer queue
Arguments:
Queue - Pointer to the queue to add
--*/
{
// We do nothing here. The newly created queue is free floating until a timer is queued onto it.
}
VOID RtlpServiceTimer (PVOID NotUsedArg, ULONG NotUsedLowTimer, LONG NotUsedHighTimer)
/*++
Routine Description:
Services the timer. Runs in an APC.
Arguments:
NotUsedArg - Argument is not used in this function.
NotUsedLowTimer - Argument is not used in this function.
NotUsedHighTimer - Argument is not used in this function.
Remarks:
This APC is called only for timeouts of timer threads.
--*/
{
PRTLP_TIMER Timer ;
PRTLP_TIMER_QUEUE Queue ;
PLIST_ENTRY TNode ;
PLIST_ENTRY QNode ;
PLIST_ENTRY Temp ;
ULONG NewFiringTime ;
LIST_ENTRY ReinsertTimerQueueList ;
LIST_ENTRY TimersToFireList ;
RtlpResync64BitTickCount() ;
if (DPRN2) {
DbgPrint("Before service timer ThreadId<%x:%x>\n", HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread), HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess));
RtlDebugPrintTimes ();
}
ACQUIRE_GLOBAL_TIMER_LOCK();
// fire it if it even 200ms ahead. else reset the timer
if (Firing64BitTickCount.QuadPart > RtlpGet64BitTickCount(&Last64BitTickCount) + 200) {
RtlpResetTimer (TimerHandle, RtlpGetTimeRemaining (TimerHandle), NULL) ;
RELEASE_GLOBAL_TIMER_LOCK() ;
return ;
}
InitializeListHead (&ReinsertTimerQueueList) ;
InitializeListHead (&TimersToFireList) ;
// We run thru all queues with DeltaFiringTime == 0 and fire all timers that have DeltaFiringTime == 0. We remove the fired timers and either free them
// (for one shot timers) or put them in aside list (for periodic timers).
// After we have finished firing all timers in a queue we reinsert the timers in the aside list back into the queue based on their new firing times.
// Similarly, we remove each fired Queue and put it in a aside list. After firing
// all queues with DeltaFiringTime == 0, we reinsert the Queues in the aside list
// and reprogram the NT timer to be the firing time of the first queue in the list
for (QNode = TimerQueues.Flink ; QNode != &TimerQueues ; QNode = QNode->Flink) {
Queue = CONTAINING_RECORD (QNode, RTLP_TIMER_QUEUE, List) ;
// If the delta time in the timer queue is 0 - then this queue
// has timers that are ready to fire. Walk the list and fire all timers with
// Delta time of 0
if (Queue->DeltaFiringTime == 0) {
// Walk all timers with DeltaFiringTime == 0 and fire them. After that
// reinsert the periodic timers in the appropriate place.
RtlpFireTimersAndReorder (Queue, &NewFiringTime, &TimersToFireList) ;
// detach this Queue from the list
QNode = QNode->Blink ;
RemoveEntryList (QNode->Flink) ;
// If there are timers in the queue then prepare to reinsert the queue in TimerQueues.
if (NewFiringTime != INFINITE_TIME) {
Queue->DeltaFiringTime = NewFiringTime ;
// put the timer in list that we will process after we have fired all elements in this queue
InsertHeadList (&ReinsertTimerQueueList, &Queue->List) ;
} else {
// Queue has no more timers in it. Let the Queue float.
InitializeListHead (&Queue->List) ;
}
} else {
// No more Queues with DeltaFiringTime == 0
break ;
}
}
// At this point we have fired all the ready timers. We have two lists that need to be
// merged - TimerQueues and ReinsertTimerQueueList. The following steps do this - at the
// end of this we will reprogram the NT Timer.
if (!IsListEmpty(&TimerQueues)) {
Queue = CONTAINING_RECORD (TimerQueues.Flink, RTLP_TIMER_QUEUE, List) ;
NewFiringTime = Queue->DeltaFiringTime ;
Queue->DeltaFiringTime = 0 ;
if (!IsListEmpty (&ReinsertTimerQueueList)) {
// TimerQueues and ReinsertTimerQueueList are both non-empty. Merge them.
RtlpInsertTimersIntoDeltaList (&ReinsertTimerQueueList, &TimerQueues, NewFiringTime, &NewFiringTime) ;
}
// NewFiringTime contains the time the NT Timer should be programmed to.
} else {
if (!IsListEmpty (&ReinsertTimerQueueList)) {
// TimerQueues is empty. ReinsertTimerQueueList is not.
RtlpInsertTimersIntoDeltaList (&ReinsertTimerQueueList, &TimerQueues, 0, &NewFiringTime) ;
} else {
NewFiringTime = INFINITE_TIME ;
}
// NewFiringTime contains the time the NT Timer should be programmed to.
}
// Reset the timer to reflect the Delta time associated with the first Queue
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
if (DPRN3) {
DbgPrint("After service timer:ThreadId<%x:%x>\n", HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread), HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess));
RtlDebugPrintTimes ();
}
// finally fire all the timers
RtlpFireTimers( &TimersToFireList ) ;
RELEASE_GLOBAL_TIMER_LOCK();
}
VOID RtlpFireTimersAndReorder (PRTLP_TIMER_QUEUE Queue, ULONG *NewFiringTime, PLIST_ENTRY TimersToFireList)
/*++
Routine Description:
Fires all timers in TimerList that have DeltaFiringTime == 0.
After firing the timers it reorders the timers based on their periodic times OR frees the fired one shot timers.
Arguments:
TimerList - Timer list to work thru.
NewFiringTime - Location where the new firing time for the first timer in the delta list is returned.
--*/
{
PLIST_ENTRY TNode ;
PRTLP_TIMER Timer ;
LIST_ENTRY ReinsertTimerList ;
PLIST_ENTRY TimerList = &Queue->TimerList ;
InitializeListHead (&ReinsertTimerList) ;
*NewFiringTime = 0 ;
for (TNode = TimerList->Flink ; (TNode != TimerList) && (*NewFiringTime == 0); TNode = TimerList->Flink)
{
Timer = CONTAINING_RECORD (TNode, RTLP_TIMER, List) ;
// Fire all timers with delta time of 0
if (Timer->DeltaFiringTime == 0) {
// detach this timer from the list
RemoveEntryList (TNode) ;
// get next firing time
if (!IsListEmpty(TimerList)) {
PRTLP_TIMER TmpTimer ;
TmpTimer = CONTAINING_RECORD (TimerList->Flink, RTLP_TIMER, List) ;
*NewFiringTime = TmpTimer->DeltaFiringTime ;
TmpTimer->DeltaFiringTime = 0 ;
} else {
*NewFiringTime = INFINITE_TIME ;
}
// if timer is not periodic then remove active state. Timer will be deleted when cancel timer is called.
if (Timer->Period == 0) {
InsertHeadList( &Queue->UncancelledTimerList, &Timer->List ) ;
// if one shot wait was timed out then, deactivate the wait
if ( Timer->Wait ) {
// though timer is being "freed" in this call, it can still be used after this call
RtlpDeactivateWait( Timer->Wait ) ;
}
else {
// should be set at the end
Timer->State &= ~STATE_ACTIVE ;
}
Timer->State |= STATE_ONE_SHOT_FIRED ;
} else {
// Set the DeltaFiringTime to be the next period
Timer->DeltaFiringTime = Timer->Period ;
// reinsert the timer in the list.
RtlpInsertInDeltaList (TimerList, Timer, *NewFiringTime, NewFiringTime) ;
}
// Call the function associated with this timer. call it in the end so that RtlTimer calls can be made in the timer function
if ( (Timer->State & STATE_DONTFIRE) || (Timer->Queue->State & STATE_DONTFIRE) )
{
;
} else {
InsertTailList( TimersToFireList, &Timer->TimersToFireList ) ;
}
} else {
// No more Timers with DeltaFiringTime == 0
break ;
}
}
if ( *NewFiringTime == 0 ) {
*NewFiringTime = INFINITE_TIME ;
}
}
VOID RtlpInsertTimersIntoDeltaList (IN PLIST_ENTRY NewTimerList, IN PLIST_ENTRY DeltaTimerList, IN ULONG TimeRemaining, OUT ULONG *NewFiringTime)
/*++
Routine Description:
This routine walks thru a list of timers in NewTimerList and inserts them into a delta timers list pointed to by DeltaTimerList.
The timeout associated with the first element in the new list is returned in NewFiringTime.
Arguments:
NewTimerList - List of timers that need to be inserted into the DeltaTimerList
DeltaTimerList - Existing delta list of zero or more timers.
TimeRemaining - Firing time of the first element in the DeltaTimerList
NewFiringTime - Location where the new firing time will be returned
--*/
{
PRTLP_GENERIC_TIMER Timer ;
PLIST_ENTRY TNode ;
PLIST_ENTRY Temp ;
for (TNode = NewTimerList->Flink ; TNode != NewTimerList ; TNode = TNode->Flink) {
Temp = TNode->Blink ;
RemoveEntryList (Temp->Flink) ;
Timer = CONTAINING_RECORD (TNode, RTLP_GENERIC_TIMER, List) ;
if (RtlpInsertInDeltaList (DeltaTimerList, Timer, TimeRemaining, NewFiringTime)) {
TimeRemaining = *NewFiringTime ;
}
TNode = Temp ;
}
}
LONG RtlpTimerThread (PVOID Initialized)
/*++
Routine Description:
All the timer activity takes place in APCs.
Arguments:
Initialized - Used to notify the starter of the thread that thread initialization has completed
--*/
{
LARGE_INTEGER TimeOut ;
// no structure initializations should be done here as new timer thread may be created after threadPoolCleanup
TimerThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
// Reset the NT Timer to never fire initially
RtlpResetTimer (TimerHandle, -1, NULL) ;
// Notify starter of this thread that it has initialized
InterlockedExchange ((ULONG *) Initialized, 1) ;
// Sleep alertably so that all the activity can take place in APCs
for ( ; ; ) {
// Set timeout for the largest timeout possible
TimeOut.LowPart = 0 ;
TimeOut.HighPart = 0x80000000 ;
NtDelayExecution (TRUE, &TimeOut) ;
}
return 0 ; // Keep compiler happy
}
NTSTATUS RtlpInitializeTimerThreadPool ()
/*++
Routine Description:
This routine is used to initialize structures used for Timer Thread
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
LARGE_INTEGER TimeOut ;
// In order to avoid an explicit RtlInitialize() function to initialize the wait thread pool
// we use StartedTimerInitialization and CompletedTimerInitialization to provide us the
// necessary synchronization to avoid multiple threads from initializing the thread pool.
// This scheme does not work if RtlInitializeCriticalSection() or NtCreateEvent fails - but in this case the caller has not choices left.
if (!InterlockedExchange(&StartedTimerInitialization, 1L)) {
if (CompletedTimerInitialization)
InterlockedExchange(&CompletedTimerInitialization, 0 ) ;
do {
Status = RtlInitializeCriticalSection( &TimerCriticalSection ) ;// Initialize global timer lock
if (! NT_SUCCESS( Status )) {
break ;
}
Status = NtCreateTimer(&TimerHandle, TIMER_ALL_ACCESS, NULL, NotificationTimer) ;
if (!NT_SUCCESS(Status) )
break ;
InitializeListHead (&TimerQueues) ; // Initialize Timer Queue Structures
// initialize tick count
Resync64BitTickCount.QuadPart = NtGetTickCount() ;
Firing64BitTickCount.QuadPart = 0 ;
Status = RtlpStartThreadFunc (RtlpTimerThread, &TimerThreadHandle) ;
if (!NT_SUCCESS(Status) )
break ;
} while(FALSE ) ;
if (!NT_SUCCESS(Status) ) {
ASSERT (Status == STATUS_SUCCESS) ;
StartedTimerInitialization = 0 ;
InterlockedExchange (&CompletedTimerInitialization, ~0) ;
return Status ;
}
InterlockedExchange (&CompletedTimerInitialization, 1L) ;
} else {
// Sleep 1 ms and see if the other thread has completed initialization
ONE_MILLISECOND_TIMEOUT(TimeOut) ;
while (!(volatile ULONG) CompletedTimerInitialization) {
NtDelayExecution (FALSE, &TimeOut) ;
}
if (CompletedTimerInitialization != 1)
Status = STATUS_NO_MEMORY ;
}
return NT_SUCCESS(Status) ? STATUS_SUCCESS : Status ;
}
NTSTATUS RtlpDeleteTimerQueue (PRTLP_TIMER_QUEUE Queue)
/*++
Routine Description:
This routine deletes the queue specified in the Request and frees all timers
Arguments:
Queue - queue to delete
Event - Event Handle used for signalling completion of request
--*/
{
ULONG TimeRemaining ;
ULONG NewFiringTime ;
PLIST_ENTRY Node ;
PRTLP_TIMER Timer ;
RtlpResync64BitTickCount() ;
SET_DEL_TIMERQ_SIGNATURE( Queue ) ;
// If there are no timers in the queue then it is not attached to TimerQueues
// In this case simply free the memory and return. Otherwise we have to first
// remove the queue from the TimerQueues List, update the firing time if this
// was the first queue in the list and then walk all the timers and free them before freeing the Timer Queue.
if (!IsListEmpty (&Queue->List)) {
TimeRemaining = RtlpGetTimeRemaining (TimerHandle) + RtlpGetQueueRelativeTime (Queue) ;
if (RtlpRemoveFromDeltaList (&TimerQueues, Queue, TimeRemaining, &NewFiringTime))
{
// If removed from head of queue list, reset the timer
RtlpResetTimer (TimerHandle, NewFiringTime, NULL) ;
}
// Free all the timers associated with this queue
for (Node = Queue->TimerList.Flink ; Node != &Queue->TimerList ; ) {
Timer = CONTAINING_RECORD (Node, RTLP_TIMER, List) ;
Node = Node->Flink ;
RtlpCancelTimerEx( Timer ,TRUE ) ; // Queue being deleted
}
}
// Free all the uncancelled one shot timers in this queue
for (Node = Queue->UncancelledTimerList.Flink ; Node != &Queue->UncancelledTimerList ; ) {
Timer = CONTAINING_RECORD (Node, RTLP_TIMER, List) ;
Node = Node->Flink ;
RtlpCancelTimerEx( Timer ,TRUE ) ; // Queue being deleted
}
// delete the queue completely if the RefCount is 0
if ( InterlockedDecrement( &Queue->RefCount ) == 0 ) {
RtlpDeleteTimerQueueComplete( Queue ) ;
return STATUS_SUCCESS ;
} else {
return STATUS_PENDING ;
}
}
VOID RtlpDeleteTimerQueueComplete (PRTLP_TIMER_QUEUE Queue)
/*++
Routine Description:
This routine frees the queue and sets the event.
Arguments:
Queue - queue to delete
Event - Event Handle used for signalling completion of request
--*/
{
#if DBG1
if (DPRN1)
DbgPrint("<%d> Queue: %x: deleted\n\n", Queue->DbgId, (ULONG_PTR)Queue) ;
#endif
InterlockedDecrement( &NumTimerQueues ) ;
// Notify the thread issuing the cancel that the request is completed
if ( Queue->CompletionEvent )
NtSetEvent (Queue->CompletionEvent, NULL) ;
RtlpFreeTPHeap( Queue ) ;
}
VOID RtlpThreadCleanup ()
/*++
Routine Description:
This routine is used for exiting timer, wait and IOworker threads.
--*/
{
NtTerminateThread( NtCurrentThread(), 0) ;
}
NTSTATUS RtlpWaitForEvent (HANDLE Event, HANDLE ThreadHandle)
/*++
Routine Description:
Waits for the event to be signalled. If the event is not signalled within one second, then checks to see that the thread is alive
Arguments:
Event : Event handle used for signalling completion of request
ThreadHandle: Thread to check whether still alive
Return Value:
STATUS_SUCCESS if event was signalled
else return NTSTATUS
--*/
{
NTSTATUS Status ;
LARGE_INTEGER TimeOut ;
ONE_SECOND_TIMEOUT(TimeOut) ;// Timeout of 1 second for request to complete
Wait:
Status = NtWaitForSingleObject (Event, FALSE, &TimeOut) ;
if (Status == STATUS_TIMEOUT) {
// The wait timed out. Check to see if the wait thread is still alive.
// This is done by trying to queue a dummy APC to it.
// There is no better way known to determine if the thread has died unexpectedly.
// If so then go back to waiting.
Status = NtQueueApcThread(ThreadHandle, (PPS_APC_ROUTINE)RtlpDoNothing, NULL, NULL, NULL);
if (NT_SUCCESS(Status) ) {
// Wait thread is still alive. Go back to waiting.
goto Wait ;
} else {
// The wait thread died between the time the APC was queued and the the time we started waiting on NtQueryInformationThread()
DbgPrint ("Thread died before event could be signalled") ;
}
}
return NT_SUCCESS(Status) ? STATUS_SUCCESS : Status ;
}
PRTLP_EVENT RtlpGetWaitEvent (VOID)
/*++
Routine Description:
Returns an event from the event cache.
Return Value:
Pointer to event structure
--*/
{
NTSTATUS Status;
PRTLP_EVENT Event ;
if (!CompletedEventCacheInitialization) {
RtlpInitializeEventCache () ;
}
RtlEnterCriticalSection (&EventCacheCriticalSection) ;
if (!IsListEmpty (&EventCache)) {
Event = (PRTLP_EVENT) RemoveHeadList (&EventCache) ;
} else {
Event = RtlpForceAllocateTPHeap( sizeof( RTLP_EVENT ), 0 );
if (!Event) {
RtlLeaveCriticalSection (&EventCacheCriticalSection) ;
return NULL ;
}
Status = NtCreateEvent(&Event->Handle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE);
if (!NT_SUCCESS(Status) ) {
RtlpFreeTPHeap( Event ) ;
RtlLeaveCriticalSection (&EventCacheCriticalSection) ;
return NULL ;
}
}
RtlLeaveCriticalSection (&EventCacheCriticalSection) ;
return Event ;
}
VOID RtlpFreeWaitEvent (PRTLP_EVENT Event)
/*++
Routine Description:
Frees the event to the event cache
Arguments:
Event - the event struct to put back into the cache
--*/
{
if ( Event == NULL )
return ;
InitializeListHead (&Event->List) ;
RtlEnterCriticalSection (&EventCacheCriticalSection) ;
if ( NumUnusedEvents > MAX_UNUSED_EVENTS ) {
NtClose( Event->Handle ) ;
RtlpFreeTPHeap( Event ) ;
} else {
InsertHeadList (&EventCache, &Event->List) ;
NumUnusedEvents++ ;
}
RtlLeaveCriticalSection (&EventCacheCriticalSection) ;
}
VOID RtlpInitializeEventCache (VOID)
/*++
Routine Description:
Initializes the event cache
--*/
{
NTSTATUS Status;
LARGE_INTEGER TimeOut ;
if (!InterlockedExchange(&StartedEventCacheInitialization, 1L)) {
InitializeListHead (&EventCache) ;
Status = RtlInitializeCriticalSection(&EventCacheCriticalSection) ;
ASSERT (Status == STATUS_SUCCESS) ;
NumUnusedEvents = 0 ;
InterlockedExchange (&CompletedEventCacheInitialization, 1L) ;
} else {
// sleep for 1 milliseconds and see if the initialization is complete
ONE_MILLISECOND_TIMEOUT(TimeOut) ;
while (!(volatile ULONG) CompletedEventCacheInitialization) {
NtDelayExecution (FALSE, &TimeOut) ;
}
}
}
VOID PrintTimerQueue(PLIST_ENTRY QNode, ULONG Delta, ULONG Count)
{
PLIST_ENTRY Tnode ;
PRTLP_TIMER Timer ;
PRTLP_TIMER_QUEUE Queue ;
Queue = CONTAINING_RECORD (QNode, RTLP_TIMER_QUEUE, List) ;
DbgPrint("<%1d> Queue: %x FiringTime:%d\n", Count, (ULONG_PTR)Queue, Queue->DeltaFiringTime);
for (Tnode=Queue->TimerList.Flink; Tnode!=&Queue->TimerList; Tnode=Tnode->Flink)
{
Timer = CONTAINING_RECORD (Tnode, RTLP_TIMER, List) ;
Delta += Timer->DeltaFiringTime ;
DbgPrint(" Timer: %x Delta:%d Period:%d\n",(ULONG_PTR)Timer, Delta, Timer->Period);
}
}
VOID RtlDebugPrintTimes ()
{
PLIST_ENTRY QNode ;
ULONG Count = 0 ;
ULONG Delta = RtlpGetTimeRemaining (TimerHandle) ;
ULONG CurrentThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread) ;
RtlpResync64BitTickCount();
if (CompletedTimerInitialization != 1) {
DbgPrint("===========RtlTimerThread not yet initialized==========\n");
return ;
}
if (CurrentThreadId == TimerThreadId)
{
PRTLP_TIMER_QUEUE Queue ;
DbgPrint("================Printing timerqueues====================\n");
DbgPrint("TimeRemaining: %d\n", Delta);
for (QNode = TimerQueues.Flink; QNode != &TimerQueues; QNode = QNode->Flink)
{
Queue = CONTAINING_RECORD (QNode, RTLP_TIMER_QUEUE, List) ;
Delta += Queue->DeltaFiringTime ;
PrintTimerQueue(QNode, Delta, ++Count);
}
DbgPrint("================Printed ================================\n");
}
else
{
NtQueueApcThread(TimerThreadHandle, (PPS_APC_ROUTINE)RtlDebugPrintTimes, NULL, NULL, NULL);
}
}
/*DO NOT USE THIS FUNCTION: REPLACED BY RTLCREATETIMER*/
NTSTATUS
RtlSetTimer(
IN HANDLE TimerQueueHandle,
OUT HANDLE *Handle,
IN WAITORTIMERCALLBACKFUNC Function,
IN PVOID Context,
IN ULONG DueTime,
IN ULONG Period,
IN ULONG Flags
)
{
static ULONG Count = 0;
if (Count++ ==0) {
DbgPrint("Using obsolete function call: RtlSetTimer\n");
DbgBreakPoint();
DbgPrint("Using obsolete function call: RtlSetTimer\n");
}
return RtlCreateTimer(TimerQueueHandle, Handle, Function, Context, DueTime, Period, Flags) ;
}
PVOID RtlpForceAllocateTPHeap(ULONG dwSize, ULONG dwFlags)
/*++
Routine Description:
This routine goes into an infinite loop trying to allocate the memory.
Arguments:
dwSize - size of memory to be allocated
dwFlags - Flags for memory allocation
Return Value:
ptr to memory
--*/
{
PVOID ptr;
ptr = RtlpAllocateTPHeap(dwSize, dwFlags);
if (ptr)
return ptr;
{
LARGE_INTEGER TimeOut ;
do {
ONE_SECOND_TIMEOUT(TimeOut) ;
NtDelayExecution (FALSE, &TimeOut) ;
ptr = RtlpAllocateTPHeap(dwSize, dwFlags);
if (ptr)
break;
} while (TRUE) ;
}
return ptr;
}
/*DO NOT USE THIS FUNCTION: REPLACED BY RTLDeleteTimer*/
NTSTATUS RtlCancelTimer(IN HANDLE TimerQueueHandle, IN HANDLE TimerToCancel)
/*++
Routine Description:
This routine cancels the timer. This call is non-blocking.
The timer Callback will not be executed after this call returns.
Arguments:
TimerQueueHandle - Handle identifying the queue from which to delete timer
TimerToCancel - Handle identifying the timer to cancel
Return Value:
NTSTATUS - Result code from call. The following are returned
STATUS_SUCCESS - Timer cancelled. All callbacks completed.
STATUS_PENDING - Timer cancelled. Some callbacks still not completed.
--*/
{
static ULONG Count = 0;
if (Count++ ==0) {
DbgPrint("Using obsolete function call: RtlCancelTimer\n");
DbgBreakPoint();
DbgPrint("Using obsolete function call: RtlCancelTimer\n");
}
return RtlDeleteTimer( TimerQueueHandle, TimerToCancel, NULL ) ;
}