Source code of Windows XP (NT5)
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.

830 lines
21 KiB

  1. /*==========================================================================
  2. *
  3. * Copyright (C) 1999 Microsoft Corporation. All Rights Reserved.
  4. *
  5. * File: Timer.cpp
  6. * Content: This file contains code to for the Protocol timers
  7. *
  8. * History:
  9. * Date By Reason
  10. * ==== == ======
  11. * 06/04/98 aarono Created
  12. * 07/01/00 masonb Assumed Ownership
  13. *
  14. ****************************************************************************/
  15. #include "dnproti.h"
  16. /*
  17. ** Quick Insert Optimizers
  18. **
  19. ** In a very high user system there are going to be many timers being set and cancelled. Timer
  20. ** cancels and timer fires are already optimized, but as the timer list grows the SetTimer operations
  21. ** become higher and higher overhead as we walk through a longer and longer chain for our insertion-sort.
  22. **
  23. ** Front First for Short Timers
  24. **
  25. ** When very short timers are being set we can assume that they will insert towards the front of the
  26. ** timer list. So it would be smarter to walk the list front-to-back instead of the back-to-front default
  27. ** behavior which correctly assumes that most new timers will be firing after timers already set. If the
  28. ** the Timeout value of a new timer is near the current timer resolution then we will try the front-first
  29. ** insertion-sort instead. This will hopefully reduce short timer sets to fairly quick operations
  30. **
  31. ** Standard Long Timers
  32. **
  33. ** Standard means that they will all have the same duration. If we keep a seperate chain
  34. ** for all these timers with a constant duration they can be trivally inserted at the end of the chain. This
  35. ** will be used for the periodic background checks run on each endpoint every couple of seconds.
  36. **
  37. ** Quick Set Timer Array
  38. **
  39. ** The really big optimization is an array of timeout lists, with a current pointer. Periodic timeout
  40. ** will walk the array a number of slots corresponding to the interval since it was last run. All events
  41. ** on those lists will be scheduled. This turns all SetTimer ops into constant time operations
  42. ** no matter how many timers are running in the system. This can be used for all timers within the
  43. ** range of the array (resolution X number of slots) which may be 4ms * 256 slots or a 1K ms range. We expect
  44. ** most link timers to fall into this range, although it can be doubled or quadrupled quite trivially.
  45. **
  46. ** I plan to run QST algorithm on any server platform, which will replace Front First Short Timers for
  47. ** obvious reasons. Client or Peer servers will use FFS instead. Both configs will benefit from StdLTs
  48. ** unless the range of the QST array grows to encompass the standard length timeout.
  49. */
  50. #define DEFAULT_TIME_RESOLUTION 4 /* ms */
  51. #define MAX_TIMER_THREADS_PER_PROCESSOR 8
  52. DWORD WINAPI TimerWorkerThread(LPVOID);
  53. CBilink g_blMyTimerList; // Random Timer List
  54. CBilink g_blStdTimerList; // Standard Length Timer List
  55. DNCRITICAL_SECTION g_csMyTimerListLock; // One lock will guard both lists
  56. LPFPOOL g_pTimerPool = NULL;
  57. DWORD g_dwWorkaroundTimerID;
  58. DWORD g_dwUnique = 0;
  59. UINT g_uiTimeSetEventFlags = TIME_PERIODIC;
  60. DNCRITICAL_SECTION g_csThreadListLock; // locks ALL this stuff.
  61. CBilink g_blThreadList; // ThreadPool grabs work from here.
  62. DWORD g_nThreads = 0; // number of running threads.
  63. DWORD g_dwActiveRequests = 0; // number of requests being processed.
  64. DWORD g_fShutDown = TRUE;
  65. DWORD g_dwExtraSignals = 0;
  66. HANDLE g_hWorkToDoSem = 0;
  67. SYSTEM_INFO g_SystemInfo;
  68. DWORD g_dwMaxTimerThreads = MAX_TIMER_THREADS_PER_PROCESSOR;
  69. HANDLE *g_phTimerThreadHandles = NULL;
  70. /***
  71. *
  72. * QUICK-START TIMER SUPPORT
  73. *
  74. ***/
  75. #define QST_SLOTCOUNT 2048 // 2048 seperate timer queues
  76. #define QST_GRANULARITY 4 // 4 ms clock granularity * 2048 slots == 8192 ms max timeout value
  77. #define QST_MAX_TIMEOUT (QST_SLOTCOUNT * QST_GRANULARITY)
  78. #define QST_MOD_MASK (QST_SLOTCOUNT - 1) // Calculate a quick modulo operation for wrapping around the array
  79. #if ( (QST_GRANULARITY - 1) & QST_GRANULARITY )
  80. This Will Not Compile -- ASSERT that QST_GRANULARITY is power of 2!
  81. #endif
  82. #if ( (QST_SLOTCOUNT - 1) & QST_SLOTCOUNT )
  83. This Will Not Compile -- ASSERT that QST_SLOTCOUNT is power of 2!
  84. #endif
  85. CBilink g_rgblQSTimerArray[QST_SLOTCOUNT];
  86. UINT g_uiQSTCurrentIndex; // Last array slot that was executed
  87. DWORD g_dwQSTLastRunTime; // Tick count when QSTs last ran
  88. /*
  89. * END OF QST SUPPORT
  90. */
  91. #undef Lock
  92. #undef Unlock
  93. #define Lock DNEnterCriticalSection
  94. #define Unlock DNLeaveCriticalSection
  95. /*
  96. ** Periodic Timer
  97. **
  98. ** This runs every RESOLUTION millisecs and checks for expired timers. It must check two lists
  99. ** for expired timers, plus a variable number of slots in the QST array.
  100. */
  101. #undef DPF_MODNAME
  102. #define DPF_MODNAME "PeriodicTimer"
  103. void CALLBACK PeriodicTimer (UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
  104. {
  105. DWORD time;
  106. PMYTIMER pTimerWalker;
  107. CBilink *pBilink;
  108. DWORD dwReleaseCount = 0;
  109. INT interval;
  110. DWORD slot_count;
  111. if(g_fShutDown)
  112. {
  113. return;
  114. }
  115. time = GETTIMESTAMP();
  116. Lock(&g_csMyTimerListLock);
  117. Lock(&g_csThreadListLock);
  118. time += (DEFAULT_TIME_RESOLUTION/2);
  119. // Service QST lists: Calculate how many array slots have expired and
  120. // service any timers in those slots.
  121. interval = (INT) (time - g_dwQSTLastRunTime);
  122. if( (interval) > 0)
  123. {
  124. slot_count = ((DWORD) interval) / QST_GRANULARITY;
  125. slot_count = MIN(slot_count, QST_SLOTCOUNT);
  126. if(slot_count < QST_SLOTCOUNT)
  127. {
  128. g_dwQSTLastRunTime += (slot_count * QST_GRANULARITY);
  129. }
  130. else
  131. {
  132. // If there was a LONG delay in scheduling this, (longer then the range of the whole array)
  133. // then we must complete everything that is on the array and then re-synchronize the times
  134. slot_count = QST_SLOTCOUNT;
  135. g_dwQSTLastRunTime = time;
  136. }
  137. while(slot_count--)
  138. {
  139. while( (pBilink = g_rgblQSTimerArray[g_uiQSTCurrentIndex].GetNext()) != &g_rgblQSTimerArray[g_uiQSTCurrentIndex] )
  140. {
  141. pTimerWalker = CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
  142. pBilink->RemoveFromList();
  143. pTimerWalker->Bilink.InsertBefore( &g_blThreadList);
  144. pTimerWalker->TimerState = QueuedForThread;
  145. dwReleaseCount++;
  146. }
  147. g_uiQSTCurrentIndex = (g_uiQSTCurrentIndex + 1) & QST_MOD_MASK;
  148. }
  149. }
  150. // Walk the sorted timer list. Expired timers will all be at the front of the
  151. // list so we can stop checking as soon as we find any un-expired timer.
  152. pBilink = g_blMyTimerList.GetNext();
  153. while(pBilink != &g_blMyTimerList)
  154. {
  155. pTimerWalker = CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
  156. pBilink = pBilink->GetNext();
  157. if(((INT)(time-pTimerWalker->TimeOut) > 0))
  158. {
  159. pTimerWalker->Bilink.RemoveFromList();
  160. pTimerWalker->Bilink.InsertBefore( &g_blThreadList);
  161. pTimerWalker->TimerState = QueuedForThread;
  162. dwReleaseCount++;
  163. }
  164. else
  165. {
  166. break;
  167. }
  168. }
  169. // Next walk the Standard Length list. Same rules apply
  170. pBilink=g_blStdTimerList.GetNext();
  171. while(pBilink != &g_blStdTimerList)
  172. {
  173. pTimerWalker = CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
  174. pBilink = pBilink->GetNext();
  175. if(((INT)(time-pTimerWalker->TimeOut) > 0))
  176. {
  177. pTimerWalker->Bilink.RemoveFromList();
  178. pTimerWalker->Bilink.InsertBefore( &g_blThreadList);
  179. pTimerWalker->TimerState = QueuedForThread;
  180. dwReleaseCount++;
  181. }
  182. else
  183. {
  184. break;
  185. }
  186. }
  187. g_dwActiveRequests += dwReleaseCount;
  188. Unlock(&g_csThreadListLock);
  189. Unlock(&g_csMyTimerListLock);
  190. ReleaseSemaphore(g_hWorkToDoSem,dwReleaseCount,NULL);
  191. }
  192. #undef DPF_MODNAME
  193. #define DPF_MODNAME "ScheduleTimerThread"
  194. VOID ScheduleTimerThread(MYTIMERCALLBACK TimerCallBack, PVOID UserContext, PVOID *pHandle, PUINT pUnique)
  195. {
  196. PMYTIMER pTimer;
  197. if(g_fShutDown)
  198. {
  199. ASSERT(0);
  200. *pHandle = 0;
  201. *pUnique = 0;
  202. return;
  203. }
  204. pTimer = static_cast<PMYTIMER>( g_pTimerPool->Get(g_pTimerPool) );
  205. if (!pTimer)
  206. {
  207. *pHandle = 0;
  208. *pUnique = 0;
  209. return;
  210. }
  211. DPFX(DPFPREP,DPF_TIMER_LVL, "Parameters: TimerCallBack[%p], UserContext[%p] - Timer[%p]", TimerCallBack, UserContext, pTimer);
  212. pTimer->CallBack = TimerCallBack;
  213. pTimer->Context = UserContext;
  214. Lock(&g_csMyTimerListLock);
  215. Lock(&g_csThreadListLock);
  216. *pUnique = ++g_dwUnique;
  217. if(g_dwUnique == 0)
  218. {
  219. *pUnique = ++g_dwUnique;
  220. }
  221. pTimer->Unique = *pUnique;
  222. *pHandle = pTimer;
  223. pTimer->Bilink.InsertBefore( &g_blThreadList);
  224. pTimer->TimerState = QueuedForThread;
  225. g_dwActiveRequests++;
  226. Unlock(&g_csThreadListLock);
  227. Unlock(&g_csMyTimerListLock);
  228. ReleaseSemaphore(g_hWorkToDoSem,1,NULL);
  229. }
  230. #undef DPF_MODNAME
  231. #define DPF_MODNAME "SetMyTimer"
  232. VOID SetMyTimer(DWORD dwTimeOut, DWORD, MYTIMERCALLBACK TimerCallBack, PVOID UserContext, PVOID *pHandle, PUINT pUnique)
  233. {
  234. CBilink* pBilink;
  235. PMYTIMER pMyTimerWalker, pTimer;
  236. DWORD time;
  237. BOOL fInserted=FALSE;
  238. UINT Offset;
  239. UINT Index;
  240. if (g_fShutDown)
  241. {
  242. ASSERT(0);
  243. *pHandle = 0;
  244. *pUnique = 0;
  245. return;
  246. }
  247. time = GETTIMESTAMP();
  248. pTimer = static_cast<PMYTIMER>( g_pTimerPool->Get(g_pTimerPool) );
  249. if (!pTimer)
  250. {
  251. *pHandle = 0;
  252. *pUnique = 0;
  253. return;
  254. }
  255. DPFX(DPFPREP,DPF_TIMER_LVL, "Parameters: dwTimeOut[%d], TimerCallBack[%p], UserContext[%p] - Timer[%p]", dwTimeOut, TimerCallBack, UserContext, pTimer);
  256. pTimer->CallBack = TimerCallBack;
  257. pTimer->Context = UserContext;
  258. Lock(&g_csMyTimerListLock);
  259. *pUnique = ++g_dwUnique;
  260. if(g_dwUnique == 0)
  261. {
  262. *pUnique = ++g_dwUnique;
  263. }
  264. pTimer->Unique = *pUnique;
  265. *pHandle = pTimer;
  266. pTimer->TimeOut=time+dwTimeOut;
  267. pTimer->TimerState=WaitingForTimeout;
  268. if(dwTimeOut < QST_MAX_TIMEOUT)
  269. {
  270. Offset = (dwTimeOut + (QST_GRANULARITY/2)) / QST_GRANULARITY; // Round nearest and convert time to slot offset
  271. Index = (Offset + g_uiQSTCurrentIndex) & QST_MOD_MASK; // Our index will be Current + Offset MOD TableSize
  272. pTimer->Bilink.InsertBefore( &g_rgblQSTimerArray[Index]); // Its called Quick-Start for a reason.
  273. }
  274. // OPTIMIZE FOR STANDARD TIMER
  275. //
  276. // Rather then calling a special API for StandardLongTimers as described above, we can just pull out
  277. // any timer with the correct Timeout value and stick it on the end of the StandardTimerList. I believe
  278. // this is the most straightforward way to do it. Now really, we could put anything with a TO +/- resolution
  279. // on the standard list too, but that might not be all that useful...
  280. else if(dwTimeOut == STANDARD_LONG_TIMEOUT_VALUE)
  281. {
  282. // This is a STANDARD TIMEOUT so add it to the end of the standard list.
  283. pTimer->Bilink.InsertBefore( &g_blStdTimerList);
  284. }
  285. // OPTIMIZE FOR SHORT TIMERS !! DONT NEED TO DO THIS IF USING Quick Start Timers !!
  286. //
  287. // If the timer has a very small Timeout value (~20ms) lets insert from the head of the list
  288. // instead of from the tail.
  289. else
  290. {
  291. // DEFAULT - Assume new timers will likely sort to the end of the list.
  292. //
  293. // Insert this guy in the sorted list by timeout time, walking from the tail forward.
  294. pBilink=g_blMyTimerList.GetPrev();
  295. while(pBilink != &g_blMyTimerList)
  296. {
  297. pMyTimerWalker=CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
  298. pBilink=pBilink->GetPrev();
  299. if((int)(pTimer->TimeOut-pMyTimerWalker->TimeOut) > 0 )
  300. {
  301. pTimer->Bilink.InsertAfter( &pMyTimerWalker->Bilink);
  302. fInserted=TRUE;
  303. break;
  304. }
  305. }
  306. if(!fInserted)
  307. {
  308. pTimer->Bilink.InsertAfter( &g_blMyTimerList);
  309. }
  310. }
  311. Unlock(&g_csMyTimerListLock);
  312. return;
  313. }
  314. #undef DPF_MODNAME
  315. #define DPF_MODNAME "CancelMyTimer"
  316. HRESULT CancelMyTimer(PVOID dwTimer, DWORD Unique)
  317. {
  318. PMYTIMER pTimer = (PMYTIMER)dwTimer;
  319. HRESULT hr = DPNERR_GENERIC;
  320. if(pTimer == 0)
  321. {
  322. return DPN_OK;
  323. }
  324. DPFX(DPFPREP,DPF_TIMER_LVL, "Parameters: Timer[%p]", pTimer);
  325. Lock(&g_csMyTimerListLock);
  326. Lock(&g_csThreadListLock);
  327. if(pTimer->Unique == Unique)
  328. {
  329. switch(pTimer->TimerState)
  330. {
  331. case WaitingForTimeout:
  332. pTimer->Bilink.RemoveFromList();
  333. pTimer->TimerState = End;
  334. pTimer->Unique = 0;
  335. g_pTimerPool->Release(g_pTimerPool, pTimer);
  336. hr=DPN_OK;
  337. break;
  338. case QueuedForThread:
  339. pTimer->Bilink.RemoveFromList();
  340. pTimer->TimerState = End;
  341. pTimer->Unique = 0;
  342. g_pTimerPool->Release(g_pTimerPool, pTimer);
  343. if(g_dwActiveRequests)
  344. {
  345. g_dwActiveRequests--;
  346. }
  347. g_dwExtraSignals++;
  348. hr = DPN_OK;
  349. break;
  350. default:
  351. DPFX(DPFPREP,DPF_TIMER_LVL, "Couldn't cancel timer - Timer[%p]", pTimer);
  352. break;
  353. }
  354. }
  355. Unlock(&g_csThreadListLock);
  356. Unlock(&g_csMyTimerListLock);
  357. return hr;
  358. }
  359. #undef DPF_MODNAME
  360. #define DPF_MODNAME "TimerInit"
  361. /*
  362. This function is for initialization that is done only once for the life of the module
  363. */
  364. HRESULT TimerInit()
  365. {
  366. DWORD iSlot;
  367. DPFX(DPFPREP,DPF_TIMER_LVL, "Timer module-level initialization");
  368. if (DNOSIsXPOrGreater())
  369. {
  370. g_uiTimeSetEventFlags |= TIME_KILL_SYNCHRONOUS;
  371. }
  372. // Determine the maximum number of worker threads we will allow
  373. // Returns void, can't fail apparently
  374. GetSystemInfo(&g_SystemInfo);
  375. if (g_SystemInfo.dwNumberOfProcessors < 1)
  376. {
  377. g_SystemInfo.dwNumberOfProcessors = 1;
  378. }
  379. g_dwMaxTimerThreads = g_SystemInfo.dwNumberOfProcessors * MAX_TIMER_THREADS_PER_PROCESSOR;
  380. // Track thread handles in an array so we can wait on them at shutdown.
  381. g_phTimerThreadHandles = new HANDLE[g_dwMaxTimerThreads];
  382. if ( g_phTimerThreadHandles == NULL)
  383. {
  384. return DPNERR_OUTOFMEMORY;
  385. }
  386. g_blMyTimerList.Initialize();
  387. g_blStdTimerList.Initialize();
  388. g_blThreadList.Initialize();
  389. // Initialize all of the CBilink's
  390. for(iSlot = 0; iSlot < QST_SLOTCOUNT; iSlot++)
  391. {
  392. g_rgblQSTimerArray[iSlot].Initialize();
  393. }
  394. if (DNInitializeCriticalSection(&g_csMyTimerListLock) == FALSE)
  395. {
  396. delete[] g_phTimerThreadHandles;
  397. g_phTimerThreadHandles = NULL;
  398. return DPNERR_OUTOFMEMORY;
  399. }
  400. DebugSetCriticalSectionRecursionCount(&g_csMyTimerListLock,0);
  401. if (DNInitializeCriticalSection(&g_csThreadListLock) == FALSE)
  402. {
  403. DNDeleteCriticalSection(&g_csMyTimerListLock);
  404. delete[] g_phTimerThreadHandles;
  405. g_phTimerThreadHandles = NULL;
  406. return DPNERR_OUTOFMEMORY;
  407. }
  408. DebugSetCriticalSectionRecursionCount(&g_csThreadListLock,0);
  409. g_pTimerPool = FPM_Create(sizeof(MYTIMER),NULL,NULL,NULL,NULL);
  410. if(!g_pTimerPool)
  411. {
  412. DNDeleteCriticalSection(&g_csThreadListLock);
  413. DNDeleteCriticalSection(&g_csMyTimerListLock);
  414. delete[] g_phTimerThreadHandles;
  415. g_phTimerThreadHandles = NULL;
  416. return DPNERR_OUTOFMEMORY;
  417. }
  418. // Set our time resolution to 1ms, ignore failure.
  419. (VOID)timeBeginPeriod(1);
  420. return DPN_OK;
  421. }
  422. #undef DPF_MODNAME
  423. #define DPF_MODNAME "TimerDeinit"
  424. /*
  425. This function is for initialization that is done only once for the life of the module
  426. */
  427. VOID TimerDeinit()
  428. {
  429. ASSERT(g_fShutDown);
  430. DPFX(DPFPREP,DPF_TIMER_LVL, "Timer module-level deinitialization");
  431. timeEndPeriod(1);
  432. DNDeleteCriticalSection(&g_csMyTimerListLock);
  433. DNDeleteCriticalSection(&g_csThreadListLock);
  434. if(g_pTimerPool)
  435. {
  436. g_pTimerPool->Fini(g_pTimerPool);
  437. }
  438. if (g_phTimerThreadHandles)
  439. {
  440. delete[] g_phTimerThreadHandles;
  441. g_phTimerThreadHandles = NULL;
  442. }
  443. }
  444. #undef DPF_MODNAME
  445. #define DPF_MODNAME "InitTimerWorkaround"
  446. HRESULT InitTimerWorkaround()
  447. {
  448. DWORD dwJunk;
  449. DWORD iSlot;
  450. DPFX(DPFPREP,DPF_TIMER_LVL, "Initialize Timer Package");
  451. // Reinitialize globals
  452. g_nThreads = 0; // number of running threads.
  453. g_dwActiveRequests = 0; // number of requests being processed.
  454. g_dwExtraSignals = 0;
  455. ASSERT(g_phTimerThreadHandles);
  456. memset(g_phTimerThreadHandles, 0, sizeof(HANDLE) * g_dwMaxTimerThreads);
  457. ASSERT(g_blMyTimerList.IsEmpty());
  458. ASSERT(g_blStdTimerList.IsEmpty());
  459. ASSERT(g_blThreadList.IsEmpty());
  460. #ifdef DEBUG
  461. for(iSlot = 0; iSlot < QST_SLOTCOUNT; iSlot++)
  462. {
  463. ASSERT(g_rgblQSTimerArray[iSlot].IsEmpty());
  464. }
  465. #endif
  466. g_uiQSTCurrentIndex = 0;
  467. g_dwQSTLastRunTime = GETTIMESTAMP();
  468. g_hWorkToDoSem = CreateSemaphore(NULL, 0, 65535, NULL);
  469. if (!g_hWorkToDoSem)
  470. {
  471. return DPNERR_OUTOFMEMORY;
  472. }
  473. // Start the timer
  474. g_dwWorkaroundTimerID = timeSetEvent(DEFAULT_TIME_RESOLUTION, DEFAULT_TIME_RESOLUTION, PeriodicTimer, 0, g_uiTimeSetEventFlags);
  475. if(!g_dwWorkaroundTimerID)
  476. {
  477. FiniTimerWorkaround();
  478. return DPNERR_OUTOFMEMORY;
  479. }
  480. // We are up and running. Do this before starting the thread.
  481. g_fShutDown = FALSE;
  482. g_nThreads = 1;
  483. g_phTimerThreadHandles[0] = CreateThread(NULL, 4096, TimerWorkerThread, 0, 0, &dwJunk);
  484. if( !g_phTimerThreadHandles[0])
  485. {
  486. g_nThreads = 0;
  487. FiniTimerWorkaround();
  488. return DPNERR_OUTOFMEMORY;
  489. }
  490. return DPN_OK;
  491. }
  492. #undef DPF_MODNAME
  493. #define DPF_MODNAME "PurgeTimerList"
  494. VOID PurgeTimerList(CBilink *pList)
  495. {
  496. PMYTIMER pTimer;
  497. while(!pList->IsEmpty())
  498. {
  499. pTimer = CONTAINING_RECORD(pList->GetNext(), MYTIMER, Bilink);
  500. pTimer->Unique = 0;
  501. pTimer->TimerState = End;
  502. pTimer->Bilink.RemoveFromList();
  503. g_pTimerPool->Release(g_pTimerPool, pTimer);
  504. }
  505. }
  506. #undef DPF_MODNAME
  507. #define DPF_MODNAME "FiniTimerWorkaround"
  508. VOID FiniTimerWorkaround()
  509. {
  510. DWORD iSlot;
  511. DPFX(DPFPREP,DPF_TIMER_LVL, "Deinitialize Timer Package");
  512. // At this point:
  513. // 1) No one else will call SetMyTimer or ScheduleTimerThread
  514. // 2) The only timer left should be AdjustTimerResolution
  515. // Kill the timer so it never fires again
  516. if(g_dwWorkaroundTimerID)
  517. {
  518. // We have to do this outside the lock because on XP this will be waiting on the last timer to fire
  519. // which may be waiting for the lock.
  520. timeKillEvent(g_dwWorkaroundTimerID);
  521. if (!(g_uiTimeSetEventFlags & TIME_KILL_SYNCHRONOUS))
  522. {
  523. // The WinMM timer may try to fire again, so wait a little while for it
  524. DPFX(DPFPREP,DPF_TIMER_LVL, "OS is not XP or better, waiting for WinMM timer to finish");
  525. Sleep(2000);
  526. }
  527. }
  528. // At this point:
  529. // 1) The winmm timer will not fire again and therefore PeriodicTimer will not be called again
  530. // Tell all remaining timer threads to shutdown
  531. Lock(&g_csThreadListLock);
  532. g_fShutDown = TRUE;
  533. Unlock(&g_csThreadListLock);
  534. ReleaseSemaphore(g_hWorkToDoSem, g_dwMaxTimerThreads, NULL);
  535. // At this point:
  536. // 1) No threads should be waiting in TimerWorkerThread and no new ones will be scheduled
  537. Lock(&g_csThreadListLock);
  538. for (iSlot = 0; iSlot < g_dwMaxTimerThreads; iSlot++)
  539. {
  540. // We can stop at the first NULL handle
  541. if (!g_phTimerThreadHandles[iSlot])
  542. {
  543. break;
  544. }
  545. Unlock(&g_csThreadListLock);
  546. WaitForSingleObject(g_phTimerThreadHandles[iSlot], INFINITE);
  547. CloseHandle(g_phTimerThreadHandles[iSlot]);
  548. Lock(&g_csThreadListLock);
  549. g_phTimerThreadHandles[iSlot] = 0;
  550. }
  551. Unlock(&g_csThreadListLock);
  552. // At this point:
  553. // 1) All TimerWorkerThreads are gone
  554. CloseHandle(g_hWorkToDoSem);
  555. g_hWorkToDoSem = 0;
  556. PurgeTimerList(&g_blMyTimerList);
  557. PurgeTimerList(&g_blStdTimerList);
  558. PurgeTimerList(&g_blThreadList);
  559. for(iSlot = 0; iSlot < QST_SLOTCOUNT; iSlot++)
  560. {
  561. PurgeTimerList(&g_rgblQSTimerArray[iSlot]);
  562. }
  563. ASSERT(g_blMyTimerList.IsEmpty());
  564. ASSERT(g_blStdTimerList.IsEmpty());
  565. ASSERT(g_blThreadList.IsEmpty());
  566. #ifdef DEBUG
  567. for(iSlot = 0; iSlot < QST_SLOTCOUNT; iSlot++)
  568. {
  569. ASSERT(g_rgblQSTimerArray[iSlot].IsEmpty());
  570. }
  571. #endif
  572. }
  573. #undef DPF_MODNAME
  574. #define DPF_MODNAME "TimerWorkerThread"
  575. DWORD WINAPI TimerWorkerThread(LPVOID)
  576. {
  577. CBilink *pBilink;
  578. PMYTIMER pTimer;
  579. DWORD dwJunk;
  580. DWORD iThread;
  581. HRESULT hr;
  582. DPFX(DPFPREP,DPF_TIMER_LVL, "Timer thread starting 0x%x", GetCurrentThreadId());
  583. if ((hr = COM_CoInitialize(NULL)) != S_OK)
  584. {
  585. DPFX(DPFPREP,0, "Timer thread failed to initialize COM hr=0x%x", hr);
  586. goto Exit;
  587. }
  588. while (1)
  589. {
  590. WaitForSingleObject(g_hWorkToDoSem, INFINITE);
  591. Lock(&g_csThreadListLock);
  592. if(g_fShutDown)
  593. {
  594. Unlock(&g_csThreadListLock);
  595. break;
  596. }
  597. if(g_dwExtraSignals)
  598. {
  599. g_dwExtraSignals--;
  600. Unlock(&g_csThreadListLock);
  601. continue;
  602. }
  603. if (g_dwActiveRequests > g_nThreads && g_nThreads < g_dwMaxTimerThreads)
  604. {
  605. ASSERT(g_phTimerThreadHandles[0] != 0); // The first slot should never be empty
  606. // Find the first empty slot.
  607. for (iThread = 0; iThread < g_dwMaxTimerThreads; iThread++)
  608. {
  609. if (g_phTimerThreadHandles[iThread] == 0)
  610. {
  611. // NOTE: CreateThread takes a long time and we are stalling all work by having
  612. // the lock when we call it. Revise in future.
  613. g_phTimerThreadHandles[iThread] = CreateThread(NULL, 4096, TimerWorkerThread, 0, 0, &dwJunk);
  614. if (g_phTimerThreadHandles[iThread])
  615. {
  616. g_nThreads++;
  617. }
  618. // If CreateThread failed no harm is done we just don't get the extra help of
  619. // another worker thread.
  620. }
  621. }
  622. }
  623. pBilink = g_blThreadList.GetNext();
  624. if(pBilink == &g_blThreadList)
  625. {
  626. Unlock(&g_csThreadListLock);
  627. continue;
  628. };
  629. pBilink->RemoveFromList(); // pull off the list.
  630. pTimer = CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
  631. // Call a callback
  632. DPFX(DPFPREP,DPF_TIMER_LVL, "Servicing Timer Job - Timer[%p], Context[%p], Callback[%p]", pTimer, pTimer->Context, pTimer->CallBack);
  633. pTimer->TimerState=InCallBack;
  634. Unlock(&g_csThreadListLock);
  635. (pTimer->CallBack)(pTimer, (UINT) pTimer->Unique, pTimer->Context);
  636. pTimer->Unique = 0;
  637. pTimer->TimerState = End;
  638. g_pTimerPool->Release(g_pTimerPool, pTimer);
  639. Lock(&g_csThreadListLock);
  640. if(g_dwActiveRequests)
  641. {
  642. g_dwActiveRequests--;
  643. }
  644. Unlock(&g_csThreadListLock);
  645. }
  646. COM_CoUninitialize();
  647. Exit:
  648. // Thread is terminating.
  649. DPFX(DPFPREP,DPF_TIMER_LVL, "Timer thread exiting 0x%x", GetCurrentThreadId());
  650. return 0;
  651. }