|
|
//+----------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 2001
//
// File: kerbscav.cxx
//
// Contents: Scavenger (task automation) code
//
//
// History: 22-April-2001 Created MarkPu
//
//-----------------------------------------------------------------------------
#ifndef WIN32_CHICAGO
extern "C" { #include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <dsysdbg.h>
} #else
#include <kerb.hxx>
#include <kerbp.h>
#endif
#include <kerbcomm.h>
#include <kerbscav.h>
//
// FESTER: not a good idea to have these as globals, in case the application
// would want multiple scavenger instances. This will do for now.
//
BOOLEAN ScavengerInitialized = FALSE; RTL_CRITICAL_SECTION ScavengerLock; HANDLE ScavengerTimerQueue = NULL; HANDLE ScavengerTimerShutdownEvent = NULL; LIST_ENTRY ScavengerTaskQueue = {0}; LIST_ENTRY ScavengerDeadPool = {0}; ULONG ScavengerTaskQueueSize = 0; ULONG ScavengerDeadPoolSize = 0;
#define LockScavengerQueue() RtlEnterCriticalSection( &ScavengerLock )
#define UnlockScavengerQueue() RtlLeaveCriticalSection( &ScavengerLock )
struct SCAVENGER_TASK { LIST_ENTRY m_ListEntry;
//
// Periodicity control code
//
DWORD m_InsideTrigger; // Set to the ID of the callback thread
BOOLEAN m_Canceled; // TRUE if task was canceled
BOOLEAN m_Periodic; // TRUE if periodic
LONG m_Interval; // recurrence interval, in milliseconds
//
// Task management
//
HANDLE m_Timer; // Timer handle
ULONG m_Flags; // Timer flags (see CreateTimerQueueTimer)
HANDLE m_ShutdownEvent; // Shutdown event
LONG m_Processing; // Set to TRUE while inside the trigger
KERB_TASK_TRIGGER m_pfnTrigger; // Invocation callback
KERB_TASK_DESTROY m_pfnDestroy; // Destruction callback
void * m_Context; // User-supplied task context
};
typedef SCAVENGER_TASK * PSCAVENGER_TASK;
// ----------------------------------------------------------------------------
//
// Internal scavenger routines
//
// ----------------------------------------------------------------------------
VOID ScavengerTimerCallback( IN PVOID Parameter, IN BOOLEAN Reason );
//+----------------------------------------------------------------------------
//
// Function: ScavengerFreeTask
//
// Synopsis: Task 'destructor'
//
// Arguments: Task - task to be freed
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
void ScavengerFreeTask( IN PSCAVENGER_TASK Task ) { DsysAssert( Task );
if ( Task->m_pfnDestroy ) {
Task->m_pfnDestroy( Task->m_Context ); }
NtClose( Task->m_ShutdownEvent ); MIDL_user_free( Task );
return; }
//+----------------------------------------------------------------------------
//
// Function: ScavengerPurgeDeadPool
//
// Synopsis: Disposes of items in the deadpool
//
// Arguments: TaskToAvoid - Task to leave hanging around (because
// it corresponds to the current timer callback)
// This parameter can be NULL
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
void ScavengerPurgeDeadPool( IN OPTIONAL PSCAVENGER_TASK TaskToAvoid ) { ULONG TasksLeftOver = 0;
LockScavengerQueue();
while ( !IsListEmpty( &ScavengerDeadPool ) && TasksLeftOver < ScavengerDeadPoolSize ) {
//
// Get a task out of the list
//
BOOLEAN PutItBack = FALSE; PSCAVENGER_TASK Task = CONTAINING_RECORD( RemoveHeadList( &ScavengerDeadPool ), SCAVENGER_TASK, m_ListEntry );
//
// Only canceled tasks are allowed in the deadpool
//
DsysAssert( Task->m_Canceled );
DsysAssert( ScavengerDeadPoolSize > 0 ); ScavengerDeadPoolSize -= 1;
UnlockScavengerQueue();
if ( Task == TaskToAvoid ) {
//
// If this is the task associated with the current callback, skip it
// This routine might be called from inside a timer callback routine,
// and deleting a timer handle from inside its timer callback routine
// leads to devastating consequences
//
PutItBack = TRUE;
} else {
//
// Destroy the timer handle if it still exists
//
if ( Task->m_Timer != NULL ) {
BOOL Success;
Success = DeleteTimerQueueTimer( ScavengerTimerQueue, Task->m_Timer, Task->m_ShutdownEvent );
DsysAssert( Success || ( GetLastError() == ERROR_IO_PENDING ));
Task->m_Timer = NULL; }
//
// If the shutdown event is signaled,
// it is safe to dispose of the task;
// Otherwise, someone else will have to garbage collect this one
//
if ( WAIT_OBJECT_0 == WaitForSingleObject( Task->m_ShutdownEvent, 0 )) {
ScavengerFreeTask( Task );
} else {
PutItBack = TRUE; } }
LockScavengerQueue();
//
// If this is 'our' task, or there was trouble, insert it at the tail
// so we can continue with tasks at the head of the deadpool list
//
if ( PutItBack ) {
InsertTailList( &ScavengerDeadPool, &Task->m_ListEntry ); ScavengerDeadPoolSize += 1; TasksLeftOver += 1; } }
UnlockScavengerQueue();
return; }
//+----------------------------------------------------------------------------
//
// Function: ScavengerCancelTask
//
// Synopsis: Stops a task's timer for subsequent removal
//
// Arguments: Task - Task to cancel
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
void ScavengerCancelTask( IN PSCAVENGER_TASK Task ) { DsysAssert( Task );
KERB_TASK_DESTROY pfnDestroy = NULL; void * Context = NULL;
LockScavengerQueue();
//
// Only canceled tasks are allowed in the deadpool
//
DsysAssert( Task->m_Canceled );
//
// Move the task from the active task list to the deadpool
//
//
// First, remove the task from the active task list
//
RemoveEntryList( &Task->m_ListEntry ); ScavengerTaskQueueSize -= 1;
//
// Invoke the destruction callback right away, any further manipulation
// of this task by the client is illegal. Do this outside the queue lock.
//
if ( Task->m_pfnDestroy ) {
pfnDestroy = Task->m_pfnDestroy; Context = Task->m_Context; Task->m_pfnDestroy = NULL; }
//
// Finally, put the task on the deadpool
//
InsertTailList( &ScavengerDeadPool, &Task->m_ListEntry ); ScavengerDeadPoolSize += 1;
UnlockScavengerQueue();
if ( pfnDestroy ) {
pfnDestroy( Context ); }
return; }
//+----------------------------------------------------------------------------
//
// Function: ScavengerAddTask
//
// Synopsis: Common logic involved in scheduling a new task
//
// Arguments: Parameter - Task being scheduled
//
// Returns: STATUS_SUCCESS if happy
// STATUS_ error code otherwise
//
//-----------------------------------------------------------------------------
NTSTATUS ScavengerAddTask( IN PSCAVENGER_TASK Task ) { NTSTATUS Status = STATUS_SUCCESS; BOOL Success;
DsysAssert( Task );
LockScavengerQueue();
//
// Assumptions: properly configured task, ready to be scheduled
//
DsysAssert( Task->m_InsideTrigger == 0 ); DsysAssert( !Task->m_Canceled ); DsysAssert( Task->m_Timer == NULL ); DsysAssert( Task->m_ShutdownEvent != NULL ); DsysAssert( Task->m_Processing == FALSE );
//
// Schedule the task by creating its timer
//
Success = CreateTimerQueueTimer( &Task->m_Timer, ScavengerTimerQueue, ScavengerTimerCallback, Task, Task->m_Interval, Task->m_Periodic ? Task->m_Interval : 0, Task->m_Flags );
if ( !Success ) {
//
// FESTER: map GetLastError() to an NT status code maybe?
//
Status = STATUS_UNSUCCESSFUL; // DsysAssert( FALSE );
} else {
InsertHeadList( &ScavengerTaskQueue, &Task->m_ListEntry ); ScavengerTaskQueueSize += 1; }
UnlockScavengerQueue();
return Status; }
//+----------------------------------------------------------------------------
//
// Function: ScavengerTimerCallback
//
// Synopsis: Scavenger worker routine
//
// Arguments: Parameter - Task handle
// Reason - see definition of WAITORTIMERCALLBACK
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID ScavengerTimerCallback( IN PVOID Parameter, IN BOOLEAN Reason ) { PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )Parameter;
UNREFERENCED_PARAMETER( Reason );
DsysAssert( Task ); DsysAssert( Reason == TRUE ); DsysAssert( Task->m_pfnTrigger );
//
// Callbacks that step on each others' heels are thrown out
//
if ( FALSE != InterlockedCompareExchange( &Task->m_Processing, TRUE, FALSE )) {
return; }
//
// Invoke the trigger
//
DsysAssert( Task->m_InsideTrigger == 0 ); DsysAssert( !Task->m_Canceled );
Task->m_InsideTrigger = GetCurrentThreadId(); Task->m_pfnTrigger( Task, Task->m_Context ); Task->m_InsideTrigger = 0;
if ( !Task->m_Periodic ) {
//
// Non-periodic tasks get removed right away
//
Task->m_Canceled = TRUE; }
//
// If the task has been canceled, move it to the deadpool
//
if ( Task->m_Canceled ) {
ScavengerCancelTask( Task );
} else {
//
// Task has not been canceled, so open it up to timer callbacks
//
InterlockedExchange( &Task->m_Processing, FALSE ); }
//
// A timer callback is a good place to bury some bodies
//
ScavengerPurgeDeadPool( Task );
return; }
// ----------------------------------------------------------------------------
//
// External scavenger interfaces
//
// ----------------------------------------------------------------------------
//+----------------------------------------------------------------------------
//
// Function: KerbInitializeScavenger
//
// Synopsis: Initializes the scavenger
//
// Arguments: None
//
// Returns: STATUS_SUCCESS if happy
// STATUS_ error code otherwise
//
//-----------------------------------------------------------------------------
NTSTATUS KerbInitializeScavenger() { NTSTATUS Status;
DsysAssert( !ScavengerInitialized );
//
// Task queue and dead pool could be protected by different
// locks, but the amount of time spent inside those locks is minimal,
// so the same lock is used
//
Status = RtlInitializeCriticalSection( &ScavengerLock );
if ( !NT_SUCCESS( Status )) {
return Status; }
InitializeListHead( &ScavengerTaskQueue ); InitializeListHead( &ScavengerDeadPool ); ScavengerTaskQueueSize = 0; ScavengerDeadPoolSize = 0;
DsysAssert( ScavengerTimerShutdownEvent == NULL );
Status = NtCreateEvent( &ScavengerTimerShutdownEvent, EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE, NULL, SynchronizationEvent, FALSE );
if ( !NT_SUCCESS( Status )) {
goto Error; }
DsysAssert( ScavengerTimerQueue == NULL );
Status = RtlCreateTimerQueue( &ScavengerTimerQueue );
if ( !NT_SUCCESS( Status )) {
goto Error; }
//
// We're ready to rock-n-roll
//
ScavengerInitialized = TRUE;
Status = STATUS_SUCCESS;
Cleanup:
return Status;
Error:
DsysAssert( !NT_SUCCESS( Status ));
if ( ScavengerTimerQueue != NULL ) {
RtlDeleteTimerQueue( ScavengerTimerQueue ); ScavengerTimerQueue = NULL; }
if ( ScavengerTimerShutdownEvent != NULL ) {
NtClose( ScavengerTimerShutdownEvent ); ScavengerTimerShutdownEvent = NULL; }
RtlDeleteCriticalSection( &ScavengerLock );
ScavengerInitialized = FALSE;
goto Cleanup; }
//+----------------------------------------------------------------------------
//
// Function: KerbShutdownScavenger
//
// Synopsis: Shuts down the scavenger
//
// Arguments: None
//
// Returns: STATUS_SUCCESS if everything cleaned up properly
// STATUS_ error code otherwise
//
// Note: If errors are encountered, the scavenger will not be destroyed,
// but the task queue will be emptied.
//
//-----------------------------------------------------------------------------
NTSTATUS KerbShutdownScavenger() { NTSTATUS Status;
DsysAssert( ScavengerInitialized );
Status = RtlDeleteTimerQueueEx( ScavengerTimerQueue, ScavengerTimerShutdownEvent );
ScavengerPurgeDeadPool( NULL );
WaitForSingleObject( ScavengerTimerShutdownEvent, INFINITE );
//
// Purge the contents of the scavenger queue
// NOTE: no need to lock the queue anymore, as the timer has been shut down
//
while ( !IsListEmpty( &ScavengerTaskQueue )) {
PSCAVENGER_TASK Task = CONTAINING_RECORD( RemoveHeadList( &ScavengerTaskQueue ), SCAVENGER_TASK, m_ListEntry );
ScavengerFreeTask( Task );
ScavengerTaskQueueSize -= 1; }
DsysAssert( ScavengerTaskQueueSize == 0 );
if ( NT_SUCCESS( Status )) {
NtClose( ScavengerTimerShutdownEvent ); ScavengerTimerShutdownEvent = NULL; ScavengerTimerQueue = NULL; RtlDeleteCriticalSection( &ScavengerLock ); ScavengerInitialized = FALSE; }
return Status; }
//+----------------------------------------------------------------------------
//
// Function: KerbAddScavengerTask
//
// Synopsis: Adds a task to the list of those managed by the scavenger object
//
// Arguments: Periodic - If TRUE, this is to be a recurring task
// Interval - Execution interval in milliseconds
// Flags - WT_ flags (see CreateTimerQueueTimer)
// pfnTrigger - Trigger callback
// pfnDestroy - Destruction callback (OPTIONAL)
// TaskItem - Task context (OPTIONAL)
//
// Returns: STATUS_SUCCESS if everything cleaned up properly
// STATUS_ error code otherwise
//
//-----------------------------------------------------------------------------
NTSTATUS KerbAddScavengerTask( IN BOOLEAN Periodic, IN LONG Interval, IN ULONG Flags, IN KERB_TASK_TRIGGER pfnTrigger, IN KERB_TASK_DESTROY pfnDestroy, IN void * TaskItem, OUT OPTIONAL void * * TaskHandle ) { NTSTATUS Status; PSCAVENGER_TASK Task;
DsysAssert( ScavengerInitialized );
//
// Validate the passed in parameters
//
if ( pfnTrigger == NULL || ( Periodic && Interval == 0 )) {
// DsysAssert( FALSE && "RTFM: Invalid parameter passed in to KerbAddScavengerTask." );
return STATUS_INVALID_PARAMETER; }
Task = ( PSCAVENGER_TASK )MIDL_user_allocate( sizeof( SCAVENGER_TASK ));
if ( Task == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES; }
Task->m_InsideTrigger = 0; Task->m_Canceled = FALSE; Task->m_Periodic = Periodic; Task->m_Interval = Interval; Task->m_Timer = NULL; Task->m_Flags = Flags; Task->m_ShutdownEvent = NULL; Task->m_Processing = FALSE; Task->m_pfnTrigger = pfnTrigger; Task->m_pfnDestroy = pfnDestroy; Task->m_Context = TaskItem;
Status = NtCreateEvent( &Task->m_ShutdownEvent, EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE, NULL, SynchronizationEvent, FALSE );
if ( !NT_SUCCESS( Status )) {
MIDL_user_free( Task ); return Status; }
Status = ScavengerAddTask( Task );
if ( !NT_SUCCESS( Status )) {
Task->m_pfnDestroy = NULL; // Didn't take ownership yet, caller will destroy
ScavengerFreeTask( Task ); Task = NULL;
}
if ( TaskHandle ) {
*TaskHandle = Task; }
return Status; }
//+----------------------------------------------------------------------------
//
// Function: KerbTaskDoItNow
//
// Synopsis: Makes the task fire right away
//
// Arguments: TaskHandle - Task handle
//
// Returns: TRUE if successful, FALSE otherwise
//
// NOTE: this function can only be called from outside a task trigger callback
//
//-----------------------------------------------------------------------------
BOOL KerbTaskDoItNow( IN HANDLE TaskHandle ) { PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
DsysAssert( Task );
return ChangeTimerQueueTimer( ScavengerTimerQueue, Task->m_Timer, 0, 0 ); }
//+----------------------------------------------------------------------------
//
// Function: KerbTaskIsPeriodic
//
// Synopsis: Tells whether a given task is a periodic task
//
// Arguments: TaskHandle - Task handle
//
// Returns: TRUE if the task is periodic, FALSE otherwise
//
// NOTE: this function can only be called from inside a task trigger callback
//
//-----------------------------------------------------------------------------
BOOLEAN KerbTaskIsPeriodic( IN void * TaskHandle ) { PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
DsysAssert( Task ); DsysAssert( Task->m_InsideTrigger == GetCurrentThreadId());
return Task->m_Periodic; }
//+----------------------------------------------------------------------------
//
// Function: KerbTaskGetInterval
//
// Synopsis: Retrieves the interval of a periodic task
//
// Arguments: TaskHandle - Task handle
//
// Returns: Interval associated with the task, in milliseconds
//
// NOTE: this function can only be called from inside a task trigger callback
//
//-----------------------------------------------------------------------------
LONG KerbTaskGetInterval( IN void * TaskHandle ) { PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
DsysAssert( Task ); DsysAssert( Task->m_InsideTrigger == GetCurrentThreadId());
return Task->m_Interval; }
//+----------------------------------------------------------------------------
//
// Function: KerbTaskCancel
//
// Synopsis: Cancels the task
//
// Arguments: TaskHandle - Task handle
//
// Returns: Nothing
//
// NOTE: this function can only be called from inside a task trigger callback
//
//-----------------------------------------------------------------------------
void KerbTaskCancel( IN void * TaskHandle ) { PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
DsysAssert( Task ); DsysAssert( Task->m_InsideTrigger == GetCurrentThreadId());
Task->m_Canceled = TRUE;
return; }
|