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

  1. /******************************************************************************
  2. *
  3. * Copyright (C) 1999-2002 Microsoft Corporation. All Rights Reserved.
  4. *
  5. * File: timers.cpp
  6. *
  7. * Content: DirectPlay Thread Pool timer functions.
  8. *
  9. * History:
  10. * Date By Reason
  11. * ======== ======== =========
  12. * 10/31/01 VanceO Based on DPlay Protocol Quick Start Timers.
  13. *
  14. ******************************************************************************/
  15. #include "dpnthreadpooli.h"
  16. #undef DPF_MODNAME
  17. #define DPF_MODNAME "InitializeWorkQueueTimerInfo"
  18. //=============================================================================
  19. // InitializeWorkQueueTimerInfo
  20. //-----------------------------------------------------------------------------
  21. //
  22. // Description: Initializes the timer info for the given work queue.
  23. //
  24. // Arguments:
  25. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to initialize.
  26. //
  27. // Returns: HRESULT
  28. // DPN_OK - Successfully initialized the work queue object's
  29. // timer information.
  30. // DPNERR_OUTOFMEMORY - Failed to allocate memory while initializing.
  31. //=============================================================================
  32. HRESULT InitializeWorkQueueTimerInfo(DPTPWORKQUEUE * const pWorkQueue)
  33. {
  34. HRESULT hr;
  35. DWORD dwTemp;
  36. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  37. LARGE_INTEGER liDueTime;
  38. HANDLE hTimer;
  39. #ifdef DBG
  40. DWORD dwError;
  41. #endif // DBG
  42. #endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
  43. #ifdef DPNBUILD_DYNAMICTIMERSETTINGS
  44. pWorkQueue->dwTimerBucketGranularity = DEFAULT_TIMER_BUCKET_GRANULARITY;
  45. //
  46. // The granularity must be a power of 2 in order for our ceiling, mask and
  47. // divisor optimizations to work.
  48. //
  49. DNASSERT(! ((pWorkQueue->dwTimerBucketGranularity - 1) & pWorkQueue->dwTimerBucketGranularity));
  50. pWorkQueue->dwTimerBucketGranularityCeiling = pWorkQueue->dwTimerBucketGranularity - 1;
  51. pWorkQueue->dwTimerBucketGranularityFloorMask = ~(pWorkQueue->dwTimerBucketGranularityCeiling); // negating the ceiling round factor (which happens to also be the modulo mask) gives us the floor mask
  52. pWorkQueue->dwTimerBucketGranularityDivisor = pWorkQueue->dwTimerBucketGranularity >> 1;
  53. pWorkQueue->dwNumTimerBuckets = DEFAULT_NUM_TIMER_BUCKETS;
  54. //
  55. // The bucket count must be a power of 2 in order for our mask optimizations
  56. // to work.
  57. //
  58. DNASSERT(! ((pWorkQueue->dwNumTimerBuckets - 1) & pWorkQueue->dwNumTimerBuckets));
  59. pWorkQueue->dwNumTimerBucketsModMask = pWorkQueue->dwNumTimerBuckets - 1;
  60. #endif // DPNBUILD_DYNAMICTIMERSETTINGS
  61. //
  62. // Initialize the last process time, rounding down to the last whole bucket.
  63. //
  64. pWorkQueue->dwLastTimerProcessTime = GETTIMESTAMP() & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue);
  65. DPFX(DPFPREP, 7, "Current bucket index = %u at time %u, array time length = %u.",
  66. ((pWorkQueue->dwLastTimerProcessTime / TIMER_BUCKET_GRANULARITY(pWorkQueue)) % NUM_TIMER_BUCKETS(pWorkQueue)),
  67. pWorkQueue->dwLastTimerProcessTime,
  68. (TIMER_BUCKET_GRANULARITY(pWorkQueue) * NUM_TIMER_BUCKETS(pWorkQueue)));
  69. //
  70. // Allocate the timer bucket array.
  71. //
  72. pWorkQueue->paSlistTimerBuckets = (DNSLIST_HEADER*) DNMalloc(NUM_TIMER_BUCKETS(pWorkQueue) * sizeof(DNSLIST_HEADER));
  73. if (pWorkQueue->paSlistTimerBuckets == NULL)
  74. {
  75. DPFX(DPFPREP, 0, "Couldn't allocate memory for %u timer buckets!",
  76. NUM_TIMER_BUCKETS(pWorkQueue));
  77. hr = DPNERR_OUTOFMEMORY;
  78. goto Failure;
  79. }
  80. //
  81. // Initialize all of the timer buckets.
  82. //
  83. for(dwTemp = 0; dwTemp < NUM_TIMER_BUCKETS(pWorkQueue); dwTemp++)
  84. {
  85. DNInitializeSListHead(&pWorkQueue->paSlistTimerBuckets[dwTemp]);
  86. }
  87. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  88. pWorkQueue->dwPossibleMissedTimerWindow = 0;
  89. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  90. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  91. //
  92. // Create a waitable timer for the timer thread.
  93. //
  94. hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
  95. if (hTimer == NULL)
  96. {
  97. #ifdef DBG
  98. dwError = GetLastError();
  99. DPFX(DPFPREP, 0, "Couldn't create waitable timer (err = %u)!", dwError);
  100. #endif // DBG
  101. hr = DPNERR_OUTOFMEMORY;
  102. goto Failure;
  103. }
  104. pWorkQueue->hTimer = MAKE_DNHANDLE(hTimer);
  105. //
  106. // Kick off the waitable timer so that it wakes up every 'granularity'
  107. // milliseconds. Tell it not to start until 1 resolution period elapses
  108. // because there probably aren't any timers yet, and more importantly,
  109. // there probably aren't any threads that are waiting yet. Delaying the
  110. // start hopefully will reduce unnecessary CPU usage.
  111. //
  112. // The due-time value is negative because that indicates relative time to
  113. // SetWaitableTimer, and multiplied by 10000 because it is in 100
  114. // nanosecond increments.
  115. //
  116. liDueTime.QuadPart = -1 * TIMER_BUCKET_GRANULARITY(pWorkQueue) * 10000;
  117. if (! SetWaitableTimer(HANDLE_FROM_DNHANDLE(pWorkQueue->hTimer),
  118. &liDueTime,
  119. TIMER_BUCKET_GRANULARITY(pWorkQueue),
  120. NULL,
  121. NULL,
  122. FALSE))
  123. {
  124. #ifdef DBG
  125. dwError = GetLastError();
  126. DPFX(DPFPREP, 0, "Couldn't create waitable timer (err = %u)!", dwError);
  127. #endif // DBG
  128. hr = DPNERR_OUTOFMEMORY;
  129. goto Failure;
  130. }
  131. #pragma TODO(vanceo, "We should avoid setting this timer until there are threads, and stop it when there aren't any")
  132. #endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
  133. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  134. //
  135. // Initialize the timer package debugging/tuning statistics.
  136. //
  137. pWorkQueue->dwTotalNumTimerChecks = 0;
  138. pWorkQueue->dwTotalNumBucketsProcessed = 0;
  139. pWorkQueue->dwTotalNumTimersScheduled = 0;
  140. pWorkQueue->dwTotalNumLongTimersRescheduled = 0;
  141. pWorkQueue->dwTotalNumSuccessfulCancels = 0;
  142. pWorkQueue->dwTotalNumFailedCancels = 0;
  143. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  144. pWorkQueue->dwTotalPossibleMissedTimerWindows = 0;
  145. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  146. #endif // DPNBUILD_THREADPOOLSTATISTICS
  147. hr = DPN_OK;
  148. Exit:
  149. return hr;
  150. Failure:
  151. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  152. if (pWorkQueue->hTimer != NULL)
  153. {
  154. DNCloseHandle(pWorkQueue->hTimer);
  155. pWorkQueue->hTimer = NULL;
  156. }
  157. #endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
  158. if (pWorkQueue->paSlistTimerBuckets != NULL)
  159. {
  160. DNFree(pWorkQueue->paSlistTimerBuckets);
  161. pWorkQueue->paSlistTimerBuckets = NULL;
  162. }
  163. goto Exit;
  164. } // InitializeWorkQueueTimerInfo
  165. #undef DPF_MODNAME
  166. #define DPF_MODNAME "DeinitializeWorkQueueTimerInfo"
  167. //=============================================================================
  168. // DeinitializeWorkQueueTimerInfo
  169. //-----------------------------------------------------------------------------
  170. //
  171. // Description: Cleans up work queue timer info.
  172. //
  173. // Arguments:
  174. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to initialize.
  175. //
  176. // Returns: Nothing.
  177. //=============================================================================
  178. void DeinitializeWorkQueueTimerInfo(DPTPWORKQUEUE * const pWorkQueue)
  179. {
  180. DWORD dwTemp;
  181. DNSLIST_ENTRY * pSlistEntry;
  182. CWorkItem * pWorkItem;
  183. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  184. BOOL fResult;
  185. fResult = DNCloseHandle(pWorkQueue->hTimer);
  186. DNASSERT(fResult);
  187. pWorkQueue->hTimer = NULL;
  188. #endif // WINNT or (WIN95 AND ! DPNBUILD_NOWAITABLETIMERSON9X)
  189. //
  190. // Empty out the timer buckets. The only thing left should be cancelled
  191. // timers that the threads/DoWork didn't happen to clean up.
  192. //
  193. DNASSERT(pWorkQueue->paSlistTimerBuckets != NULL);
  194. for(dwTemp = 0; dwTemp < NUM_TIMER_BUCKETS(pWorkQueue); dwTemp++)
  195. {
  196. //
  197. // This doesn't really need to be interlocked since no one should be
  198. // using it anymore, but oh well...
  199. //
  200. pSlistEntry = DNInterlockedFlushSList(&pWorkQueue->paSlistTimerBuckets[dwTemp]);
  201. while (pSlistEntry != NULL)
  202. {
  203. pWorkItem = CONTAINING_OBJECT(pSlistEntry, CWorkItem, m_SlistEntry);
  204. pSlistEntry = pSlistEntry->Next;
  205. //
  206. // Make sure the item has been cancelled as noted above.
  207. //
  208. DNASSERT(pWorkItem->m_fCancelledOrCompleting);
  209. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  210. pWorkItem = NULL;
  211. }
  212. }
  213. DNFree(pWorkQueue->paSlistTimerBuckets);
  214. pWorkQueue->paSlistTimerBuckets = NULL;
  215. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  216. //
  217. // Print our debugging/tuning statistics.
  218. //
  219. #ifdef DPNBUILD_ONLYONEPROCESSOR
  220. DPFX(DPFPREP, 7, "Work queue 0x%p timer stats:", pWorkQueue);
  221. #else // ! DPNBUILD_ONLYONEPROCESSOR
  222. DPFX(DPFPREP, 7, "Work queue 0x%p (CPU %u) timer stats:", pWorkQueue, pWorkQueue->dwCPUNum);
  223. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  224. DPFX(DPFPREP, 7, " TotalNumTimerChecks = %u", pWorkQueue->dwTotalNumTimerChecks);
  225. DPFX(DPFPREP, 7, " TotalNumBucketsProcessed = %u", pWorkQueue->dwTotalNumBucketsProcessed);
  226. DPFX(DPFPREP, 7, " TotalNumTimersScheduled = %u", pWorkQueue->dwTotalNumTimersScheduled);
  227. DPFX(DPFPREP, 7, " TotalNumLongTimersRescheduled = %u", pWorkQueue->dwTotalNumLongTimersRescheduled);
  228. DPFX(DPFPREP, 7, " TotalNumSuccessfulCancels = %u", pWorkQueue->dwTotalNumSuccessfulCancels);
  229. DPFX(DPFPREP, 7, " TotalNumFailedCancels = %u", pWorkQueue->dwTotalNumFailedCancels);
  230. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  231. DPFX(DPFPREP, 7, " TotalPossibleMissedTimerWindows = %u", pWorkQueue->dwTotalPossibleMissedTimerWindows);
  232. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  233. #endif // DPNBUILD_THREADPOOLSTATISTICS
  234. } // DeinitializeWorkQueueTimerInfo
  235. #undef DPF_MODNAME
  236. #define DPF_MODNAME "ScheduleTimer"
  237. //=============================================================================
  238. // ScheduleTimer
  239. //-----------------------------------------------------------------------------
  240. //
  241. // Description: Schedules a new work item for some point in the future.
  242. //
  243. // Arguments:
  244. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
  245. // DWORD dwDelay - How much time should elapsed before
  246. // executing the work item, in ms.
  247. // PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute when timer
  248. // elapses.
  249. // PVOID pvCallbackContext - User specified context to pass to
  250. // callback.
  251. // void ** ppvTimerData - Place to store pointer to data for
  252. // timer so that it can be cancelled.
  253. // UINT * puiTimerUnique - Place to store uniqueness value for
  254. // timer so that it can be cancelled.
  255. //
  256. // Returns: BOOL
  257. // TRUE - Successfully scheduled the item.
  258. // FALSE - Failed to allocate memory for scheduling the item.
  259. //=============================================================================
  260. BOOL ScheduleTimer(DPTPWORKQUEUE * const pWorkQueue,
  261. const DWORD dwDelay,
  262. const PFNDPTNWORKCALLBACK pfnWorkCallback,
  263. PVOID const pvCallbackContext,
  264. void ** const ppvTimerData,
  265. UINT * const puiTimerUnique)
  266. {
  267. CWorkItem * pWorkItem;
  268. DWORD dwCurrentTime;
  269. DWORD dwBucket;
  270. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  271. DWORD dwPossibleMissWindow;
  272. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  273. //
  274. // Delays of over 24 days seem a bit excessive.
  275. //
  276. DNASSERT(dwDelay < 0x80000000);
  277. //
  278. // Get an entry from the pool.
  279. //
  280. pWorkItem = (CWorkItem*) pWorkQueue->pWorkItemPool->Get(pWorkQueue);
  281. if (pWorkItem == NULL)
  282. {
  283. return FALSE;
  284. }
  285. //
  286. // Fill in the return values used for cancellation.
  287. //
  288. (*ppvTimerData) = pWorkItem;
  289. (*puiTimerUnique) = pWorkItem->m_uiUniqueID;
  290. //
  291. // Initialize the work item.
  292. //
  293. pWorkItem->m_pfnWorkCallback = pfnWorkCallback;
  294. pWorkItem->m_pvCallbackContext = pvCallbackContext;
  295. ThreadpoolStatsCreate(pWorkItem);
  296. dwCurrentTime = GETTIMESTAMP();
  297. pWorkItem->m_dwDueTime = dwCurrentTime + dwDelay;
  298. //
  299. // Calculate how far in the future this is. Round up to the next bucket
  300. // time.
  301. //
  302. dwBucket = pWorkItem->m_dwDueTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue);
  303. //
  304. // Convert into units of buckets by dividing by dwTimerBucketGranularity.
  305. //
  306. dwBucket = dwBucket >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue);
  307. //
  308. // The actual index will be modulo dwNumTimerBuckets.
  309. //
  310. dwBucket = dwBucket & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue);
  311. //
  312. // Note that the timer thread theoretically could be processing the bucket
  313. // into which we are inserting, but since both threads are working from the
  314. // same time base, as long as we are at least one bucket in the future, we
  315. // should not get missed. We rounded up and the processing function rounds
  316. // down in an attempt to insure that.
  317. //
  318. 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.",
  319. pWorkItem, pvCallbackContext, pWorkItem->m_dwDueTime, pfnWorkCallback,
  320. pWorkItem->m_uiUniqueID, pWorkQueue, dwDelay, dwBucket);
  321. //
  322. // Push this timer onto the appropriate timer bucket list.
  323. //
  324. DNInterlockedPushEntrySList(&pWorkQueue->paSlistTimerBuckets[dwBucket],
  325. &pWorkItem->m_SlistEntry);
  326. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  327. //
  328. // Although this function is very short, it's still possible that it took
  329. // too long, especially on timers with miniscule delays. Give a hint to
  330. // the timer thread that it needs to look for timers that got missed.
  331. //
  332. // Note that really long delays could confuse this.
  333. //
  334. dwPossibleMissWindow = GETTIMESTAMP() - pWorkItem->m_dwDueTime;
  335. if ((int) dwPossibleMissWindow >= 0)
  336. {
  337. DWORD dwResult;
  338. dwPossibleMissWindow++; // make it so a value of 0 still adds something to dwPossibleMissedTimerWindow
  339. dwResult = DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwPossibleMissedTimerWindow), dwPossibleMissWindow);
  340. DPFX(DPFPREP, 4, "Possibly missed timer work item 0x%p (delay %u ms), increased missed timer window (%u ms) by %u ms.",
  341. pWorkItem, dwDelay, dwResult, dwPossibleMissWindow);
  342. }
  343. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  344. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  345. //
  346. // Update the timer package debugging/tuning statistics.
  347. //
  348. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumTimersScheduled));
  349. #endif // DPNBUILD_THREADPOOLSTATISTICS
  350. return TRUE;
  351. } // ScheduleTimer
  352. #undef DPF_MODNAME
  353. #define DPF_MODNAME "CancelTimer"
  354. //=============================================================================
  355. // CancelTimer
  356. //-----------------------------------------------------------------------------
  357. //
  358. // Description: Attempts to cancel a timed work item. If the item is
  359. // already in the process of completing, DPNERR_CANNOTCANCEL is
  360. // returned, and the callback will still be (or is being) called.
  361. // If the item could be cancelled, DPN_OK is returned and the
  362. // callback will not be executed.
  363. //
  364. // Arguments:
  365. // void * pvTimerData - Pointer to data for timer being cancelled.
  366. // UINT uiTimerUnique - Uniqueness value for timer being cancelled.
  367. //
  368. // Returns: HRESULT
  369. // DPN_OK - Successfully cancelled.
  370. // DPNERR_CANNOTCANCEL - Could not cancel the item.
  371. //=============================================================================
  372. HRESULT CancelTimer(void * const pvTimerData,
  373. const UINT uiTimerUnique)
  374. {
  375. HRESULT hr;
  376. CWorkItem * pWorkItem;
  377. //
  378. // This cancellation lookup mechanism assumes that the memory for already
  379. // completed entries is still allocated. If the pooling mechanism changes,
  380. // this will have to be revised. Obviously, that also means we assume the
  381. // memory was valid in the first place. Passing in a garbage pvTimerData
  382. // value will cause a crash. Also see the various calls to
  383. // pWorkItem->m_pfnWorkCallback.
  384. //
  385. DNASSERT(pvTimerData != NULL);
  386. pWorkItem = (CWorkItem*) pvTimerData;
  387. if (pWorkItem->m_uiUniqueID == uiTimerUnique)
  388. {
  389. //
  390. // Attempt to mark the item as cancelled. If it was already in the
  391. // process of completing, (or I suppose if you had already cancelled
  392. // this same timer, but don't do that :), this would have already been
  393. // set to TRUE.
  394. //
  395. if (! DNInterlockedExchange((LPLONG) (&pWorkItem->m_fCancelledOrCompleting), TRUE))
  396. {
  397. DPFX(DPFPREP, 5, "Marked timer work item 0x%p (unique ID %u) as cancelled.",
  398. pWorkItem, uiTimerUnique);
  399. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  400. //
  401. // Update the timer package debugging/tuning statistics.
  402. //
  403. DNInterlockedIncrement((LPLONG) (&pWorkItem->m_pWorkQueue->dwTotalNumSuccessfulCancels));
  404. #endif // DPNBUILD_THREADPOOLSTATISTICS
  405. hr = DPN_OK;
  406. }
  407. else
  408. {
  409. DPFX(DPFPREP, 5, "Timer work item 0x%p (unique ID %u) already completing.",
  410. pWorkItem, uiTimerUnique);
  411. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  412. //
  413. // Update the timer package debugging/tuning statistics.
  414. //
  415. DNInterlockedIncrement((LPLONG) (&pWorkItem->m_pWorkQueue->dwTotalNumFailedCancels));
  416. #endif // DPNBUILD_THREADPOOLSTATISTICS
  417. hr = DPNERR_CANNOTCANCEL;
  418. }
  419. }
  420. else
  421. {
  422. DPFX(DPFPREP, 5, "Timer work item 0x%p unique ID does not match (%u != %u).",
  423. pWorkItem, pWorkItem->m_uiUniqueID, uiTimerUnique);
  424. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  425. //
  426. // Update the timer package debugging/tuning statistics.
  427. //
  428. DNInterlockedIncrement((LPLONG) (&pWorkItem->m_pWorkQueue->dwTotalNumFailedCancels));
  429. #endif // DPNBUILD_THREADPOOLSTATISTICS
  430. hr = DPNERR_CANNOTCANCEL;
  431. }
  432. return hr;
  433. } // CancelTimer
  434. #undef DPF_MODNAME
  435. #define DPF_MODNAME "ResetCompletingTimer"
  436. //=============================================================================
  437. // ResetCompletingTimer
  438. //-----------------------------------------------------------------------------
  439. //
  440. // Description: Reschedules a timed work item whose callback is currently
  441. // being called. Resetting timers that have not expired yet,
  442. // timers that have been cancelled, or timers whose callback has
  443. // already returned is not allowed.
  444. //
  445. // Arguments:
  446. // void * pvTimerData - Pointer to data for timer being
  447. // reset.
  448. // DWORD dwNewDelay - How much time should elapsed
  449. // before executing the work item
  450. // again, in ms.
  451. // PFNDPTNWORKCALLBACK pfnNewWorkCallback - Callback to execute when timer
  452. // elapses.
  453. // PVOID pvNewCallbackContext - User specified context to pass to
  454. // callback.
  455. // UINT * puiNewTimerUnique - Place to store new uniqueness
  456. // value for timer so that it can
  457. // be cancelled.
  458. //
  459. // Returns: None.
  460. //=============================================================================
  461. void ResetCompletingTimer(void * const pvTimerData,
  462. const DWORD dwNewDelay,
  463. const PFNDPTNWORKCALLBACK pfnNewWorkCallback,
  464. PVOID const pvNewCallbackContext,
  465. UINT *const puiNewTimerUnique)
  466. {
  467. CWorkItem * pWorkItem;
  468. DPTPWORKQUEUE * pWorkQueue;
  469. DWORD dwCurrentTime;
  470. DWORD dwBucket;
  471. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  472. DWORD dwPossibleMissWindow;
  473. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  474. //
  475. // The timer must be valid, similar to CancelTimer.
  476. //
  477. DNASSERT(pvTimerData != NULL);
  478. pWorkItem = (CWorkItem*) pvTimerData;
  479. DNASSERT(pWorkItem->m_fCancelledOrCompleting);
  480. //
  481. // Delays of over 24 days seem a bit excessive.
  482. //
  483. DNASSERT(dwNewDelay < 0x80000000);
  484. pWorkQueue = pWorkItem->m_pWorkQueue;
  485. //
  486. // Reinitialize the work item.
  487. //
  488. pWorkItem->m_pfnWorkCallback = pfnNewWorkCallback;
  489. pWorkItem->m_pvCallbackContext = pvNewCallbackContext;
  490. ThreadpoolStatsCreate(pWorkItem);
  491. dwCurrentTime = GETTIMESTAMP();
  492. pWorkItem->m_dwDueTime = dwCurrentTime + dwNewDelay;
  493. pWorkItem->m_fCancelledOrCompleting = FALSE;
  494. pWorkItem->m_uiUniqueID++;
  495. //
  496. // Fill in the return value for cancellation.
  497. //
  498. (*puiNewTimerUnique) = pWorkItem->m_uiUniqueID;
  499. //
  500. // Calculate how far in the future this is. Round up to the next bucket
  501. // time.
  502. //
  503. dwBucket = pWorkItem->m_dwDueTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue);
  504. //
  505. // Convert into units of buckets by dividing by dwTimerBucketGranularity.
  506. //
  507. dwBucket = dwBucket >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue);
  508. //
  509. // The actual index will be modulo dwNumTimerBuckets.
  510. //
  511. dwBucket = dwBucket & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue);
  512. //
  513. // Note that the timer thread theoretically could be processing the bucket
  514. // into which we are inserting, but since both threads are working from the
  515. // same time base, as long as we are at least one bucket in the future, we
  516. // should not get missed. We rounded up and the processing function rounds
  517. // down in an attempt to insure that.
  518. //
  519. 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.",
  520. pWorkItem, pvNewCallbackContext, pWorkItem->m_dwDueTime, pfnNewWorkCallback,
  521. pWorkItem->m_uiUniqueID, pWorkQueue, dwNewDelay, dwBucket);
  522. //
  523. // Push this timer onto the appropriate timer bucket list.
  524. //
  525. DNInterlockedPushEntrySList(&pWorkQueue->paSlistTimerBuckets[dwBucket],
  526. &pWorkItem->m_SlistEntry);
  527. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  528. //
  529. // Although this function is very short, it's still possible that it took
  530. // too long, especially on timers with miniscule delays. Give a hint to
  531. // the timer thread that it needs to look for timers that got missed.
  532. //
  533. // Note that really long delays could confuse this.
  534. //
  535. dwPossibleMissWindow = GETTIMESTAMP() - pWorkItem->m_dwDueTime;
  536. if ((int) dwPossibleMissWindow >= 0)
  537. {
  538. DWORD dwResult;
  539. dwPossibleMissWindow++; // make it so a value of 0 still adds something to dwPossibleMissedTimerWindow
  540. dwResult = DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwPossibleMissedTimerWindow), dwPossibleMissWindow);
  541. DPFX(DPFPREP, 4, "Possibly missed timer work item 0x%p (delay %u ms), increased missed timer window (%u ms) by %u ms.",
  542. pWorkItem, dwNewDelay, dwResult, dwPossibleMissWindow);
  543. }
  544. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  545. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  546. //
  547. // Update the timer package debugging/tuning statistics.
  548. //
  549. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumTimersScheduled));
  550. #endif // DPNBUILD_THREADPOOLSTATISTICS
  551. } // ResetCompletingTimer
  552. #undef DPF_MODNAME
  553. #define DPF_MODNAME "ProcessTimers"
  554. //=============================================================================
  555. // ProcessTimers
  556. //-----------------------------------------------------------------------------
  557. //
  558. // Description: Queues any expired timers as work items and performs lazy
  559. // pool releases of cancelled timers.
  560. //
  561. // When this implementation does not use I/O completion ports,
  562. // the new work items are added to the passed in list without
  563. // using Interlocked functions.
  564. //
  565. // It is assumed that only one thread will call this function
  566. // at a time.
  567. //
  568. // Arguments:
  569. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
  570. // DNSLIST_ENTRY ** ppHead - Pointer to initial list head pointer, and
  571. // place to store new head pointer.
  572. // DNSLIST_ENTRY ** ppTail - Pointer to existing list tail pointer, or
  573. // place to store new tail pointer.
  574. // USHORT * pusCount - Pointer to existing list count, it will be
  575. // updated to reflect new items.
  576. //
  577. // Returns: Nothing.
  578. //=============================================================================
  579. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  580. void ProcessTimers(DPTPWORKQUEUE * const pWorkQueue)
  581. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  582. void ProcessTimers(DPTPWORKQUEUE * const pWorkQueue,
  583. DNSLIST_ENTRY ** const ppHead,
  584. DNSLIST_ENTRY ** const ppTail,
  585. USHORT * const pusCount)
  586. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  587. {
  588. DWORD dwCurrentTime;
  589. DWORD dwElapsedTime;
  590. DWORD dwExpiredBuckets;
  591. DWORD dwBucket;
  592. #if ((! defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) && (! defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  593. DWORD dwPossibleMissedTimerWindow;
  594. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  595. DNSLIST_ENTRY * pSlistEntry;
  596. CWorkItem * pWorkItem;
  597. BOOL fCancelled;
  598. #ifdef DBG
  599. BOOL fNotServicedForLongTime = FALSE;
  600. DWORD dwBucketTime;
  601. #endif // DBG
  602. //
  603. // Retrieve the current time, rounding down to the last fully completed
  604. // bucket time slice.
  605. //
  606. dwCurrentTime = GETTIMESTAMP() & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue);
  607. #ifndef DPNBUILD_DONTCHECKFORMISSEDTIMERS
  608. #ifdef DPNBUILD_NOMISSEDTIMERSHINT
  609. //
  610. // Always re-check the previous bucket.
  611. //
  612. pWorkQueue->dwLastTimerProcessTime -= TIMER_BUCKET_GRANULARITY(pWorkQueue);
  613. #else // ! DPNBUILD_NOMISSEDTIMERSHINT
  614. //
  615. // See if any threads hinted that there might be missed timers. If so, we
  616. // will artifically open the window a bit more, hopefully to include them.
  617. //
  618. dwPossibleMissedTimerWindow = DNInterlockedExchange((LPLONG) (&pWorkQueue->dwPossibleMissedTimerWindow), 0);
  619. dwPossibleMissedTimerWindow = (dwPossibleMissedTimerWindow + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue))
  620. & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue);
  621. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  622. pWorkQueue->dwTotalPossibleMissedTimerWindows += dwPossibleMissedTimerWindow;
  623. #endif // DPNBUILD_THREADPOOLSTATISTICS
  624. pWorkQueue->dwLastTimerProcessTime -= dwPossibleMissedTimerWindow;
  625. #endif // ! DPNBUILD_NOMISSEDTIMERSHINT
  626. #endif // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS
  627. //
  628. // See if enough time has elapsed to cause any buckets to expire. If not,
  629. // there's nothing to do.
  630. //
  631. dwElapsedTime = dwCurrentTime - pWorkQueue->dwLastTimerProcessTime;
  632. if (dwElapsedTime > 0)
  633. {
  634. //DPFX(DPFPREP, 9, "Processing timers for worker queue 0x%p, rounded time = %u, elapsed time = %u", pWorkQueue, dwCurrentTime, dwElapsedTime);
  635. //
  636. // The time difference should be an even multiple of the timer bucket
  637. // granularity (negating the floor mask gives us the modulo mask).
  638. //
  639. DNASSERT((dwElapsedTime & ~(TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue))) == 0);
  640. //
  641. // We should not have failed to run for over 24 days, so if this assert
  642. // fires, time probably went backward or some such nonsense.
  643. //
  644. DNASSERT(dwElapsedTime < 0x80000000);
  645. //
  646. // Figure out how many buckets we need to process by dividing by
  647. // dwTimerBucketGranularity.
  648. //
  649. dwExpiredBuckets = dwElapsedTime >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue);
  650. if (dwExpiredBuckets > NUM_TIMER_BUCKETS(pWorkQueue))
  651. {
  652. //
  653. // A really long time has elapsed since the last time we serviced
  654. // the timers (equal to or longer than the range of the entire
  655. // array). We must complete everything that is on the array. To
  656. // prevent us from walking the same bucket more than once, cap the
  657. // number we're going to check.
  658. //
  659. dwExpiredBuckets = NUM_TIMER_BUCKETS(pWorkQueue);
  660. #ifdef DBG
  661. fNotServicedForLongTime = FALSE;
  662. #endif // DBG
  663. }
  664. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  665. //
  666. // Update the timer package debugging/tuning statistics.
  667. //
  668. pWorkQueue->dwTotalNumTimerChecks++;
  669. pWorkQueue->dwTotalNumBucketsProcessed += dwExpiredBuckets;
  670. #endif // DPNBUILD_THREADPOOLSTATISTICS
  671. //
  672. // Convert the start time into units of buckets by dividing by
  673. // dwTimerBucketGranularity.
  674. //
  675. dwBucket = pWorkQueue->dwLastTimerProcessTime >> TIMER_BUCKET_GRANULARITY_DIVISOR(pWorkQueue);
  676. //
  677. // The actual index will be modulo dwNumTimerBuckets.
  678. //
  679. dwBucket = dwBucket & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue);
  680. #ifdef DBG
  681. dwBucketTime = pWorkQueue->dwLastTimerProcessTime;
  682. #endif // DBG
  683. //
  684. // Walk through the list of expired buckets. Since the bucket array
  685. // started at time 0, the current bucket is the current time modulo
  686. // the number of buckets.
  687. //
  688. while (dwExpiredBuckets > 0)
  689. {
  690. dwExpiredBuckets--;
  691. #ifdef DBG
  692. //DPFX(DPFPREP, 9, "Servicing bucket #%u, time = %u, %u buckets remaining.", dwBucket, dwBucketTime, dwExpiredBuckets);
  693. DNASSERT((int) (dwCurrentTime - dwBucketTime) >= 0);
  694. #endif // DBG
  695. //
  696. // Dump the entire list of timer entries (if any) into a local
  697. // variable and walk through it at our leisure.
  698. //
  699. pSlistEntry = DNInterlockedFlushSList(&pWorkQueue->paSlistTimerBuckets[dwBucket]);
  700. while (pSlistEntry != NULL)
  701. {
  702. pWorkItem = CONTAINING_OBJECT(pSlistEntry, CWorkItem, m_SlistEntry);
  703. pSlistEntry = pSlistEntry->Next;
  704. //
  705. // Queue it for processing or reschedule the timer depending on
  706. // whether it's actually due now, and whether it's cancelled.
  707. //
  708. if (((int) (dwCurrentTime - pWorkItem->m_dwDueTime)) > 0)
  709. {
  710. //
  711. // The timer has expired. It may have been cancelled; if
  712. // not, we need to queue if for completion. Either way,
  713. // it's not cancellable any more.
  714. //
  715. fCancelled = (BOOL) DNInterlockedExchange((LPLONG) (&pWorkItem->m_fCancelledOrCompleting),
  716. TRUE);
  717. //
  718. // If the timer was cancelled, just put the entry back into
  719. // the pool. Otherwise, queue the work item.
  720. //
  721. if (fCancelled)
  722. {
  723. DPFX(DPFPREP, 5, "Returning timer work item 0x%p (unique ID %u, due time = %u, bucket %u) back to pool.",
  724. pWorkItem, pWorkItem->m_uiUniqueID,
  725. pWorkItem->m_dwDueTime, dwBucket);
  726. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  727. }
  728. else
  729. {
  730. DPFX(DPFPREP, 8, "Queueing timer work item 0x%p (unique ID %u, due time = %u, bucket %u) for completion on queue 0x%p.",
  731. pWorkItem, pWorkItem->m_uiUniqueID,
  732. pWorkItem->m_dwDueTime, dwBucket,
  733. pWorkQueue);
  734. #ifdef DBG
  735. //
  736. // Make sure we didn't miss any timers last time around,
  737. // unless we were really, really delayed.
  738. //
  739. {
  740. DWORD dwTimePastDueTime;
  741. DWORD dwElapsedTimeWithRoundError;
  742. dwTimePastDueTime = dwCurrentTime - pWorkItem->m_dwDueTime;
  743. dwElapsedTimeWithRoundError = dwElapsedTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue);
  744. if (dwTimePastDueTime > dwElapsedTimeWithRoundError)
  745. {
  746. DPFX(DPFPREP, 1, "Missed timer work item 0x%p, its due time of %u is off by about %u ms.",
  747. pWorkItem, pWorkItem->m_dwDueTime, dwTimePastDueTime);
  748. #if ((defined(DPNBUILD_DONTCHECKFORMISSEDTIMERS)) || (defined(DPNBUILD_NOMISSEDTIMERSHINT)))
  749. DNASSERTX(fNotServicedForLongTime, 2);
  750. #else // ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and ! DPNBUILD_NOMISSEDTIMERSHINT
  751. DNASSERT(fNotServicedForLongTime);
  752. #endif // ! ! DPNBUILD_DONTCHECKFORMISSEDTIMERS and DPNBUILD_NOMISSEDTIMERSHINT
  753. }
  754. }
  755. #endif // DBG
  756. ThreadpoolStatsQueue(pWorkItem);
  757. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  758. //
  759. // Queue it to the I/O completion port.
  760. //
  761. BOOL fResult;
  762. fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  763. 0,
  764. 0,
  765. &pWorkItem->m_Overlapped);
  766. DNASSERT(fResult);
  767. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  768. //
  769. // Add it to the caller's list.
  770. //
  771. if ((*ppHead) == NULL)
  772. {
  773. *ppTail = &pWorkItem->m_SlistEntry;
  774. }
  775. pWorkItem->m_SlistEntry.Next = *ppHead;
  776. *ppHead = &pWorkItem->m_SlistEntry;
  777. (*pusCount)++;
  778. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  779. }
  780. }
  781. else
  782. {
  783. //
  784. // It's a "long" timer, and hasn't expired yet. Sample the
  785. // boolean to see if it has been cancelled. If so, just
  786. // put the entry back into the pool. Otherwise, put it
  787. // back into the bucket.
  788. //
  789. fCancelled = pWorkItem->m_fCancelledOrCompleting;
  790. if (fCancelled)
  791. {
  792. DPFX(DPFPREP, 5, "Returning timer work item 0x%p (unique ID %u, due time = %u, bucket %u) back to pool.",
  793. pWorkItem, pWorkItem->m_uiUniqueID,
  794. pWorkItem->m_dwDueTime, dwBucket);
  795. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  796. }
  797. else
  798. {
  799. DPFX(DPFPREP, 8, "Putting long timer work item 0x%p (unique ID %u, due time = %u) back in bucket %u.",
  800. pWorkItem, pWorkItem->m_uiUniqueID,
  801. pWorkItem->m_dwDueTime, dwBucket);
  802. #ifdef DBG
  803. //
  804. // Make sure it really is in the future.
  805. //
  806. DWORD dwRoundedDueTime = (pWorkItem->m_dwDueTime + TIMER_BUCKET_GRANULARITY_CEILING(pWorkQueue))
  807. & TIMER_BUCKET_GRANULARITY_FLOOR_MASK(pWorkQueue);
  808. DWORD dwTotalArrayTime = TIMER_BUCKET_GRANULARITY(pWorkQueue) * NUM_TIMER_BUCKETS(pWorkQueue);
  809. DNASSERT((dwRoundedDueTime - dwBucketTime) >= dwTotalArrayTime);
  810. #endif // DBG
  811. #pragma TODO(vanceo, "Investigate if saving up all long timers and pushing the whole list on at once will be beneficial")
  812. DNInterlockedPushEntrySList(&pWorkQueue->paSlistTimerBuckets[dwBucket],
  813. &pWorkItem->m_SlistEntry);
  814. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  815. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumLongTimersRescheduled));
  816. #endif // DPNBUILD_THREADPOOLSTATISTICS
  817. }
  818. }
  819. } // end while (still more timer entries in bucket)
  820. dwBucket = (dwBucket + 1) & NUM_TIMER_BUCKETS_MOD_MASK(pWorkQueue);
  821. #ifdef DBG
  822. dwBucketTime += TIMER_BUCKET_GRANULARITY(pWorkQueue);
  823. #endif // DBG
  824. } // end while (still more expired buckets)
  825. //
  826. // Remember when we started processing for use the next time through.
  827. //
  828. pWorkQueue->dwLastTimerProcessTime = dwCurrentTime;
  829. }
  830. } // ProcessTimers