|
|
/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
sched.cpp
Abstract:
Scheduling work items
Allows scheduling callbacks based on timeout or sognalling event handle
Author:
Vlad Sadovsky (vlads) 31-Jan-1997
Environment:
User Mode - Win32
Revision History:
31-Jan-1997 VladS created 30-Apr-1997 VladS Added support for asyncronous events
--*/
//
// Include Headers
//
#include "precomp.h"
#include "stiexe.h"
#include <stilib.h>
//
// Global definitions
//
//
// Use reference counting on context objects
//
#define USE_REF 1
#define EVENT_ARRAY_SIZE 32
#define LockScheduleList() g_SchedulerLock.Lock();
#define UnlockScheduleList() g_SchedulerLock.Unlock();
#define SIGNATURE_SCHED (DWORD)'SCHa'
#define SIGNATURE_SCHED_FREE (DWORD)'SCHf'
class SCHED_ITEM { public:
SCHED_ITEM( PFN_SCHED_CALLBACK pfnCallback, PVOID pContext, DWORD msecTime, int nPriority, DWORD dwSerial, HANDLE hEvent) :m_pfnCallback ( pfnCallback ), m_pContext ( pContext ), m_nPriority ( nPriority ), m_dwSerialNumber( dwSerial ), m_hRegisteredEventHandle(hEvent), m_Signature ( SIGNATURE_SCHED ) { if (m_hRegisteredEventHandle) { m_msecExpires = INFINITE; } else { m_msecExpires = GetTickCount() + msecTime; } }
~SCHED_ITEM( VOID ) { ASSERT(m_ListEntry.Flink == NULL ); m_Signature = SIGNATURE_SCHED_FREE; }
BOOL CheckSignature( VOID ) const { return m_Signature == SIGNATURE_SCHED; }
#ifdef DEBUG
VOID DumpObject(VOID) { /* This will cause problems in 64bit (the m_Signature)....
DBG_TRC(("ScheduleWorkItem: Dumping itself:this(%X) Sign(%4c) ListEntry(%X,%X,%X) Ser#(%d) Context(%X)"), \ this,(char *)m_Signature, &m_ListEntry,m_ListEntry.Flink,m_ListEntry.Blink, m_dwSerialNumber,m_pContext); */ } #endif
LIST_ENTRY m_ListEntry; // Connection field
DWORD m_Signature; // Validity verification
PFN_SCHED_CALLBACK m_pfnCallback; // Work processing callback
PVOID m_pContext; // Context pointer ( usually object ptr)
DWORD m_msecExpires; // Time when timeout expires for this item (in ms)
int m_nPriority; //
DWORD m_dwSerialNumber; // To identify work item when removing
HANDLE m_hRegisteredEventHandle; //
};
DWORD SchedulerThread( LPDWORD lpdwParam );
//
// Global data items
//
CRIT_SECT g_SchedulerLock; LIST_ENTRY g_ScheduleListHead;
BOOL g_fSchedulerInitialized = FALSE; BOOL g_fSchedulePaused = FALSE; BOOL g_fSchedShutdown = FALSE;
HANDLE g_hSchedulerEvent;
HANDLE g_aEventArray[EVENT_ARRAY_SIZE]; UINT g_uiUsedHandles = 0;
//
// Used as identification for work items, incremented on each new item allocated
//
static LONG g_dwSchedSerial = 0;
BOOL SchedulerInitialize( VOID ) /*++
Routine Description:
Initializes the scheduler package
Arguments:
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/ { DWORD idThread; HANDLE hSchedulerThread;
if ( g_fSchedulerInitialized ) return TRUE;
::ZeroMemory(g_aEventArray,sizeof(g_aEventArray));
g_hSchedulerEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( !g_hSchedulerEvent ) { return FALSE; }
// Save event handle in global array as first element
g_aEventArray[g_uiUsedHandles++] = g_hSchedulerEvent;
InitializeListHead( &g_ScheduleListHead );
hSchedulerThread = ::CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) SchedulerThread, NULL, 0, &idThread );
if ( !hSchedulerThread ) { CloseHandle( g_hSchedulerEvent ); return FALSE; }
CloseHandle( hSchedulerThread );
g_fSchedulerInitialized = TRUE;
STIMONWPRINTF(TEXT("Work item scheduler initialized"));
return TRUE; }
VOID SchedulerTerminate( VOID ) /*++
Routine Description:
Terminates and cleans up the scheduling package. Any items left on the list are *not* called during cleanup.
--*/ { SCHED_ITEM *psi;
if ( !g_fSchedulerInitialized ) return;
g_fSchedShutdown = TRUE;
SetEvent( g_hSchedulerEvent ) ;
// Protected code block
{ TAKE_CRIT_SECT t(g_SchedulerLock);
//
// Delete all of the items that were scheduled, note we do *not*
// call any scheduled items in the list (there shouldn't be any)
//
while ( !IsListEmpty( &g_ScheduleListHead ) ) {
psi = CONTAINING_RECORD( g_ScheduleListHead.Flink, SCHED_ITEM, m_ListEntry );
ASSERT( psi->CheckSignature() );
RemoveEntryList( &psi->m_ListEntry ); psi->m_ListEntry.Flink = NULL;
delete psi; } }
Sleep( 250 );
CloseHandle( g_hSchedulerEvent );
g_fSchedulerInitialized = FALSE; }
BOOL SchedulerSetPauseState( BOOL fNewState ) { BOOL fOldState = g_fSchedulePaused;
g_fSchedulePaused = fNewState;
return fOldState; }
DWORD ScheduleWorkItem( PFN_SCHED_CALLBACK pfnCallback, PVOID pContext, DWORD msecTime, HANDLE hEvent, int nPriority ) /*++
Routine Description:
Adds a timed work item to the work list
Arguments:
pfnCallback - Function to call pContext - Context to pass to the callback hEvent - handle of event to wait on before signalling callback msecTime - number of milliseconds to wait before calling timeout nPriority - Thread priority to set for work item
Return Value:
zero on failure, non-zero on success. The return value can be used to remove the scheduled work item.
--*/ { SCHED_ITEM *psi; SCHED_ITEM *psiList; LIST_ENTRY *pEntry; DWORD dwRet = 0; BOOL fValidRequest = FALSE;
ASSERT( g_fSchedulerInitialized );
if ( !g_fSchedulerInitialized ) return 0;
//
// Scheduler currently only supports normal thread priority
//
ASSERT( nPriority == THREAD_PRIORITY_NORMAL );
InterlockedIncrement(&g_dwSchedSerial);
psi = new SCHED_ITEM( pfnCallback, pContext, msecTime, nPriority, g_dwSchedSerial, hEvent );
if ( !psi ) { return 0; }
// BEGIN PROTECTED CODE
{ TAKE_CRIT_SECT t(g_SchedulerLock);
// Commented out to reduce debug output when lock management auto-unlocking is enabled
//DBG_TRC(("Scheduler adding work item (%X) "),psi);
//
// Validate scheduling request. If it timer based - always valid
// if it is passing event handle, we are limited by the size of wait array, so first check to see
// if array is not full
//
fValidRequest = TRUE; if (hEvent && (hEvent!=INVALID_HANDLE_VALUE)) { if (g_uiUsedHandles < EVENT_ARRAY_SIZE) { g_aEventArray[g_uiUsedHandles++] = hEvent; } else { fValidRequest = FALSE; dwRet = 0; } }
//
// Insert the list in order based on expires time
//
if(fValidRequest) {
for ( pEntry = g_ScheduleListHead.Flink; pEntry != &g_ScheduleListHead; pEntry = pEntry->Flink ) {
psiList = CONTAINING_RECORD( pEntry, SCHED_ITEM,m_ListEntry );
if ( psiList->m_msecExpires > psi->m_msecExpires ) { break; } }
//
// This should work in whether the list is empty or this is the last item
// on the list
//
psi->m_ListEntry.Flink = pEntry; psi->m_ListEntry.Blink = pEntry->Blink;
pEntry->Blink->Flink = &psi->m_ListEntry; pEntry->Blink = &psi->m_ListEntry;
dwRet = psi->m_dwSerialNumber;
#if 0
DBG_TRC(("Scheduler added work item (%X) with cookie(%d) before (%X). Head=(%X) "),psi,psi->m_dwSerialNumber,pEntry,&g_ScheduleListHead); psi->DumpObject(); #endif
}
} // END PROTECTED CODE
//
// Kick off scheduler thread
//
if(fValidRequest) { SetEvent( g_hSchedulerEvent ); } else { delete psi; }
return dwRet; }
BOOL RemoveWorkItem( DWORD dwCookie ) /*++
Routine Description:
Removes a scheduled work item
Arguments:
dwCookie - The return value from a previous call to ScheduleWorkItem
Return Value:
TRUE if the item was found, FALSE if the item was not found.
--*/ { SCHED_ITEM * psi; LIST_ENTRY * pEntry;
// BEGIN PROTECTED CODE
{ TAKE_CRIT_SECT t(g_SchedulerLock);
DBG_TRC(("Schedule::RemoveWorkItem (%X) ", dwCookie));
//
// Walk the list to find an item with matching id
//
for ( pEntry = g_ScheduleListHead.Flink; pEntry != &g_ScheduleListHead; pEntry = pEntry->Flink ) {
psi = CONTAINING_RECORD( pEntry, SCHED_ITEM,m_ListEntry );
ASSERT( psi->CheckSignature() );
if ( dwCookie == psi->m_dwSerialNumber ) {
//
// Found our item
//
#if 0
DBG_TRC(("Scheduler removing work item (%X) "),psi); psi->DumpObject(); #endif
RemoveEntryList( pEntry ); pEntry->Flink = NULL;
//
// If this work item is associated with asyncronous event , remove event handle
// from wait array
//
if (psi->m_hRegisteredEventHandle && psi->m_hRegisteredEventHandle != INVALID_HANDLE_VALUE) {
UINT uiIndex;
// Find handle in wait array
for (uiIndex = 0; uiIndex < g_uiUsedHandles; uiIndex++ ) {
if ( g_aEventArray[uiIndex] == psi->m_hRegisteredEventHandle ) {
memcpy(&g_aEventArray[uiIndex], &g_aEventArray[uiIndex+1], sizeof(g_aEventArray[0])*(g_uiUsedHandles - uiIndex - 1) );
g_aEventArray[g_uiUsedHandles--] = NULL; } } }
// Destroy work item now
delete psi;
return TRUE; } }
} // END PROTECTED CODE
//
// Item with given number not found
//
return FALSE; }
DWORD SchedulerThread( LPDWORD lpdwParam ) /*++
Routine Description:
Initializes the scheduler/timer package
Arguments:
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/ { DWORD cmsecWait = INFINITE; DWORD TickCount; SCHED_ITEM *psi = NULL; LIST_ENTRY *pEntry; DWORD dwErr; UINT uiSignalledIndex; BOOL fFoundSignalledItem = FALSE;
while ( TRUE ) {
dwErr = ::WaitForMultipleObjects(g_uiUsedHandles, g_aEventArray, FALSE, cmsecWait );
uiSignalledIndex = dwErr - WAIT_OBJECT_0;
//
// If we're shutting down, get out
//
if ( g_fSchedShutdown ) { goto Exit; }
#if 0
DebugDumpScheduleList(TEXT("SchedulerThread")); #endif
switch (dwErr) { default: if ((uiSignalledIndex > 0) && (uiSignalledIndex < g_uiUsedHandles )) { //
// One of the devices signalled event. Find work item for this device
// and invoke callback
//
LockScheduleList()
fFoundSignalledItem = FALSE;
for ( pEntry = g_ScheduleListHead.Flink; pEntry != &g_ScheduleListHead; pEntry = pEntry->Flink ) {
psi = CONTAINING_RECORD( pEntry, SCHED_ITEM,m_ListEntry );
ASSERT( psi->CheckSignature() );
if ( g_aEventArray[uiSignalledIndex] == psi->m_hRegisteredEventHandle ) {
fFoundSignalledItem = TRUE;
RemoveEntryList( &psi->m_ListEntry ); psi->m_ListEntry.Flink = NULL;
#ifdef USE_REF
// Reference context object
if(psi->m_pContext) { ((IUnknown *)psi->m_pContext)->AddRef(); } #endif
//
// Delete event handle from the array
//
if (uiSignalledIndex < g_uiUsedHandles-1 ) { memcpy(&g_aEventArray[uiSignalledIndex], &g_aEventArray[uiSignalledIndex+1], sizeof(g_aEventArray[0])*(g_uiUsedHandles - uiSignalledIndex - 1) ); }
g_aEventArray[g_uiUsedHandles--] = NULL;
break; } }
UnlockScheduleList()
//
// If signalled item had been found and verified - invoke callback and remove it
//
if (fFoundSignalledItem) {
if(psi->m_pContext) { psi->m_pfnCallback( psi->m_pContext ); #ifdef USE_REF
((IUnknown *)psi->m_pContext)->Release(); #endif
}
delete psi; }
continue; }
//
// Fall through to signalled scheduler event
//
case WAIT_OBJECT_0:
//
// Means a new item has been scheduled, reset the timeout or
// we are shutting down
//
if ( g_fSchedShutdown ) { goto Exit; }
LockScheduleList();
//
// Get the timeout value for the first item in the list
//
if ( !IsListEmpty( &g_ScheduleListHead ) ) {
psi = CONTAINING_RECORD( g_ScheduleListHead.Flink, SCHED_ITEM, m_ListEntry );
ASSERT( psi->CheckSignature() );
//
// Make sure the front item hasn't already expired
//
TickCount = GetTickCount();
if ( TickCount > psi->m_msecExpires ) { // We have at least one work item needing attention
goto RunItems; }
cmsecWait = psi->m_msecExpires - TickCount; } else { cmsecWait = INFINITE; }
UnlockScheduleList();
break;
case WAIT_TIMEOUT:
StartAgain: //
// If we're shutting down, get out
//
if ( g_fSchedShutdown ) { goto Exit; }
if (g_fSchedulePaused ) { continue; }
TickCount = GetTickCount();
//
// Walk the sorted list for expired work items
//
LockScheduleList(); RunItems: //
// If no items, schedule no timeout
//
if ( IsListEmpty( &g_ScheduleListHead )) { cmsecWait = INFINITE; }
for ( pEntry = g_ScheduleListHead.Flink; pEntry != &g_ScheduleListHead; ) { psi = CONTAINING_RECORD( pEntry, SCHED_ITEM,m_ListEntry );
ASSERT( psi->CheckSignature() );
//
// Go through expired items, skipping the ones with event handle set
//
if ( (TickCount > psi->m_msecExpires) && !psi->m_hRegisteredEventHandle ) {
pEntry = pEntry->Flink;
// Take item off the list
RemoveEntryList( &psi->m_ListEntry ); psi->m_ListEntry.Flink = NULL;
#ifdef USE_REF
// Reference context object
if(psi->m_pContext) { ((IUnknown *)psi->m_pContext)->AddRef(); } #endif
//
// Unlock the list so clients can add additional
// schedule items
//
UnlockScheduleList();
if (psi->m_pContext) { psi->m_pfnCallback( psi->m_pContext ); #ifdef USE_REF
((IUnknown *)psi->m_pContext)->Release(); #endif
}
delete psi;
//
// Start looking in the list from the beginning in case
// new items have been added or removed
//
goto StartAgain; } else { //
// Since they are in sorted order once we hit one that's
// not expired we don't need to look further
//
cmsecWait = psi->m_msecExpires - TickCount; break; } }
UnlockScheduleList();
break; } } // while ( TRUE )
Exit:
return 0; }
#ifdef DEBUG
VOID DebugDumpScheduleList( LPCTSTR pszId ) { if ( !g_fSchedulerInitialized ) { STIMONWPRINTF(TEXT("Schedule list not initialized")); return; }
LIST_ENTRY * pentry; LIST_ENTRY * pentryNext;
SCHED_ITEM * psi = NULL;
TAKE_CRIT_SECT t(g_SchedulerLock);
DBG_TRC(("Validating schedule list . Called from (%S)" ,pszId ? pszId : TEXT("Unknown")));
for ( pentry = g_ScheduleListHead.Flink; pentry != &g_ScheduleListHead; pentry = pentryNext ) {
pentryNext = pentry->Flink;
psi = CONTAINING_RECORD( pentry, SCHED_ITEM,m_ListEntry );
ASSERT( psi->CheckSignature() ); psi->DumpObject();
} }
#endif
|