Leaked source code of windows server 2003
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.
 
 
 
 
 
 

971 lines
34 KiB

/******************************************************************************
*
* 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