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