windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/iisrtl/sched.cxx

1106 lines
29 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
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()