1106 lines
29 KiB
C++
1106 lines
29 KiB
C++
/*++
|
||
|
||
Copyright (c) 1995-2000 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:
|
||
Murali R. Krishnan (MuraliK) 16-Sept-1996
|
||
Added scheduler items cache
|
||
George V. Reilly (GeorgeRe) May-1999
|
||
Removed the global variables; turned into refcounted objects, so
|
||
that code will survive stops and restarts when work items take a
|
||
long time to complete
|
||
--*/
|
||
|
||
//
|
||
// Include Headers
|
||
//
|
||
|
||
#include "precomp.hxx"
|
||
#include <objbase.h>
|
||
|
||
#define DLL_IMPLEMENTATION
|
||
#define IMPLEMENTATION_EXPORT
|
||
#include <irtldbg.h>
|
||
#include "sched.hxx"
|
||
|
||
|
||
// Initialize class static members
|
||
CSchedData* CSchedData::sm_psd = NULL;
|
||
CLockedDoubleList CSchedData::sm_lstSchedulers;
|
||
LONG CSchedData::sm_nID = 0;
|
||
LONG CThreadData::sm_nID = 1000;
|
||
LONG SCHED_ITEM::sm_lSerialNumber = SCHED_ITEM::SERIAL_NUM_INITIAL_VALUE;
|
||
|
||
|
||
|
||
|
||
// SCHED_ITEM Finite State Machine
|
||
SCHED_ITEM_STATE sg_rgSchedNextState[SI_OP_MAX][SI_MAX_ITEMS] = {
|
||
|
||
// operation = SI_OP_ADD
|
||
{ // old state
|
||
SI_ERROR, // SI_ERROR
|
||
SI_ACTIVE, // SI_IDLE
|
||
SI_ERROR, // SI_ACTIVE
|
||
SI_ERROR, // SI_ACTIVE_PERIODIC
|
||
SI_ERROR, // SI_CALLBACK_PERIODIC
|
||
SI_ERROR, // SI_TO_BE_DELETED
|
||
},
|
||
|
||
// operation = SI_OP_ADD_PERIODIC
|
||
{ // old state
|
||
SI_ERROR, // SI_ERROR
|
||
SI_ACTIVE_PERIODIC, // SI_IDLE
|
||
SI_ERROR, // SI_ACTIVE
|
||
SI_ERROR, // SI_ACTIVE_PERIODIC
|
||
SI_ACTIVE_PERIODIC, // SI_CALLBACK_PERIODIC: rescheduling
|
||
// periodic item
|
||
SI_ERROR, // SI_TO_BE_DELETED
|
||
},
|
||
|
||
// operation = SI_OP_CALLBACK
|
||
{ // old state
|
||
SI_ERROR, // SI_ERROR
|
||
SI_ERROR, // SI_IDLE
|
||
SI_TO_BE_DELETED, // SI_ACTIVE: to be removed after completing
|
||
// callbacks
|
||
SI_CALLBACK_PERIODIC, // SI_ACTIVE_PERIODIC
|
||
SI_ERROR, // SI_CALLBACK_PERIODIC
|
||
SI_ERROR, // SI_TO_BE_DELETED
|
||
},
|
||
|
||
// operation = SI_OP_DELETE
|
||
{ // old state
|
||
SI_ERROR, // SI_ERROR
|
||
SI_ERROR, // SI_IDLE
|
||
SI_IDLE, // SI_ACTIVE
|
||
SI_IDLE, // SI_ACTIVE_PERIODIC
|
||
SI_TO_BE_DELETED, // SI_CALLBACK_PERIODIC: mark this to be
|
||
// deleted after return
|
||
SI_TO_BE_DELETED, // SI_TO_BE_DELETED: idempotent delete ops
|
||
}
|
||
};
|
||
|
||
|
||
|
||
//
|
||
// Global data items
|
||
//
|
||
LONG cSchedInits = 0;
|
||
LONG cSchedUninits = 0;
|
||
|
||
|
||
/************************************************************
|
||
* Public functions of Scheduler
|
||
************************************************************/
|
||
|
||
|
||
|
||
BOOL
|
||
SchedulerInitialize(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initializes the scheduler/timer package
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
TRUE if successful, FALSE on error (call GetLastError)
|
||
|
||
--*/
|
||
{
|
||
// g_fErrorFlags |= DEBUG_SCHED;
|
||
// g_pDebug->m_iControlFlag |= DEBUG_SCHED;
|
||
|
||
++cSchedInits;
|
||
|
||
unsigned idThread;
|
||
LONG i, numThreads;
|
||
CSchedData* const psd = new CSchedData();
|
||
|
||
if (psd == NULL || !psd->IsValid())
|
||
return FALSE;
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT, "SchedulerInitialize: inits=%d, uninits=%d\n",
|
||
cSchedInits, cSchedUninits));
|
||
}
|
||
|
||
if ( TsIsNtServer() ) {
|
||
numThreads = max(NumProcessors(), NUM_SCHEDULE_THREADS_NTS);
|
||
} else {
|
||
numThreads = NUM_SCHEDULE_THREADS_PWS;
|
||
}
|
||
|
||
numThreads = min(numThreads, MAX_THREADS);
|
||
// numThreads = MAX_THREADS;
|
||
|
||
DBG_ASSERT(numThreads > 0);
|
||
|
||
for (i = 0; i < numThreads; ++i)
|
||
{
|
||
CThreadData* ptd = new CThreadData(psd);
|
||
|
||
if (ptd == NULL || !ptd->IsValid())
|
||
{
|
||
numThreads = i;
|
||
if (ptd != NULL)
|
||
ptd->Release();
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (numThreads == 0)
|
||
{
|
||
delete psd;
|
||
return FALSE;
|
||
}
|
||
|
||
// Kick scheduler threads into life now that everything has been
|
||
// initialized.
|
||
psd->m_lstThreads.Lock();
|
||
|
||
for (CListEntry* ple = psd->m_lstThreads.First();
|
||
ple != psd->m_lstThreads.HeadNode();
|
||
ple = ple->Flink)
|
||
{
|
||
CThreadData* ptd = CONTAINING_RECORD(ple, CThreadData, m_leThreads);
|
||
DBG_ASSERT(ptd->CheckSignature());
|
||
ResumeThread(ptd->m_hThreadSelf);
|
||
}
|
||
|
||
psd->m_lstThreads.Unlock();
|
||
|
||
// Update the global pointer to the scheduler
|
||
CSchedData* const psd2 =
|
||
(CSchedData*) InterlockedExchangePointer((VOID**)&CSchedData::sm_psd, psd);
|
||
|
||
DBG_ASSERT(psd2 == NULL);
|
||
|
||
return TRUE;
|
||
} // SchedulerInitialize()
|
||
|
||
|
||
|
||
VOID
|
||
SchedulerTerminate(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Terminates and cleans up the scheduling package. Any items left on the
|
||
list are *not* called during cleanup.
|
||
|
||
--*/
|
||
{
|
||
// Grab the global pointer, then set it to NULL
|
||
CSchedData* const psd =
|
||
(CSchedData*) InterlockedExchangePointer((VOID**)&CSchedData::sm_psd, NULL);
|
||
|
||
DBG_ASSERT(psd == NULL || !psd->m_fShutdown);
|
||
|
||
++cSchedUninits;
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT, "SchedulerTerminate: inits=%d, uninits=%d\n",
|
||
cSchedInits, cSchedUninits));
|
||
}
|
||
|
||
if (psd == NULL || psd->m_fShutdown)
|
||
{
|
||
// already shutting down
|
||
return;
|
||
}
|
||
|
||
psd->Terminate();
|
||
}
|
||
|
||
|
||
void
|
||
CSchedData::Terminate()
|
||
{
|
||
HANDLE ahThreadIds[MAX_THREADS];
|
||
int i;
|
||
CListEntry* ple;
|
||
|
||
m_fShutdown = TRUE;
|
||
|
||
const int nMaxTries = 1;
|
||
const DWORD dwTimeOut = INFINITE;
|
||
|
||
for (int iTries = 0; iTries < nMaxTries; ++iTries)
|
||
{
|
||
int nThreads = 0;
|
||
|
||
m_lstThreads.Lock();
|
||
|
||
for (ple = m_lstThreads.First(), i = 0;
|
||
ple != m_lstThreads.HeadNode();
|
||
ple = ple->Flink, i++)
|
||
{
|
||
CThreadData* ptd = CONTAINING_RECORD(ple, CThreadData,
|
||
m_leThreads);
|
||
DBG_ASSERT(ptd->CheckSignature());
|
||
// Set the shutdown event once for each thread
|
||
DBGPRINTF(( DBG_CONTEXT, "CSchedData::Terminate: iteration %d, "
|
||
"notifying %ld\n", iTries, ptd->m_nID));
|
||
DBG_REQUIRE( SetEvent(ptd->m_hevtShutdown) );
|
||
ahThreadIds[i] = ptd->m_hThreadSelf;
|
||
++nThreads;
|
||
}
|
||
|
||
m_lstThreads.Unlock();
|
||
|
||
if (nThreads == 0)
|
||
break;
|
||
|
||
// Wait for all the threads to shut down
|
||
DWORD dw = WaitForMultipleObjects(nThreads, ahThreadIds,
|
||
TRUE, dwTimeOut);
|
||
DBGPRINTF(( DBG_CONTEXT, "CSchedData::Terminate: WFMO = %x\n", dw));
|
||
}
|
||
|
||
LockItems();
|
||
|
||
//
|
||
// 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 ( !m_lstItems.IsEmpty() )
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"[CSchedData::Terminate] Warning - Items in schedule list "
|
||
"at termination\n" ));
|
||
int c = 0;
|
||
|
||
CListEntry* pleSave = NULL;
|
||
|
||
for (ple = m_lstItems.First();
|
||
ple != m_lstItems.HeadNode();
|
||
ple = pleSave)
|
||
{
|
||
SCHED_ITEM* psiList = CONTAINING_RECORD( ple, SCHED_ITEM,
|
||
_ListEntry );
|
||
DBG_ASSERT(psiList->CheckSignature());
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
|
||
psiList,
|
||
psiList->_dwSerialNumber,
|
||
psiList->_pContext,
|
||
psiList->_pfnCallback,
|
||
psiList->_siState,
|
||
psiList->_dwCallbackThreadId
|
||
));
|
||
}
|
||
|
||
pleSave = ple->Flink;
|
||
|
||
if (!psiList->FInsideCallbackOnOtherThread())
|
||
{
|
||
CDoubleList::RemoveEntry( &psiList->_ListEntry );
|
||
ple->Flink = NULL;
|
||
DeleteSchedItem(psiList);
|
||
++c;
|
||
}
|
||
}
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"[CSchedData::Terminate] %d items deleted\n", c ));
|
||
}
|
||
|
||
UnlockItems();
|
||
|
||
Release(); // release the last reference to `this'
|
||
} // CSchedData::Terminate()
|
||
|
||
|
||
CSchedData::~CSchedData()
|
||
{
|
||
DBG_ASSERT(m_lstThreads.IsEmpty());
|
||
DBG_ASSERT(m_lstItems.IsEmpty());
|
||
DBG_ASSERT(m_cThreads == 0);
|
||
DBG_ASSERT(m_cRefs == 0);
|
||
|
||
sm_lstSchedulers.RemoveEntry(&m_leGlobalList);
|
||
CloseHandle(m_hevtNotify);
|
||
delete m_pachSchedItems;
|
||
|
||
CListEntry* pleSave = NULL;
|
||
for (CListEntry* ple = m_lstDeadThreads.First();
|
||
ple != m_lstDeadThreads.HeadNode();
|
||
ple = pleSave)
|
||
{
|
||
pleSave = ple->Flink;
|
||
CThreadData* ptd = CONTAINING_RECORD(ple, CThreadData, m_leThreads);
|
||
DBG_ASSERT(ptd->CheckSignature());
|
||
delete ptd;
|
||
}
|
||
|
||
m_dwSignature = SIGNATURE_SCHEDDATA_FREE;
|
||
}
|
||
|
||
|
||
|
||
DWORD
|
||
WINAPI
|
||
ScheduleWorkItem(
|
||
PFN_SCHED_CALLBACK pfnCallback,
|
||
PVOID pContext,
|
||
DWORD msecTime,
|
||
BOOL fPeriodic
|
||
)
|
||
/*++
|
||
|
||
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.
|
||
|
||
--*/
|
||
{
|
||
CSchedData* const psd = CSchedData::Scheduler();
|
||
|
||
if (psd == NULL)
|
||
return 0;
|
||
|
||
//
|
||
// 1. alloc a new scheduler item
|
||
//
|
||
|
||
SCHED_ITEM* psi = psd->NewSchedItem(pfnCallback, pContext, msecTime);
|
||
|
||
if ( !psi )
|
||
{
|
||
// unable to create the item - return error cookie '0'
|
||
return 0;
|
||
}
|
||
|
||
DWORD dwRet = psi->_dwSerialNumber;
|
||
SCHED_OPS siop = ((fPeriodic)? SI_OP_ADD_PERIODIC : SI_OP_ADD);
|
||
psi->_siState = sg_rgSchedNextState[siop][SI_IDLE];
|
||
|
||
//
|
||
// 2. Insert the scheduler item into the active scheduler work-items list.
|
||
//
|
||
|
||
psd->LockItems();
|
||
psd->InsertIntoWorkItemList( psi);
|
||
psd->UnlockItems();
|
||
|
||
//
|
||
// 3. Indicate to scheduler threads that there is one new item on the list
|
||
//
|
||
|
||
DBG_REQUIRE( SetEvent( psd->m_hevtNotify ));
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"ScheduleWorkItem: (%d) "
|
||
"[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
|
||
psd->m_nID,
|
||
psi,
|
||
psi->_dwSerialNumber,
|
||
psi->_pContext,
|
||
psi->_pfnCallback,
|
||
psi->_siState,
|
||
psi->_dwCallbackThreadId
|
||
));
|
||
}
|
||
|
||
return dwRet;
|
||
} // ScheduleWorkItem()
|
||
|
||
|
||
|
||
BOOL
|
||
WINAPI
|
||
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.
|
||
|
||
--*/
|
||
{
|
||
CSchedData* const psd = CSchedData::Scheduler();
|
||
|
||
if (psd == NULL)
|
||
return FALSE;
|
||
|
||
SCHED_ITEM* psi = NULL;
|
||
BOOL fWait = FALSE;
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT, "RemoveWorkItem: cookie=%d sched=%d\n",
|
||
dwCookie, psd->m_nID));
|
||
}
|
||
|
||
//
|
||
// 1. lock the list
|
||
//
|
||
|
||
psd->LockItems();
|
||
|
||
//
|
||
// 2. Find scheduler item on the list.
|
||
//
|
||
|
||
psi = psd->FindSchedulerItem( dwCookie);
|
||
|
||
if ( NULL != psi)
|
||
{
|
||
|
||
//
|
||
// 3. based on the state of the item take necessary actions.
|
||
//
|
||
|
||
SCHED_ITEM_STATE st =
|
||
sg_rgSchedNextState[SI_OP_DELETE][psi->_siState];
|
||
psi->_siState = st;
|
||
switch ( st)
|
||
{
|
||
|
||
case SI_TO_BE_DELETED:
|
||
{
|
||
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"SchedItem(%08p) marked to be deleted\n",
|
||
psi));
|
||
// item will be deleted later.
|
||
|
||
if (psi->FInsideCallbackOnOtherThread()) {
|
||
// need to wait till callback complete
|
||
psi->AddEvent();
|
||
fWait = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
// fallthru
|
||
|
||
case SI_IDLE:
|
||
{
|
||
// delete immediately
|
||
CDoubleList::RemoveEntry( &psi->_ListEntry );
|
||
psi->_ListEntry.Flink = NULL;
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"RemoveWorkItem: "
|
||
"[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
|
||
psi,
|
||
psi->_dwSerialNumber,
|
||
psi->_pContext,
|
||
psi->_pfnCallback,
|
||
psi->_siState,
|
||
psi->_dwCallbackThreadId
|
||
));
|
||
}
|
||
|
||
psd->DeleteSchedItem(psi);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
DBG_ASSERT( FALSE);
|
||
break;
|
||
} // switch()
|
||
}
|
||
|
||
// 4. Unlock the list
|
||
psd->UnlockItems();
|
||
|
||
// 5. Wait for callback event and release the item
|
||
if (fWait)
|
||
{
|
||
LONG l = psi->WaitForEventAndRelease();
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT, "RemoveWorkItem: %d "
|
||
"WaitForEventAndRelease returned %d.\n",
|
||
dwCookie, l));
|
||
}
|
||
if (l == 0)
|
||
psd->DeleteSchedItem(psi);
|
||
}
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
if ( NULL == psi)
|
||
DBGPRINTF(( DBG_CONTEXT, "RemoveWorkItem: %d not found\n",
|
||
dwCookie));
|
||
}
|
||
|
||
// return TRUE if we found the item
|
||
return ( NULL != psi);
|
||
} // RemoveWorkItem()
|
||
|
||
|
||
|
||
|
||
|
||
DWORD
|
||
WINAPI
|
||
ScheduleAdjustTime(
|
||
DWORD dwCookie,
|
||
DWORD msecNewTime
|
||
)
|
||
/*++
|
||
This function finds the scheduler object for given cookie and
|
||
changes the interval for next timeout of the item. Returns a
|
||
Win32 error code: NO_ERROR => success.
|
||
|
||
--*/
|
||
{
|
||
CSchedData* const psd = CSchedData::Scheduler();
|
||
|
||
if (psd == NULL)
|
||
return ERROR_NO_DATA;
|
||
|
||
DBG_ASSERT( 0 != dwCookie);
|
||
|
||
psd->LockItems();
|
||
|
||
// 1. Find the work item for given cookie
|
||
SCHED_ITEM* psi = psd->FindSchedulerItem( dwCookie);
|
||
|
||
if ( NULL != psi)
|
||
{
|
||
|
||
// 2. Remove the item from the list
|
||
CDoubleList::RemoveEntry( &psi->_ListEntry );
|
||
psi->_ListEntry.Flink = NULL;
|
||
|
||
// 3. Change the timeout value
|
||
|
||
psi->ChangeTimeInterval( msecNewTime);
|
||
|
||
// 4. Recalc expiry time and reinsert into the list of work items.
|
||
psi->CalcExpiresTime();
|
||
psd->InsertIntoWorkItemList( psi);
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"ScheduleAdjustTime: "
|
||
"[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
|
||
psi,
|
||
psi->_dwSerialNumber,
|
||
psi->_pContext,
|
||
psi->_pfnCallback,
|
||
psi->_siState,
|
||
psi->_dwCallbackThreadId
|
||
));
|
||
}
|
||
|
||
}
|
||
|
||
psd->UnlockItems();
|
||
|
||
// 5. Indicate to scheduler threads that there is one new item on the list
|
||
if (NULL != psi)
|
||
DBG_REQUIRE( SetEvent( psd->m_hevtNotify ));
|
||
|
||
return ( (NULL != psi) ? NO_ERROR : ERROR_INVALID_PARAMETER);
|
||
} // ScheduleAdjustTime()
|
||
|
||
|
||
|
||
/************************************************************
|
||
* Internal functions of Scheduler
|
||
************************************************************/
|
||
|
||
|
||
|
||
VOID
|
||
CSchedData::InsertIntoWorkItemList(SCHED_ITEM* psi)
|
||
{
|
||
SCHED_ITEM* psiList;
|
||
CListEntry* ple;
|
||
|
||
DBG_ASSERT( NULL != psi);
|
||
DBG_ASSERT(psi->CheckSignature());
|
||
DBG_ASSERT( (psi->_siState == SI_ACTIVE) ||
|
||
(psi->_siState == SI_ACTIVE_PERIODIC) ||
|
||
(psi->_siState == SI_CALLBACK_PERIODIC ) );
|
||
|
||
// Assumed that the scheduler list is locked.
|
||
DBG_ASSERT(m_lstItems.IsLocked());
|
||
|
||
//
|
||
// Insert the list in order based on expires time
|
||
//
|
||
|
||
for ( ple = m_lstItems.First();
|
||
ple != m_lstItems.HeadNode();
|
||
ple = ple->Flink )
|
||
{
|
||
psiList = CONTAINING_RECORD( ple, SCHED_ITEM, _ListEntry );
|
||
DBG_ASSERT(psiList->CheckSignature());
|
||
|
||
if ( psiList->_msecExpires > psi->_msecExpires )
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Insert the item psi in front of the item ple
|
||
// This should work in whether the list is empty or this is the last item
|
||
// on the circular list
|
||
//
|
||
|
||
psi->_ListEntry.Flink = ple;
|
||
psi->_ListEntry.Blink = ple->Blink;
|
||
|
||
ple->Blink->Flink = &psi->_ListEntry;
|
||
ple->Blink = &psi->_ListEntry;
|
||
|
||
return;
|
||
} // InsertIntoWorkItemList()
|
||
|
||
|
||
SCHED_ITEM*
|
||
CSchedData::FindSchedulerItem(DWORD dwCookie)
|
||
{
|
||
CListEntry* ple;
|
||
SCHED_ITEM* psi = NULL;
|
||
|
||
// Should be called with the scheduler list locked.
|
||
DBG_ASSERT(m_lstItems.IsLocked());
|
||
|
||
for ( ple = m_lstItems.First();
|
||
ple != m_lstItems.HeadNode();
|
||
ple = ple->Flink )
|
||
{
|
||
psi = CONTAINING_RECORD( ple, SCHED_ITEM, _ListEntry );
|
||
DBG_ASSERT( psi->CheckSignature() );
|
||
|
||
if ( dwCookie == psi->_dwSerialNumber )
|
||
{
|
||
|
||
// found the match - return
|
||
return ( psi);
|
||
}
|
||
} // for
|
||
|
||
return ( NULL);
|
||
} // FindSchedulerItem()
|
||
|
||
|
||
|
||
unsigned
|
||
__stdcall
|
||
SchedulerWorkerThread(
|
||
void* pvParam
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
ThreadProc for scheduler.
|
||
|
||
Arguments:
|
||
|
||
Unused.
|
||
|
||
Return Value:
|
||
|
||
TRUE if successful, FALSE on error (call GetLastError)
|
||
|
||
--*/
|
||
{
|
||
CThreadData* const ptd = (CThreadData*) pvParam;
|
||
DBG_ASSERT( ptd != NULL );
|
||
|
||
CSchedData* const psd = ptd->m_psdOwner;
|
||
DBG_ASSERT( psd != NULL );
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF(( DBG_CONTEXT, "SchedulerWorkerThread (%d) starting: "
|
||
"CThreadData=%p CSchedData=%p, %d\n",
|
||
ptd->m_nID,
|
||
ptd, psd, psd->m_nID));
|
||
}
|
||
|
||
int cExecuted = 0;
|
||
DWORD cmsecWait = INFINITE;
|
||
__int64 TickCount;
|
||
SCHED_ITEM * psi, * psiExpired;
|
||
CListEntry * ple;
|
||
BOOL fListLocked = FALSE;
|
||
DWORD dwWait;
|
||
HRESULT hr;
|
||
HANDLE ahEvt[2] = {psd->m_hevtNotify, ptd->m_hevtShutdown};
|
||
|
||
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||
|
||
if (hr != S_OK && hr != S_FALSE)
|
||
{
|
||
DBG_ASSERT(FALSE);
|
||
return FALSE;
|
||
}
|
||
|
||
while (!psd->m_fShutdown)
|
||
{
|
||
DBG_ASSERT(!fListLocked); // the list must be unlocked here
|
||
|
||
while ( TRUE )
|
||
{
|
||
|
||
MSG msg;
|
||
|
||
//
|
||
// Need to do MsgWait instead of WaitForSingleObject
|
||
// to process windows msgs. We now have a window
|
||
// because of COM.
|
||
//
|
||
|
||
dwWait = MsgWaitForMultipleObjects( 2,
|
||
ahEvt,
|
||
FALSE, // wait for anything
|
||
cmsecWait,
|
||
QS_ALLINPUT );
|
||
|
||
if (psd->m_fShutdown)
|
||
goto exit;
|
||
|
||
if ( (dwWait == WAIT_OBJECT_0) || // psd->m_hevtNotify
|
||
(dwWait == WAIT_OBJECT_0 + 1) || // ptd->m_hevtShutdown
|
||
(dwWait == WAIT_TIMEOUT) )
|
||
{
|
||
break;
|
||
}
|
||
|
||
while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
|
||
{
|
||
TranslateMessage( &msg );
|
||
DispatchMessage( &msg );
|
||
}
|
||
}
|
||
|
||
switch (dwWait)
|
||
{
|
||
default:
|
||
DBGPRINTF(( DBG_CONTEXT,
|
||
"[Scheduler] Error %d waiting on SchedulerEvent\n",
|
||
GetLastError() ));
|
||
// Fall through
|
||
|
||
case WAIT_OBJECT_0:
|
||
// Means a new item has been scheduled, reset the timeout, or
|
||
// we are shutting down
|
||
|
||
psd->LockItems();
|
||
fListLocked = TRUE;
|
||
|
||
// Get the timeout value for the first item in the list
|
||
|
||
if (!psd->m_lstItems.IsEmpty())
|
||
{
|
||
psi = CONTAINING_RECORD( psd->m_lstItems.First(),
|
||
SCHED_ITEM,
|
||
_ListEntry );
|
||
DBG_ASSERT(psi->CheckSignature());
|
||
|
||
// Make sure the front item hasn't already expired
|
||
|
||
TickCount = GetCurrentTimeInMilliseconds();
|
||
|
||
if (TickCount > psi->_msecExpires)
|
||
{
|
||
// Run scheduled items
|
||
break;
|
||
}
|
||
|
||
// the delay is guaranteed NOT to be > 1<<32
|
||
// as per parameter to SCHED_ITEM constructor
|
||
|
||
cmsecWait = (DWORD)(psi->_msecExpires - TickCount);
|
||
}
|
||
else
|
||
{
|
||
cmsecWait = INFINITE;
|
||
}
|
||
|
||
psd->UnlockItems();
|
||
fListLocked = FALSE;
|
||
|
||
// Wait for something else (back to sleep)
|
||
continue;
|
||
|
||
case WAIT_TIMEOUT:
|
||
// Run scheduled items
|
||
break;
|
||
}
|
||
|
||
// Run scheduled items
|
||
|
||
while (!psd->m_fShutdown)
|
||
{
|
||
// Lock the list if needed
|
||
|
||
if (!fListLocked)
|
||
{
|
||
psd->LockItems();
|
||
fListLocked = TRUE;
|
||
}
|
||
|
||
// No timeout by default (if no items found)
|
||
|
||
cmsecWait = INFINITE;
|
||
|
||
if (psd->m_lstItems.IsEmpty())
|
||
break;
|
||
|
||
// Find the first expired work item
|
||
|
||
TickCount = GetCurrentTimeInMilliseconds();
|
||
|
||
psiExpired = NULL;
|
||
|
||
for ( ple = psd->m_lstItems.First();
|
||
ple != psd->m_lstItems.HeadNode();
|
||
ple = ple->Flink
|
||
)
|
||
{
|
||
psi = CONTAINING_RECORD(ple, SCHED_ITEM, _ListEntry);
|
||
DBG_ASSERT(psi->CheckSignature());
|
||
|
||
if ( ((psi->_siState == SI_ACTIVE) ||
|
||
(psi->_siState == SI_ACTIVE_PERIODIC)) )
|
||
{
|
||
|
||
if (TickCount > psi->_msecExpires)
|
||
{
|
||
// Found Expired Item
|
||
psiExpired = psi;
|
||
}
|
||
else
|
||
{
|
||
// Since they are in sorted order, once we hit one
|
||
// that's not expired, we don't need to look further
|
||
cmsecWait = (DWORD)(psi->_msecExpires - TickCount);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// If no expired work items found, go back to sleep
|
||
|
||
if (psiExpired == NULL)
|
||
{
|
||
break;
|
||
}
|
||
|
||
// Take care of the found expired work item
|
||
|
||
SCHED_ITEM_STATE st =
|
||
sg_rgSchedNextState[SI_OP_CALLBACK][psiExpired->_siState];
|
||
|
||
psiExpired->_siState = st;
|
||
psiExpired->_dwCallbackThreadId = GetCurrentThreadId();
|
||
|
||
DBG_ASSERT(st == SI_TO_BE_DELETED || st == SI_CALLBACK_PERIODIC);
|
||
|
||
// Unlock the list while in the callback
|
||
DBG_ASSERT(fListLocked);
|
||
psd->UnlockItems();
|
||
fListLocked = FALSE;
|
||
|
||
// While in PERIODIC callback the list is kept unlocked
|
||
// leaving the object exposed
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"SchedulerWorkerThread (%d): starting %scall: "
|
||
"ser=%d ctxt=%p fncbk=%p state=%d\n",
|
||
ptd->m_nID,
|
||
(st == SI_CALLBACK_PERIODIC) ? "periodic " : "",
|
||
psiExpired->_dwSerialNumber,
|
||
psiExpired->_pContext,
|
||
psiExpired->_pfnCallback,
|
||
psiExpired->_siState));
|
||
}
|
||
|
||
// Is this still a valid function? There have been problems
|
||
// with scheduler clients (such as isatq.dll) getting unloaded,
|
||
// without first calling RemoveWorkItem to clean up.
|
||
if (IsBadCodePtr(reinterpret_cast<FARPROC>(psiExpired->_pfnCallback)))
|
||
{
|
||
DWORD dwErr = GetLastError();
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"SchedulerWorkerThread (%d): "
|
||
"invalid callback function %p, error %d\n",
|
||
ptd->m_nID,
|
||
psiExpired->_pfnCallback, dwErr));
|
||
}
|
||
psiExpired->_siState = SI_TO_BE_DELETED;
|
||
goto relock;
|
||
}
|
||
|
||
__try
|
||
{
|
||
psiExpired->_pfnCallback(psiExpired->_pContext);
|
||
}
|
||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||
{
|
||
DWORD dwErr = GetExceptionCode();
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"SchedulerWorkerThread (%d): "
|
||
"exception %d in callback function %p\n",
|
||
ptd->m_nID,
|
||
dwErr, psiExpired->_pfnCallback));
|
||
}
|
||
psiExpired->_siState = SI_TO_BE_DELETED;
|
||
}
|
||
|
||
++cExecuted;
|
||
|
||
IF_DEBUG(SCHED)
|
||
{
|
||
DBGPRINTF((DBG_CONTEXT,
|
||
"SchedulerWorkerThread (%d): finished %scall: "
|
||
"ser=%d ctxt=%p fncbk=%p state=%d\n",
|
||
ptd->m_nID,
|
||
(st == SI_CALLBACK_PERIODIC) ? "periodic " : "",
|
||
psiExpired->_dwSerialNumber,
|
||
psiExpired->_pContext,
|
||
psiExpired->_pfnCallback,
|
||
psiExpired->_siState));
|
||
}
|
||
|
||
relock:
|
||
// Relock the list
|
||
DBG_ASSERT(!fListLocked);
|
||
psd->LockItems();
|
||
fListLocked = TRUE;
|
||
|
||
psiExpired->_dwCallbackThreadId = 0;
|
||
|
||
// While in the callback the state can change
|
||
if (psiExpired->_siState == SI_TO_BE_DELETED)
|
||
{
|
||
// User requested delete
|
||
|
||
// Remove this item from the list
|
||
CDoubleList::RemoveEntry( &psiExpired->_ListEntry );
|
||
psiExpired->_ListEntry.Flink = NULL;
|
||
|
||
// While in callback RemoveWorkItem() could have attached
|
||
// an event to notify itself when callback is done
|
||
if (psiExpired->_hCallbackEvent)
|
||
{
|
||
// Signal the event after item is gone from the list
|
||
SetEvent(psiExpired->_hCallbackEvent);
|
||
// RemoveWorkItem() will remove the item
|
||
}
|
||
else
|
||
{
|
||
// Get rid of the item
|
||
psd->DeleteSchedItem(psiExpired);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// no events attached
|
||
DBG_ASSERT(psiExpired->_hCallbackEvent == NULL);
|
||
|
||
// must still remain SI_CALLBACK_PERIODIC unless deleted
|
||
DBG_ASSERT(psiExpired->_siState == SI_CALLBACK_PERIODIC);
|
||
|
||
// NYI: For now remove from the list and reinsert it
|
||
CDoubleList::RemoveEntry( &psiExpired->_ListEntry );
|
||
psiExpired->_ListEntry.Flink = NULL;
|
||
|
||
// recalc the expiry time and reinsert into the list
|
||
psiExpired->_siState =
|
||
sg_rgSchedNextState[SI_OP_ADD_PERIODIC]
|
||
[psiExpired->_siState];
|
||
psiExpired->CalcExpiresTime();
|
||
psd->InsertIntoWorkItemList(psiExpired);
|
||
}
|
||
|
||
// Start looking in the list from the beginning in case
|
||
// new items have been added or other threads removed them
|
||
|
||
} // while
|
||
|
||
if (fListLocked)
|
||
{
|
||
psd->UnlockItems();
|
||
fListLocked = FALSE;
|
||
}
|
||
|
||
} // while
|
||
|
||
exit:
|
||
CoUninitialize();
|
||
|
||
// Destroy the thread object
|
||
ptd->Release();
|
||
|
||
return cExecuted;
|
||
} // SchedulerWorkerThread()
|