mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
536 lines
11 KiB
536 lines
11 KiB
/*++
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
sched.cxx
|
|
|
|
Abstract:
|
|
|
|
This module contains a simple timer interface for scheduling future
|
|
work items
|
|
|
|
|
|
Author:
|
|
|
|
John Ludeman (johnl) 17-Jul-1995
|
|
|
|
Project:
|
|
|
|
Internet Servers Common Server DLL
|
|
|
|
Revisions:
|
|
|
|
|
|
--*/
|
|
|
|
//
|
|
// Include Headers
|
|
//
|
|
|
|
#include <tcpdllp.hxx>
|
|
#include <tsproc.hxx>
|
|
#include "tssched.hxx"
|
|
|
|
//
|
|
// Global definitions
|
|
//
|
|
|
|
#define LockScheduleList() EnterCriticalSection( &csSchedulerLock )
|
|
#define UnlockScheduleList() LeaveCriticalSection( &csSchedulerLock )
|
|
|
|
#define NUM_SCHEDULE_THREADS_PWS 1
|
|
#define NUM_SCHEDULE_THREADS_NTS 2
|
|
|
|
#define SIGNATURE_SCHED 0x45456767
|
|
#define SIGNATURE_SCHED_FREE 0x78788989
|
|
|
|
class SCHED_ITEM
|
|
{
|
|
public:
|
|
|
|
SCHED_ITEM( PFN_SCHED_CALLBACK pfnCallback,
|
|
PVOID pContext,
|
|
DWORD msecTime,
|
|
int nPriority,
|
|
DWORD dwSerial )
|
|
: _pfnCallback ( pfnCallback ),
|
|
_pContext ( pContext ),
|
|
_nPriority ( nPriority ),
|
|
_dwSerialNumber( dwSerial ),
|
|
_Signature ( SIGNATURE_SCHED )
|
|
{
|
|
_msecExpires = GetTickCount() + msecTime;
|
|
}
|
|
|
|
~SCHED_ITEM( VOID )
|
|
{
|
|
DBG_ASSERT( _ListEntry.Flink == NULL );
|
|
_Signature = SIGNATURE_SCHED_FREE;
|
|
}
|
|
|
|
BOOL CheckSignature( VOID ) const
|
|
{ return _Signature == SIGNATURE_SCHED; }
|
|
|
|
LIST_ENTRY _ListEntry;
|
|
DWORD _Signature;
|
|
PFN_SCHED_CALLBACK _pfnCallback;
|
|
PVOID _pContext;
|
|
DWORD _msecExpires;
|
|
int _nPriority;
|
|
DWORD _dwSerialNumber;
|
|
};
|
|
|
|
DWORD
|
|
SchedulerThread(
|
|
LPDWORD lpdwParam
|
|
);
|
|
|
|
//
|
|
// Global data items
|
|
//
|
|
|
|
LIST_ENTRY ScheduleListHead;
|
|
CRITICAL_SECTION csSchedulerLock;
|
|
BOOL fSchedulerInitialized = FALSE;
|
|
BOOL fSchedShutdown = FALSE;
|
|
HANDLE hSchedulerEvent;
|
|
DWORD cSchedThreads = 0;
|
|
|
|
//
|
|
// Used as identification cookie for removing items
|
|
//
|
|
|
|
DWORD dwSchedSerial = 0;
|
|
|
|
BOOL
|
|
SchedulerInitialize(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the scheduler/timer package
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE on error (call GetLastError)
|
|
|
|
--*/
|
|
{
|
|
DWORD idThread;
|
|
DWORD i;
|
|
DWORD numThreads;
|
|
|
|
if ( fSchedulerInitialized )
|
|
return TRUE;
|
|
|
|
hSchedulerEvent = CreateEvent( NULL,
|
|
FALSE,
|
|
FALSE,
|
|
NULL );
|
|
|
|
if ( !hSchedulerEvent ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( TsIsNtServer() ) {
|
|
numThreads = NUM_SCHEDULE_THREADS_NTS;
|
|
} else {
|
|
numThreads = NUM_SCHEDULE_THREADS_PWS;
|
|
}
|
|
|
|
InitializeCriticalSection( &csSchedulerLock );
|
|
|
|
InitializeListHead( &ScheduleListHead );
|
|
|
|
for ( i = 0; i < numThreads; i++ ) {
|
|
|
|
HANDLE hSchedulerThread;
|
|
|
|
hSchedulerThread = CreateThread( NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE) SchedulerThread,
|
|
NULL,
|
|
0,
|
|
&idThread );
|
|
|
|
if ( !hSchedulerThread )
|
|
{
|
|
DeleteCriticalSection( &csSchedulerLock );
|
|
CloseHandle( hSchedulerEvent );
|
|
return FALSE;
|
|
}
|
|
|
|
DBG_REQUIRE( CloseHandle( hSchedulerThread ));
|
|
|
|
cSchedThreads++;
|
|
}
|
|
|
|
fSchedulerInitialized = TRUE;
|
|
|
|
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.
|
|
|
|
--*/
|
|
{
|
|
if ( !fSchedulerInitialized )
|
|
return;
|
|
|
|
fSchedShutdown = TRUE;
|
|
|
|
DBG_REQUIRE( SetEvent( hSchedulerEvent ) );
|
|
|
|
LockScheduleList();
|
|
|
|
//
|
|
// Delete all of the items that were scheduled, note we do *not*
|
|
// call any scheduled items in the list (there shouldn't be any)
|
|
//
|
|
|
|
if ( !IsListEmpty( &ScheduleListHead ) )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[SchedulerTerminate] Warning - Items in schedule list "
|
|
"at termination\n" ));
|
|
}
|
|
|
|
UnlockScheduleList();
|
|
|
|
while ( cSchedThreads )
|
|
{
|
|
Sleep( 250 );
|
|
}
|
|
|
|
DeleteCriticalSection( &csSchedulerLock );
|
|
CloseHandle( hSchedulerEvent );
|
|
|
|
fSchedulerInitialized = FALSE;
|
|
}
|
|
|
|
DWORD
|
|
ScheduleWorkItem(
|
|
PFN_SCHED_CALLBACK pfnCallback,
|
|
PVOID pContext,
|
|
DWORD msecTime,
|
|
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
|
|
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;
|
|
|
|
DBG_ASSERT( fSchedulerInitialized );
|
|
|
|
//
|
|
// Scheduler currently only supports normal thread priority
|
|
//
|
|
|
|
DBG_ASSERT( nPriority == THREAD_PRIORITY_NORMAL );
|
|
|
|
psi = new SCHED_ITEM( pfnCallback,
|
|
pContext,
|
|
msecTime,
|
|
nPriority,
|
|
++dwSchedSerial );
|
|
|
|
if ( !psi )
|
|
return 0;
|
|
|
|
LockScheduleList();
|
|
|
|
//
|
|
// Insert the list in order based on expires time
|
|
//
|
|
|
|
for ( pEntry = ScheduleListHead.Flink;
|
|
pEntry != &ScheduleListHead;
|
|
pEntry = pEntry->Flink )
|
|
{
|
|
psiList = CONTAINING_RECORD( pEntry, SCHED_ITEM, _ListEntry );
|
|
|
|
if ( psiList->_msecExpires > psi->_msecExpires )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This should work in whether the list is empty or this is the last item
|
|
// on the list
|
|
//
|
|
|
|
psi->_ListEntry.Flink = pEntry;
|
|
psi->_ListEntry.Blink = pEntry->Blink;
|
|
|
|
pEntry->Blink->Flink = &psi->_ListEntry;
|
|
pEntry->Blink = &psi->_ListEntry;
|
|
|
|
dwRet = psi->_dwSerialNumber;
|
|
|
|
UnlockScheduleList();
|
|
|
|
DBG_REQUIRE( SetEvent( hSchedulerEvent ));
|
|
|
|
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;
|
|
|
|
LockScheduleList();
|
|
|
|
//
|
|
// We have to walk the list when removing an item to avoid the race when
|
|
// freeing a block
|
|
//
|
|
|
|
for ( pEntry = ScheduleListHead.Flink;
|
|
pEntry != &ScheduleListHead;
|
|
pEntry = pEntry->Flink )
|
|
{
|
|
psi = CONTAINING_RECORD( pEntry, SCHED_ITEM, _ListEntry );
|
|
|
|
DBG_ASSERT( psi->CheckSignature() );
|
|
|
|
if ( dwCookie == psi->_dwSerialNumber )
|
|
{
|
|
RemoveEntryList( pEntry );
|
|
pEntry->Flink = NULL;
|
|
|
|
UnlockScheduleList();
|
|
|
|
delete psi;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
UnlockScheduleList();
|
|
|
|
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;
|
|
LIST_ENTRY * pEntry;
|
|
|
|
while ( TRUE )
|
|
{
|
|
switch ( WaitForSingleObject( hSchedulerEvent, cmsecWait ))
|
|
{
|
|
default:
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[SchedulerThread] Error %d waiting on hSchedulerEvent\n",
|
|
GetLastError() ));
|
|
//
|
|
// Fall through
|
|
//
|
|
|
|
case WAIT_OBJECT_0:
|
|
|
|
//
|
|
// Means a new item has been scheduled, reset the timeout or
|
|
// we are shutting down
|
|
//
|
|
|
|
if ( fSchedShutdown )
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
LockScheduleList();
|
|
|
|
//
|
|
// Get the timeout value for the first item in the list
|
|
//
|
|
|
|
if ( !IsListEmpty( &ScheduleListHead ) )
|
|
{
|
|
psi = CONTAINING_RECORD( ScheduleListHead.Flink,
|
|
SCHED_ITEM,
|
|
_ListEntry );
|
|
|
|
DBG_ASSERT( psi->CheckSignature() );
|
|
|
|
//
|
|
// Make sure the front item hasn't already expired
|
|
//
|
|
|
|
TickCount = GetTickCount();
|
|
|
|
if ( TickCount > psi->_msecExpires )
|
|
{
|
|
goto RunItems;
|
|
}
|
|
|
|
cmsecWait = psi->_msecExpires - TickCount;
|
|
}
|
|
else
|
|
{
|
|
cmsecWait = INFINITE;
|
|
}
|
|
|
|
UnlockScheduleList();
|
|
|
|
break;
|
|
|
|
case WAIT_TIMEOUT:
|
|
|
|
StartAgain:
|
|
//
|
|
// If we're shutting down, get out
|
|
//
|
|
|
|
if ( fSchedShutdown )
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
TickCount = GetTickCount();
|
|
|
|
//
|
|
// Walk the sorted list for expired work items
|
|
//
|
|
|
|
LockScheduleList();
|
|
RunItems:
|
|
//
|
|
// If no items, schedule no timeout
|
|
//
|
|
|
|
if ( IsListEmpty( &ScheduleListHead ))
|
|
{
|
|
cmsecWait = INFINITE;
|
|
}
|
|
|
|
for ( pEntry = ScheduleListHead.Flink;
|
|
pEntry != &ScheduleListHead;
|
|
)
|
|
{
|
|
psi = CONTAINING_RECORD( pEntry, SCHED_ITEM, _ListEntry );
|
|
|
|
DBG_ASSERT( psi->CheckSignature() );
|
|
|
|
if ( TickCount > psi->_msecExpires )
|
|
{
|
|
pEntry = pEntry->Flink;
|
|
|
|
RemoveEntryList( &psi->_ListEntry );
|
|
psi->_ListEntry.Flink = NULL;
|
|
|
|
//
|
|
// Unlock the list so clients can add additional
|
|
// schedule items
|
|
//
|
|
|
|
UnlockScheduleList();
|
|
|
|
psi->_pfnCallback( psi->_pContext );
|
|
|
|
delete psi;
|
|
|
|
//
|
|
// Start looking in the list from the beginning in case
|
|
// new items have been added or other threads removed
|
|
// them
|
|
//
|
|
|
|
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->_msecExpires - TickCount;
|
|
break;
|
|
}
|
|
}
|
|
|
|
UnlockScheduleList();
|
|
|
|
break;
|
|
}
|
|
} // while ( TRUE )
|
|
|
|
Exit:
|
|
|
|
InterlockedDecrement( (LONG *) &cSchedThreads );
|
|
return 0;
|
|
}
|