|
|
/******************************************************************************
* * Copyright (C) 1999-2002 Microsoft Corporation. All Rights Reserved. * * File: timers.cpp * * Content: DirectPlay Thread Pool timer functions. * * History: * Date By Reason * ======== ======== ========= * 10/31/01 VanceO Based on DPlay Protocol Quick Start Timers. * ******************************************************************************/
#include "dpnthreadpooli.h"
#undef DPF_MODNAME
#define DPF_MODNAME "InitializeWorkQueueTimerInfo"
//=============================================================================
// InitializeWorkQueueTimerInfo
//-----------------------------------------------------------------------------
//
// Description: Initializes the timer info for the given work queue.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to initialize.
//
// Returns: HRESULT
// DPN_OK - Successfully initialized the work queue object's
// timer information.
// DPNERR_OUTOFMEMORY - Failed to allocate memory while initializing.
//=============================================================================
HRESULT InitializeWorkQueueTimerInfo(DPTPWORKQUEUE * const pWorkQueue) { HRESULT hr; DWORD dwTemp; #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
LARGE_INTEGER liDueTime; HANDLE hTimer; #ifdef DBG
DWORD dwError; #endif // DBG
#endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
#ifdef DPNBUILD_DYNAMICTIMERSETTINGS
pWorkQueue->dwTimerBucketGranularity = DEFAULT_TIMER_BUCKET_GRANULARITY; //
// The granularity must be a power of 2 in order for our ceiling, mask and
// divisor optimizations to work.
//
DNASSERT(! ((pWorkQueue->dwTimerBucketGranularity - 1) & pWorkQueue->dwTimerBucketGranularity)); pWorkQueue->dwTimerBucketGranularityCeiling = pWorkQueue->dwTimerBucketGranularity - 1; pWorkQueue->dwTimerBucketGranularityFloorMask = ~(pWorkQueue->dwTimerBucketGranularityCeiling); // negating the ceiling round factor (which happens to also be the modulo mask) gives us the floor mask
pWorkQueue->dwTimerBucketGranularityDivisor = pWorkQueue->dwTimerBucketGranularity >> 1;
pWorkQueue->dwNumTimerBuckets = DEFAULT_NUM_TIMER_BUCKETS; //
// The bucket count must be a power of 2 in order for our mask optimizations
// to work.
//
DNASSERT(! ((pWorkQueue->dwNumTimerBuckets - 1) & pWorkQueue->dwNumTimerBuckets)); pWorkQueue->dwNumTimerBucketsModMask = pWorkQueue->dwNumTimerBuckets - 1; #endif // DPNBUILD_DYNAMICTIMERSETTINGS
//
// Initialize the last process time, rounding down to the last whole bucket.
//
pWorkQueue->dwLastTimerProcessTime = GETTIMESTAMP() & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue); DPFX(DPFPREP, 7, "Current bucket index = %u at time %u, array time length = %u.", ((pWorkQueue->dwLastTimerProcessTime / TIMER_BUCKET_GRANULARITY(pWorkQueue)) % NUM_TIMER_BUCKETS(pWorkQueue)), pWorkQueue->dwLastTimerProcessTime, (TIMER_BUCKET_GRANULARITY(pWorkQueue) * NUM_TIMER_BUCKETS(pWorkQueue)));
//
// Allocate the timer bucket array.
//
pWorkQueue->paSlistTimerBuckets = (DNSLIST_HEADER*) DNMalloc(NUM_TIMER_BUCKETS(pWorkQueue) * sizeof(DNSLIST_HEADER)); if (pWorkQueue->paSlistTimerBuckets == NULL) { DPFX(DPFPREP, 0, "Couldn't allocate memory for %u timer buckets!", NUM_TIMER_BUCKETS(pWorkQueue)); hr = DPNERR_OUTOFMEMORY; goto Failure; }
//
// Initialize all of the timer buckets.
//
for(dwTemp = 0; dwTemp < NUM_TIMER_BUCKETS(pWorkQueue); dwTemp++) { DNInitializeSListHead(&pWorkQueue->paSlistTimerBuckets[dwTemp]); }
#if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
pWorkQueue->dwPossibleMissedTimerWindow = 0; #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
#if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
//
// Create a waitable timer for the timer thread.
//
hTimer = CreateWaitableTimer(NULL, FALSE, NULL); if (hTimer == NULL) { #ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create waitable timer (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; } pWorkQueue->hTimer = MAKE_DNHANDLE(hTimer);
//
// Kick off the waitable timer so that it wakes up every 'granularity'
// milliseconds. Tell it not to start until 1 resolution period elapses
// because there probably aren't any timers yet, and more importantly,
// there probably aren't any threads that are waiting yet. Delaying the
// start hopefully will reduce unnecessary CPU usage.
//
// The due-time value is negative because that indicates relative time to
// SetWaitableTimer, and multiplied by 10000 because it is in 100
// nanosecond increments.
//
liDueTime.QuadPart = -1 * TIMER_BUCKET_GRANULARITY(pWorkQueue) * 10000; if (! SetWaitableTimer(HANDLE_FROM_DNHANDLE(pWorkQueue->hTimer), &liDueTime, TIMER_BUCKET_GRANULARITY(pWorkQueue), NULL, NULL, FALSE)) { #ifdef DBG
dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create waitable timer (err = %u)!", dwError); #endif // DBG
hr = DPNERR_OUTOFMEMORY; goto Failure; }
#pragma TODO(vanceo, "We should avoid setting this timer until there are threads, and stop it when there aren't any")
#endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Initialize the timer package debugging/tuning statistics.
//
pWorkQueue->dwTotalNumTimerChecks = 0; pWorkQueue->dwTotalNumBucketsProcessed = 0; pWorkQueue->dwTotalNumTimersScheduled = 0; pWorkQueue->dwTotalNumLongTimersRescheduled = 0; pWorkQueue->dwTotalNumSuccessfulCancels = 0; pWorkQueue->dwTotalNumFailedCancels = 0; #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
pWorkQueue->dwTotalPossibleMissedTimerWindows = 0; #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
#endif // DPNBUILD_THREADPOOLSTATISTICS
hr = DPN_OK;
Exit:
return hr;
Failure:
#if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
if (pWorkQueue->hTimer != NULL) { DNCloseHandle(pWorkQueue->hTimer); pWorkQueue->hTimer = NULL; } #endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
if (pWorkQueue->paSlistTimerBuckets != NULL) { DNFree(pWorkQueue->paSlistTimerBuckets); pWorkQueue->paSlistTimerBuckets = NULL; }
goto Exit; } // InitializeWorkQueueTimerInfo
#undef DPF_MODNAME
#define DPF_MODNAME "DeinitializeWorkQueueTimerInfo"
//=============================================================================
// DeinitializeWorkQueueTimerInfo
//-----------------------------------------------------------------------------
//
// Description: Cleans up work queue timer info.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to initialize.
//
// Returns: Nothing.
//=============================================================================
void DeinitializeWorkQueueTimerInfo(DPTPWORKQUEUE * const pWorkQueue) { DWORD dwTemp; DNSLIST_ENTRY * pSlistEntry; CWorkItem * pWorkItem; #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
BOOL fResult;
fResult = DNCloseHandle(pWorkQueue->hTimer); DNASSERT(fResult); pWorkQueue->hTimer = NULL; #endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
//
// Empty out the timer buckets. The only thing left should be cancelled
// timers that the threads/DoWork didn't happen to clean up.
//
DNASSERT(pWorkQueue->paSlistTimerBuckets != NULL); for(dwTemp = 0; dwTemp < NUM_TIMER_BUCKETS(pWorkQueue); dwTemp++) { //
// This doesn't really need to be interlocked since no one should be
// using it anymore, but oh well...
//
pSlistEntry = DNInterlockedFlushSList(&pWorkQueue->paSlistTimerBuckets[dwTemp]); while (pSlistEntry != NULL) { pWorkItem = CONTAINING_OBJECT(pSlistEntry, CWorkItem, m_SlistEntry); pSlistEntry = pSlistEntry->Next;
//
// Make sure the item has been cancelled as noted above.
//
DNASSERT(pWorkItem->m_fCancelledOrCompleting);
pWorkQueue->pWorkItemPool->Release(pWorkItem); pWorkItem = NULL; } } DNFree(pWorkQueue->paSlistTimerBuckets); pWorkQueue->paSlistTimerBuckets = NULL;
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Print our debugging/tuning statistics.
//
#ifdef DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 7, "Work queue 0x%p timer stats:", pWorkQueue); #else // ! DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 7, "Work queue 0x%p (CPU %u) timer stats:", pWorkQueue, pWorkQueue->dwCPUNum); #endif // ! DPNBUILD_ONLYONEPROCESSOR
DPFX(DPFPREP, 7, " TotalNumTimerChecks = %u", pWorkQueue->dwTotalNumTimerChecks); DPFX(DPFPREP, 7, " TotalNumBucketsProcessed = %u", pWorkQueue->dwTotalNumBucketsProcessed); DPFX(DPFPREP, 7, " TotalNumTimersScheduled = %u", pWorkQueue->dwTotalNumTimersScheduled); DPFX(DPFPREP, 7, " TotalNumLongTimersRescheduled = %u", pWorkQueue->dwTotalNumLongTimersRescheduled); DPFX(DPFPREP, 7, " TotalNumSuccessfulCancels = %u", pWorkQueue->dwTotalNumSuccessfulCancels); DPFX(DPFPREP, 7, " TotalNumFailedCancels = %u", pWorkQueue->dwTotalNumFailedCancels); #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
DPFX(DPFPREP, 7, " TotalPossibleMissedTimerWindows = %u", pWorkQueue->dwTotalPossibleMissedTimerWindows); #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
#endif // DPNBUILD_THREADPOOLSTATISTICS
} // DeinitializeWorkQueueTimerInfo
#undef DPF_MODNAME
#define DPF_MODNAME "ScheduleTimer"
//=============================================================================
// ScheduleTimer
//-----------------------------------------------------------------------------
//
// Description: Schedules a new work item for some point in the future.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
// DWORD dwDelay - How much time should elapsed before
// executing the work item, in ms.
// PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute when timer
// elapses.
// PVOID pvCallbackContext - User specified context to pass to
// callback.
// void ** ppvTimerData - Place to store pointer to data for
// timer so that it can be cancelled.
// UINT * puiTimerUnique - Place to store uniqueness value for
// timer so that it can be cancelled.
//
// Returns: BOOL
// TRUE - Successfully scheduled the item.
// FALSE - Failed to allocate memory for scheduling the item.
//=============================================================================
BOOL ScheduleTimer(DPTPWORKQUEUE * const pWorkQueue, const DWORD dwDelay, const PFNDPTNWORKCALLBACK pfnWorkCallback, PVOID const pvCallbackContext, void ** const ppvTimerData, UINT * const puiTimerUnique) { CWorkItem * pWorkItem; DWORD dwCurrentTime; DWORD dwBucket; #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
DWORD dwPossibleMissWindow; #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
//
// Delays of over 24 days seem a bit excessive.
//
DNASSERT(dwDelay < 0x80000000);
//
// Get an entry from the pool.
//
pWorkItem = (CWorkItem*) pWorkQueue->pWorkItemPool->Get(pWorkQueue); if (pWorkItem == NULL) { return FALSE; }
//
// Fill in the return values used for cancellation.
//
(*ppvTimerData) = pWorkItem; (*puiTimerUnique) = pWorkItem->m_uiUniqueID;
//
// Initialize the work item.
//
pWorkItem->m_pfnWorkCallback = pfnWorkCallback; pWorkItem->m_pvCallbackContext = pvCallbackContext;
ThreadpoolStatsCreate(pWorkItem); dwCurrentTime = GETTIMESTAMP(); pWorkItem->m_dwDueTime = dwCurrentTime + dwDelay;
//
// Calculate how far in the future this is. Round up to the next bucket
// time.
//
dwBucket = pWorkItem->m_dwDueTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue); //
// Convert into units of buckets by dividing by dwTimerBucketGranularity.
//
dwBucket = dwBucket >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue); //
// The actual index will be modulo dwNumTimerBuckets.
//
dwBucket = dwBucket & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue); //
// Note that the timer thread theoretically could be processing the bucket
// into which we are inserting, but since both threads are working from the
// same time base, as long as we are at least one bucket in the future, we
// should not get missed. We rounded up and the processing function rounds
// down in an attempt to insure that.
//
DPFX(DPFPREP, 8, "Scheduling timer work item 0x%p, context = 0x%p, due time = %u, fn = 0x%p, unique ID %u, queue = 0x%p, delay = %u, bucket = %u.", pWorkItem, pvCallbackContext, pWorkItem->m_dwDueTime, pfnWorkCallback, pWorkItem->m_uiUniqueID, pWorkQueue, dwDelay, dwBucket);
//
// Push this timer onto the appropriate timer bucket list.
//
DNInterlockedPushEntrySList(&pWorkQueue->paSlistTimerBuckets[dwBucket], &pWorkItem->m_SlistEntry);
#if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
//
// Although this function is very short, it's still possible that it took
// too long, especially on timers with miniscule delays. Give a hint to
// the timer thread that it needs to look for timers that got missed.
//
// Note that really long delays could confuse this.
//
dwPossibleMissWindow = GETTIMESTAMP() - pWorkItem->m_dwDueTime; if ((int) dwPossibleMissWindow >= 0) { DWORD dwResult;
dwPossibleMissWindow++; // make it so a value of 0 still adds something to dwPossibleMissedTimerWindow
dwResult = DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwPossibleMissedTimerWindow), dwPossibleMissWindow); DPFX(DPFPREP, 4, "Possibly missed timer work item 0x%p (delay %u ms), increased missed timer window (%u ms) by %u ms.", pWorkItem, dwDelay, dwResult, dwPossibleMissWindow); } #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the timer package debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumTimersScheduled)); #endif // DPNBUILD_THREADPOOLSTATISTICS
return TRUE; } // ScheduleTimer
#undef DPF_MODNAME
#define DPF_MODNAME "CancelTimer"
//=============================================================================
// CancelTimer
//-----------------------------------------------------------------------------
//
// Description: Attempts to cancel a timed work item. If the item is
// already in the process of completing, DPNERR_CANNOTCANCEL is
// returned, and the callback will still be (or is being) called.
// If the item could be cancelled, DPN_OK is returned and the
// callback will not be executed.
//
// Arguments:
// void * pvTimerData - Pointer to data for timer being cancelled.
// UINT uiTimerUnique - Uniqueness value for timer being cancelled.
//
// Returns: HRESULT
// DPN_OK - Successfully cancelled.
// DPNERR_CANNOTCANCEL - Could not cancel the item.
//=============================================================================
HRESULT CancelTimer(void * const pvTimerData, const UINT uiTimerUnique) { HRESULT hr; CWorkItem * pWorkItem;
//
// This cancellation lookup mechanism assumes that the memory for already
// completed entries is still allocated. If the pooling mechanism changes,
// this will have to be revised. Obviously, that also means we assume the
// memory was valid in the first place. Passing in a garbage pvTimerData
// value will cause a crash. Also see the various calls to
// pWorkItem->m_pfnWorkCallback.
//
DNASSERT(pvTimerData != NULL); pWorkItem = (CWorkItem*) pvTimerData; if (pWorkItem->m_uiUniqueID == uiTimerUnique) { //
// Attempt to mark the item as cancelled. If it was already in the
// process of completing, (or I suppose if you had already cancelled
// this same timer, but don't do that :), this would have already been
// set to TRUE.
//
if (! DNInterlockedExchange((LPLONG) (&pWorkItem->m_fCancelledOrCompleting), TRUE)) { DPFX(DPFPREP, 5, "Marked timer work item 0x%p (unique ID %u) as cancelled.", pWorkItem, uiTimerUnique);
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the timer package debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkItem->m_pWorkQueue->dwTotalNumSuccessfulCancels)); #endif // DPNBUILD_THREADPOOLSTATISTICS
hr = DPN_OK; } else { DPFX(DPFPREP, 5, "Timer work item 0x%p (unique ID %u) already completing.", pWorkItem, uiTimerUnique);
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the timer package debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkItem->m_pWorkQueue->dwTotalNumFailedCancels)); #endif // DPNBUILD_THREADPOOLSTATISTICS
hr = DPNERR_CANNOTCANCEL; } } else { DPFX(DPFPREP, 5, "Timer work item 0x%p unique ID does not match (%u != %u).", pWorkItem, pWorkItem->m_uiUniqueID, uiTimerUnique);
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the timer package debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkItem->m_pWorkQueue->dwTotalNumFailedCancels)); #endif // DPNBUILD_THREADPOOLSTATISTICS
hr = DPNERR_CANNOTCANCEL; }
return hr; } // CancelTimer
#undef DPF_MODNAME
#define DPF_MODNAME "ResetCompletingTimer"
//=============================================================================
// ResetCompletingTimer
//-----------------------------------------------------------------------------
//
// Description: Reschedules a timed work item whose callback is currently
// being called. Resetting timers that have not expired yet,
// timers that have been cancelled, or timers whose callback has
// already returned is not allowed.
//
// Arguments:
// void * pvTimerData - Pointer to data for timer being
// reset.
// DWORD dwNewDelay - How much time should elapsed
// before executing the work item
// again, in ms.
// PFNDPTNWORKCALLBACK pfnNewWorkCallback - Callback to execute when timer
// elapses.
// PVOID pvNewCallbackContext - User specified context to pass to
// callback.
// UINT * puiNewTimerUnique - Place to store new uniqueness
// value for timer so that it can
// be cancelled.
//
// Returns: None.
//=============================================================================
void ResetCompletingTimer(void * const pvTimerData, const DWORD dwNewDelay, const PFNDPTNWORKCALLBACK pfnNewWorkCallback, PVOID const pvNewCallbackContext, UINT *const puiNewTimerUnique) { CWorkItem * pWorkItem; DPTPWORKQUEUE * pWorkQueue; DWORD dwCurrentTime; DWORD dwBucket; #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
DWORD dwPossibleMissWindow; #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
//
// The timer must be valid, similar to CancelTimer.
//
DNASSERT(pvTimerData != NULL); pWorkItem = (CWorkItem*) pvTimerData; DNASSERT(pWorkItem->m_fCancelledOrCompleting);
//
// Delays of over 24 days seem a bit excessive.
//
DNASSERT(dwNewDelay < 0x80000000);
pWorkQueue = pWorkItem->m_pWorkQueue;
//
// Reinitialize the work item.
//
pWorkItem->m_pfnWorkCallback = pfnNewWorkCallback; pWorkItem->m_pvCallbackContext = pvNewCallbackContext;
ThreadpoolStatsCreate(pWorkItem);
dwCurrentTime = GETTIMESTAMP(); pWorkItem->m_dwDueTime = dwCurrentTime + dwNewDelay; pWorkItem->m_fCancelledOrCompleting = FALSE; pWorkItem->m_uiUniqueID++; //
// Fill in the return value for cancellation.
//
(*puiNewTimerUnique) = pWorkItem->m_uiUniqueID;
//
// Calculate how far in the future this is. Round up to the next bucket
// time.
//
dwBucket = pWorkItem->m_dwDueTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue); //
// Convert into units of buckets by dividing by dwTimerBucketGranularity.
//
dwBucket = dwBucket >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue); //
// The actual index will be modulo dwNumTimerBuckets.
//
dwBucket = dwBucket & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue); //
// Note that the timer thread theoretically could be processing the bucket
// into which we are inserting, but since both threads are working from the
// same time base, as long as we are at least one bucket in the future, we
// should not get missed. We rounded up and the processing function rounds
// down in an attempt to insure that.
//
DPFX(DPFPREP, 8, "Rescheduling timer work item 0x%p, context = 0x%p, due time = %u, fn = 0x%p, unique ID %u, queue = 0x%p, delay = %u, bucket = %u.", pWorkItem, pvNewCallbackContext, pWorkItem->m_dwDueTime, pfnNewWorkCallback, pWorkItem->m_uiUniqueID, pWorkQueue, dwNewDelay, dwBucket);
//
// Push this timer onto the appropriate timer bucket list.
//
DNInterlockedPushEntrySList(&pWorkQueue->paSlistTimerBuckets[dwBucket], &pWorkItem->m_SlistEntry);
#if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
//
// Although this function is very short, it's still possible that it took
// too long, especially on timers with miniscule delays. Give a hint to
// the timer thread that it needs to look for timers that got missed.
//
// Note that really long delays could confuse this.
//
dwPossibleMissWindow = GETTIMESTAMP() - pWorkItem->m_dwDueTime; if ((int) dwPossibleMissWindow >= 0) { DWORD dwResult;
dwPossibleMissWindow++; // make it so a value of 0 still adds something to dwPossibleMissedTimerWindow
dwResult = DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwPossibleMissedTimerWindow), dwPossibleMissWindow); DPFX(DPFPREP, 4, "Possibly missed timer work item 0x%p (delay %u ms), increased missed timer window (%u ms) by %u ms.", pWorkItem, dwNewDelay, dwResult, dwPossibleMissWindow); } #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the timer package debugging/tuning statistics.
//
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumTimersScheduled)); #endif // DPNBUILD_THREADPOOLSTATISTICS
} // ResetCompletingTimer
#undef DPF_MODNAME
#define DPF_MODNAME "ProcessTimers"
//=============================================================================
// ProcessTimers
//-----------------------------------------------------------------------------
//
// Description: Queues any expired timers as work items and performs lazy
// pool releases of cancelled timers.
//
// When this implementation does not use I/O completion ports,
// the new work items are added to the passed in list without
// using Interlocked functions.
//
// It is assumed that only one thread will call this function
// at a time.
//
// Arguments:
// DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
// DNSLIST_ENTRY ** ppHead - Pointer to initial list head pointer, and
// place to store new head pointer.
// DNSLIST_ENTRY ** ppTail - Pointer to existing list tail pointer, or
// place to store new tail pointer.
// USHORT * pusCount - Pointer to existing list count, it will be
// updated to reflect new items.
//
// Returns: Nothing.
//=============================================================================
#ifdef DPNBUILD_USEIOCOMPLETIONPORTS
void ProcessTimers(DPTPWORKQUEUE * const pWorkQueue) #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
void ProcessTimers(DPTPWORKQUEUE * const pWorkQueue, DNSLIST_ENTRY ** const ppHead, DNSLIST_ENTRY ** const ppTail, USHORT * const pusCount) #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
{ DWORD dwCurrentTime; DWORD dwElapsedTime; DWORD dwExpiredBuckets; DWORD dwBucket; #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
DWORD dwPossibleMissedTimerWindow; #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
DNSLIST_ENTRY * pSlistEntry; CWorkItem * pWorkItem; BOOL fCancelled; #ifdef DBG
BOOL fNotServicedForLongTime = FALSE; DWORD dwBucketTime; #endif // DBG
//
// Retrieve the current time, rounding down to the last fully completed
// bucket time slice.
//
dwCurrentTime = GETTIMESTAMP() & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue);
#ifndef DPNBUILD_DONTCHECKFORMISSEDTIMERS
#ifdef DPNBUILD_NOMISSEDTIMERSHINT
//
// Always re-check the previous bucket.
//
pWorkQueue->dwLastTimerProcessTime -= TIMER_BUCKET_GRANULARITY(pWorkQueue); #else // ! DPNBUILD_NOMISSEDTIMERSHINT
//
// See if any threads hinted that there might be missed timers. If so, we
// will artifically open the window a bit more, hopefully to include them.
//
dwPossibleMissedTimerWindow = DNInterlockedExchange((LPLONG) (&pWorkQueue->dwPossibleMissedTimerWindow), 0); dwPossibleMissedTimerWindow = (dwPossibleMissedTimerWindow + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue)) & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue); #ifdef DPNBUILD_THREADPOOLSTATISTICS
pWorkQueue->dwTotalPossibleMissedTimerWindows += dwPossibleMissedTimerWindow; #endif // DPNBUILD_THREADPOOLSTATISTICS
pWorkQueue->dwLastTimerProcessTime -= dwPossibleMissedTimerWindow; #endif // ! DPNBUILD_NOMISSEDTIMERSHINT
#endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS
//
// See if enough time has elapsed to cause any buckets to expire. If not,
// there's nothing to do.
//
dwElapsedTime = dwCurrentTime - pWorkQueue->dwLastTimerProcessTime; if (dwElapsedTime > 0) { //DPFX(DPFPREP, 9, "Processing timers for worker queue 0x%p, rounded time = %u, elapsed time = %u", pWorkQueue, dwCurrentTime, dwElapsedTime);
//
// The time difference should be an even multiple of the timer bucket
// granularity (negating the floor mask gives us the modulo mask).
//
DNASSERT((dwElapsedTime & ~(TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue))) == 0); //
// We should not have failed to run for over 24 days, so if this assert
// fires, time probably went backward or some such nonsense.
//
DNASSERT(dwElapsedTime < 0x80000000);
//
// Figure out how many buckets we need to process by dividing by
// dwTimerBucketGranularity.
//
dwExpiredBuckets = dwElapsedTime >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue); if (dwExpiredBuckets > NUM_TIMER_BUCKETS(pWorkQueue)) { //
// A really long time has elapsed since the last time we serviced
// the timers (equal to or longer than the range of the entire
// array). We must complete everything that is on the array. To
// prevent us from walking the same bucket more than once, cap the
// number we're going to check.
//
dwExpiredBuckets = NUM_TIMER_BUCKETS(pWorkQueue); #ifdef DBG
fNotServicedForLongTime = FALSE; #endif // DBG
}
#ifdef DPNBUILD_THREADPOOLSTATISTICS
//
// Update the timer package debugging/tuning statistics.
//
pWorkQueue->dwTotalNumTimerChecks++; pWorkQueue->dwTotalNumBucketsProcessed += dwExpiredBuckets; #endif // DPNBUILD_THREADPOOLSTATISTICS
//
// Convert the start time into units of buckets by dividing by
// dwTimerBucketGranularity.
//
dwBucket = pWorkQueue->dwLastTimerProcessTime >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue);
//
// The actual index will be modulo dwNumTimerBuckets.
//
dwBucket = dwBucket & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue);
#ifdef DBG
dwBucketTime = pWorkQueue->dwLastTimerProcessTime; #endif // DBG
//
// Walk through the list of expired buckets. Since the bucket array
// started at time 0, the current bucket is the current time modulo
// the number of buckets.
//
while (dwExpiredBuckets > 0) { dwExpiredBuckets--;
#ifdef DBG
//DPFX(DPFPREP, 9, "Servicing bucket #%u, time = %u, %u buckets remaining.", dwBucket, dwBucketTime, dwExpiredBuckets);
DNASSERT((int) (dwCurrentTime - dwBucketTime) >= 0); #endif // DBG
//
// Dump the entire list of timer entries (if any) into a local
// variable and walk through it at our leisure.
//
pSlistEntry = DNInterlockedFlushSList(&pWorkQueue->paSlistTimerBuckets[dwBucket]); while (pSlistEntry != NULL) { pWorkItem = CONTAINING_OBJECT(pSlistEntry, CWorkItem, m_SlistEntry); pSlistEntry = pSlistEntry->Next;
//
// Queue it for processing or reschedule the timer depending on
// whether it's actually due now, and whether it's cancelled.
//
if (((int) (dwCurrentTime - pWorkItem->m_dwDueTime)) > 0) { //
// The timer has expired. It may have been cancelled; if
// not, we need to queue if for completion. Either way,
// it's not cancellable any more.
//
fCancelled = (BOOL) DNInterlockedExchange((LPLONG) (&pWorkItem->m_fCancelledOrCompleting), TRUE);
//
// If the timer was cancelled, just put the entry back into
// the pool. Otherwise, queue the work item.
//
if (fCancelled) { DPFX(DPFPREP, 5, "Returning timer work item 0x%p (unique ID %u, due time = %u, bucket %u) back to pool.", pWorkItem, pWorkItem->m_uiUniqueID, pWorkItem->m_dwDueTime, dwBucket);
pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { DPFX(DPFPREP, 8, "Queueing timer work item 0x%p (unique ID %u, due time = %u, bucket %u) for completion on queue 0x%p.", pWorkItem, pWorkItem->m_uiUniqueID, pWorkItem->m_dwDueTime, dwBucket, pWorkQueue);
#ifdef DBG
//
// Make sure we didn't miss any timers last time around,
// unless we were really, really delayed.
//
{ DWORD dwTimePastDueTime; DWORD dwElapsedTimeWithRoundError;
dwTimePastDueTime = dwCurrentTime - pWorkItem->m_dwDueTime; dwElapsedTimeWithRoundError = dwElapsedTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue); if (dwTimePastDueTime > dwElapsedTimeWithRoundError) { DPFX(DPFPREP, 1, "Missed timer work item 0x%p, its due time of %u is off by about %u ms.", pWorkItem, pWorkItem->m_dwDueTime, dwTimePastDueTime);
#if ((defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) || (defined(DPNBUILD_NOMISSEDTIMERSHINT)))
DNASSERTX(fNotServicedForLongTime, 2); #else // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
DNASSERT(fNotServicedForLongTime); #endif // ! ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and DPNBUILD_NOMISSEDTIMERSHINT
} } #endif // DBG
ThreadpoolStatsQueue(pWorkItem);
#ifdef DPNBUILD_USEIOCOMPLETIONPORTS
//
// Queue it to the I/O completion port.
//
BOOL fResult;
fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort), 0, 0, &pWorkItem->m_Overlapped); DNASSERT(fResult); #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
//
// Add it to the caller's list.
//
if ((*ppHead) == NULL) { *ppTail = &pWorkItem->m_SlistEntry; } pWorkItem->m_SlistEntry.Next = *ppHead; *ppHead = &pWorkItem->m_SlistEntry;
(*pusCount)++; #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
} } else { //
// It's a "long" timer, and hasn't expired yet. Sample the
// boolean to see if it has been cancelled. If so, just
// put the entry back into the pool. Otherwise, put it
// back into the bucket.
//
fCancelled = pWorkItem->m_fCancelledOrCompleting; if (fCancelled) { DPFX(DPFPREP, 5, "Returning timer work item 0x%p (unique ID %u, due time = %u, bucket %u) back to pool.", pWorkItem, pWorkItem->m_uiUniqueID, pWorkItem->m_dwDueTime, dwBucket);
pWorkQueue->pWorkItemPool->Release(pWorkItem); } else { DPFX(DPFPREP, 8, "Putting long timer work item 0x%p (unique ID %u, due time = %u) back in bucket %u.", pWorkItem, pWorkItem->m_uiUniqueID, pWorkItem->m_dwDueTime, dwBucket);
#ifdef DBG
//
// Make sure it really is in the future.
//
DWORD dwRoundedDueTime = (pWorkItem->m_dwDueTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue)) & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue); DWORD dwTotalArrayTime = TIMER_BUCKET_GRANULARITY(pWorkQueue) * NUM_TIMER_BUCKETS(pWorkQueue); DNASSERT((dwRoundedDueTime - dwBucketTime) >= dwTotalArrayTime); #endif // DBG
#pragma TODO(vanceo, "Investigate if saving up all long timers and pushing the whole list on at once will be beneficial")
DNInterlockedPushEntrySList(&pWorkQueue->paSlistTimerBuckets[dwBucket], &pWorkItem->m_SlistEntry);
#ifdef DPNBUILD_THREADPOOLSTATISTICS
DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumLongTimersRescheduled)); #endif // DPNBUILD_THREADPOOLSTATISTICS
} } } // end while (still more timer entries in bucket)
dwBucket = (dwBucket + 1) & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue); #ifdef DBG
dwBucketTime += TIMER_BUCKET_GRANULARITY(pWorkQueue); #endif // DBG
} // end while (still more expired buckets)
//
// Remember when we started processing for use the next time through.
//
pWorkQueue->dwLastTimerProcessTime = dwCurrentTime; } } // ProcessTimers
|