mirror of https://github.com/tongzx/nt5src
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.
1105 lines
29 KiB
1105 lines
29 KiB
/*++
|
|
|
|
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()
|