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.

2692 lines
80 KiB

  1. /******************************************************************************
  2. *
  3. * Copyright (C) 2001-2002 Microsoft Corporation. All Rights Reserved.
  4. *
  5. * File: work.cpp
  6. *
  7. * Content: DirectPlay Thread Pool work processing functions.
  8. *
  9. * History:
  10. * Date By Reason
  11. * ======== ======== =========
  12. * 10/31/01 VanceO Created.
  13. *
  14. ******************************************************************************/
  15. #include "dpnthreadpooli.h"
  16. //=============================================================================
  17. // Defines
  18. //=============================================================================
  19. #define MAX_SIMULTANEOUS_THREAD_START (MAXIMUM_WAIT_OBJECTS - 1) // WaitForMultipleObjects can only handle 64 items at a time, we need one slot for the start event
  20. #undef DPF_MODNAME
  21. #define DPF_MODNAME "InitializeWorkQueue"
  22. //=============================================================================
  23. // InitializeWorkQueue
  24. //-----------------------------------------------------------------------------
  25. //
  26. // Description: Initializes the specified work queue.
  27. //
  28. // Arguments:
  29. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to
  30. // initialize.
  31. // DWORD dwCPUNum - CPU number this queue is to
  32. // represent.
  33. // PFNDPNMESSAGEHANDLER pfnMsgHandler - User's message handler callback, or
  34. // NULL if none.
  35. // PVOID pvMsgHandlerContext - Context for user's message handler.
  36. // DWORD dwWorkerThreadTlsIndex - TLS index to use for storing worker
  37. // thread data.
  38. //
  39. // Returns: HRESULT
  40. // DPN_OK - Successfully initialized the work queue object.
  41. // DPNERR_OUTOFMEMORY - Failed to allocate memory while initializing.
  42. //=============================================================================
  43. #ifdef DPNBUILD_ONLYONETHREAD
  44. #ifdef DPNBUILD_ONLYONEPROCESSOR
  45. HRESULT InitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue)
  46. #else // ! DPNBUILD_ONLYONEPROCESSOR
  47. HRESULT InitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue,
  48. const DWORD dwCPUNum)
  49. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  50. #else // ! DPNBUILD_ONLYONETHREAD
  51. HRESULT InitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue,
  52. #ifndef DPNBUILD_ONLYONEPROCESSOR
  53. const DWORD dwCPUNum,
  54. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  55. const PFNDPNMESSAGEHANDLER pfnMsgHandler,
  56. PVOID const pvMsgHandlerContext,
  57. const DWORD dwWorkerThreadTlsIndex)
  58. #endif // ! DPNBUILD_ONLYONETHREAD
  59. {
  60. HRESULT hr;
  61. BOOL fInittedWorkItemPool = FALSE;
  62. #if ((! defined(WINCE)) || (defined(DBG)))
  63. BOOL fInittedListLock = FALSE;
  64. #endif // ! WINCE or DBG
  65. BOOL fInittedTimerInfo = FALSE;
  66. #ifndef WINCE
  67. BOOL fInittedIoInfo = FALSE;
  68. #endif // ! WINCE
  69. #ifdef DBG
  70. DWORD dwError;
  71. #endif // DBG
  72. DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
  73. pWorkQueue->Sig[0] = 'W';
  74. pWorkQueue->Sig[1] = 'R';
  75. pWorkQueue->Sig[2] = 'K';
  76. pWorkQueue->Sig[3] = 'Q';
  77. pWorkQueue->pWorkItemPool = (CFixedPool*) DNMalloc(sizeof(CFixedPool));
  78. if (pWorkQueue->pWorkItemPool == NULL)
  79. {
  80. DPFX(DPFPREP, 0, "Couldn't allocate new work item pool!");
  81. hr = DPNERR_OUTOFMEMORY;
  82. goto Failure;
  83. }
  84. if (! pWorkQueue->pWorkItemPool->Initialize(sizeof(CWorkItem),
  85. CWorkItem::FPM_Alloc,
  86. CWorkItem::FPM_Get,
  87. CWorkItem::FPM_Release,
  88. //CWorkItem::FPM_Dealloc))
  89. NULL))
  90. {
  91. DPFX(DPFPREP, 0, "Couldn't initialize work item pool!");
  92. hr = DPNERR_OUTOFMEMORY;
  93. goto Failure;
  94. }
  95. fInittedWorkItemPool = TRUE;
  96. #if ((! defined(WINCE)) || (defined(DBG)))
  97. if (! DNInitializeCriticalSection(&pWorkQueue->csListLock))
  98. {
  99. DPFX(DPFPREP, 0, "Couldn't initialize list lock!");
  100. hr = DPNERR_OUTOFMEMORY;
  101. goto Failure;
  102. }
  103. DebugSetCriticalSectionRecursionCount(&pWorkQueue->csListLock, 0);
  104. fInittedListLock = TRUE;
  105. #endif // ! WINCE or DBG
  106. #ifndef DPNBUILD_USEIOCOMPLETIONPORTS
  107. DNInitializeSListHead(&pWorkQueue->SlistFreeQueueNodes);
  108. //
  109. // Add an initial node entry as required by NB Queue implementation.
  110. //
  111. DNInterlockedPushEntrySList(&pWorkQueue->SlistFreeQueueNodes,
  112. (DNSLIST_ENTRY*) (&pWorkQueue->NBQueueBlockInitial));
  113. //
  114. // Initialize the actual non-blocking queue.
  115. //
  116. pWorkQueue->pvNBQueueWorkItems = DNInitializeNBQueueHead(&pWorkQueue->SlistFreeQueueNodes);
  117. if (pWorkQueue->pvNBQueueWorkItems == NULL)
  118. {
  119. DPFX(DPFPREP, 0, "Couldn't initialize non-blocking queue!");
  120. hr = DPNERR_OUTOFMEMORY;
  121. goto Failure;
  122. }
  123. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  124. #ifndef DPNBUILD_ONLYONETHREAD
  125. pWorkQueue->fTimerThreadNeeded = TRUE;
  126. pWorkQueue->dwNumThreadsExpected = 0;
  127. pWorkQueue->dwNumBusyThreads = 0;
  128. pWorkQueue->dwNumRunningThreads = 0;
  129. #endif // ! DPNBUILD_ONLYONETHREAD
  130. #ifndef DPNBUILD_ONLYONEPROCESSOR
  131. pWorkQueue->dwCPUNum = dwCPUNum;
  132. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  133. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  134. //
  135. // Create an I/O completion port.
  136. //
  137. HANDLE hIoCompletionPort;
  138. hIoCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
  139. NULL,
  140. 0,
  141. 0);
  142. if (hIoCompletionPort == NULL)
  143. {
  144. #ifdef DBG
  145. dwError = GetLastError();
  146. DPFX(DPFPREP, 0, "Couldn't create I/O completion port (err = %u)!", dwError);
  147. #endif // DBG
  148. hr = DPNERR_OUTOFMEMORY;
  149. goto Failure;
  150. }
  151. pWorkQueue->hIoCompletionPort = MAKE_DNHANDLE(hIoCompletionPort);
  152. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  153. //
  154. // Create an event used to wake up idle worker threads.
  155. //
  156. pWorkQueue->hAlertEvent = DNCreateEvent(NULL, FALSE, FALSE, NULL);
  157. if (pWorkQueue->hAlertEvent == NULL)
  158. {
  159. #ifdef DBG
  160. dwError = GetLastError();
  161. DPFX(DPFPREP, 0, "Couldn't create alert event (err = %u)!", dwError);
  162. #endif // DBG
  163. hr = DPNERR_OUTOFMEMORY;
  164. goto Failure;
  165. }
  166. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  167. #ifndef DPNBUILD_ONLYONETHREAD
  168. pWorkQueue->hExpectedThreadsEvent = NULL;
  169. pWorkQueue->pfnMsgHandler = pfnMsgHandler;
  170. pWorkQueue->pvMsgHandlerContext = pvMsgHandlerContext;
  171. pWorkQueue->dwWorkerThreadTlsIndex = dwWorkerThreadTlsIndex;
  172. #endif // ! DPNBUILD_ONLYONETHREAD
  173. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  174. //
  175. // Initialize our debugging/tuning statistics.
  176. //
  177. pWorkQueue->dwTotalNumWorkItems = 0;
  178. #ifndef WINCE
  179. pWorkQueue->dwTotalTimeSpentUnsignalled = 0;
  180. pWorkQueue->dwTotalTimeSpentInWorkCallbacks = 0;
  181. #endif // ! WINCE
  182. #ifndef DPNBUILD_ONLYONETHREAD
  183. pWorkQueue->dwTotalNumTimerThreadAbdications = 0;
  184. #endif // ! DPNBUILD_ONLYONETHREAD
  185. pWorkQueue->dwTotalNumWakesWithoutWork = 0;
  186. pWorkQueue->dwTotalNumContinuousWork = 0;
  187. pWorkQueue->dwTotalNumDoWorks = 0;
  188. pWorkQueue->dwTotalNumDoWorksTimeLimit = 0;
  189. pWorkQueue->dwTotalNumSimultaneousQueues = 0;
  190. memset(pWorkQueue->aCallbackStats, 0, sizeof(pWorkQueue->aCallbackStats));
  191. #endif // DPNBUILD_THREADPOOLSTATISTICS
  192. #ifdef DBG
  193. #ifndef DPNBUILD_ONLYONETHREAD
  194. //
  195. // Initialize the structures helpful for debugging.
  196. //
  197. pWorkQueue->blThreadList.Initialize();
  198. #endif // ! DPNBUILD_ONLYONETHREAD
  199. #endif // DBG
  200. //
  201. // Initialize the timer aspects of the work queue
  202. //
  203. hr = InitializeWorkQueueTimerInfo(pWorkQueue);
  204. if (hr != DPN_OK)
  205. {
  206. DPFX(DPFPREP, 0, "Couldn't initialize timer info for work queue!");
  207. goto Failure;
  208. }
  209. fInittedTimerInfo = TRUE;
  210. #ifndef WINCE
  211. //
  212. // Initialize the I/O aspects of the work queue
  213. //
  214. hr = InitializeWorkQueueIoInfo(pWorkQueue);
  215. if (hr != DPN_OK)
  216. {
  217. DPFX(DPFPREP, 0, "Couldn't initialize I/O info for work queue!");
  218. goto Failure;
  219. }
  220. fInittedIoInfo = TRUE;
  221. #endif // ! WINCE
  222. Exit:
  223. DPFX(DPFPREP, 6, "Returning: [0x%lx]", hr);
  224. return hr;
  225. Failure:
  226. #ifndef WINCE
  227. if (fInittedIoInfo)
  228. {
  229. DeinitializeWorkQueueIoInfo(pWorkQueue);
  230. fInittedIoInfo = FALSE;
  231. }
  232. #endif // ! WINCE
  233. if (fInittedTimerInfo)
  234. {
  235. DeinitializeWorkQueueTimerInfo(pWorkQueue);
  236. fInittedTimerInfo = FALSE;
  237. }
  238. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  239. if (pWorkQueue->hIoCompletionPort != NULL)
  240. {
  241. DNCloseHandle(pWorkQueue->hIoCompletionPort);
  242. pWorkQueue->hIoCompletionPort = NULL;
  243. }
  244. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  245. if (pWorkQueue->hAlertEvent != NULL)
  246. {
  247. DNCloseHandle(pWorkQueue->hAlertEvent);
  248. pWorkQueue->hAlertEvent = NULL;
  249. }
  250. if (pWorkQueue->pvNBQueueWorkItems != NULL)
  251. {
  252. DNDeinitializeNBQueueHead(pWorkQueue->pvNBQueueWorkItems);
  253. pWorkQueue->pvNBQueueWorkItems = NULL;
  254. }
  255. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  256. #if ((! defined(WINCE)) || (defined(DBG)))
  257. if (fInittedListLock)
  258. {
  259. DNDeleteCriticalSection(&pWorkQueue->csListLock);
  260. fInittedListLock = FALSE;
  261. }
  262. #endif // ! WINCE or DBG
  263. if (pWorkQueue->pWorkItemPool != NULL)
  264. {
  265. if (fInittedWorkItemPool)
  266. {
  267. pWorkQueue->pWorkItemPool->DeInitialize();
  268. fInittedWorkItemPool = FALSE;
  269. }
  270. DNFree(pWorkQueue->pWorkItemPool);
  271. pWorkQueue->pWorkItemPool = NULL;
  272. }
  273. goto Exit;
  274. } // InitializeWorkQueue
  275. #undef DPF_MODNAME
  276. #define DPF_MODNAME "DeinitializeWorkQueue"
  277. //=============================================================================
  278. // DeinitializeWorkQueue
  279. //-----------------------------------------------------------------------------
  280. //
  281. // Description: Cleans up the work queue.
  282. //
  283. // Arguments:
  284. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to clean up.
  285. //
  286. // Returns: Nothing.
  287. //=============================================================================
  288. void DeinitializeWorkQueue(DPTPWORKQUEUE * const pWorkQueue)
  289. {
  290. #ifndef DPNBUILD_ONLYONETHREAD
  291. HRESULT hr;
  292. #endif // ! DPNBUILD_ONLYONETHREAD
  293. BOOL fResult;
  294. #ifdef DBG
  295. DWORD dwMaxRecursionCount = 0;
  296. #ifdef WINNT
  297. HANDLE hThread;
  298. FILETIME ftIgnoredCreation;
  299. FILETIME ftIgnoredExit;
  300. ULONGLONG ullKernel;
  301. ULONGLONG ullUser;
  302. #endif // WINNT
  303. #endif // DBG
  304. DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
  305. #ifndef DPNBUILD_ONLYONETHREAD
  306. #ifdef DBG
  307. //
  308. // Assert that there are truly threads running if there's supposed to be,
  309. // and that we're not being called on one of those threads.
  310. //
  311. DNEnterCriticalSection(&pWorkQueue->csListLock);
  312. if (pWorkQueue->dwNumRunningThreads > 0)
  313. {
  314. CBilink * pBilink;
  315. DPTPWORKERTHREAD * pWorkerThread;
  316. DNASSERT(! pWorkQueue->blThreadList.IsEmpty());
  317. pBilink = pWorkQueue->blThreadList.GetNext();
  318. while (pBilink != &pWorkQueue->blThreadList)
  319. {
  320. pWorkerThread = CONTAINING_OBJECT(pBilink, DPTPWORKERTHREAD, blList);
  321. #ifdef WINNT
  322. hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, pWorkerThread->dwThreadID);
  323. if (hThread != NULL)
  324. {
  325. if (GetThreadTimes(hThread, &ftIgnoredCreation, &ftIgnoredExit, (LPFILETIME) (&ullKernel), (LPFILETIME) (&ullUser)))
  326. {
  327. DPFX(DPFPREP, 6, "Found worker thread ID %u/0x%x, max recursion = %u, user time = %u, kernel time = %u.",
  328. pWorkerThread->dwThreadID, pWorkerThread->dwThreadID,
  329. pWorkerThread->dwMaxRecursionCount,
  330. (ULONG) (ullUser / ((ULONGLONG) 10000)),
  331. (ULONG) (ullKernel / ((ULONGLONG) 10000)));
  332. }
  333. else
  334. {
  335. DPFX(DPFPREP, 6, "Found worker thread ID %u/0x%x, max recursion = %u (get thread times failed).",
  336. pWorkerThread->dwThreadID, pWorkerThread->dwThreadID,
  337. pWorkerThread->dwMaxRecursionCount);
  338. }
  339. CloseHandle(hThread);
  340. hThread = NULL;
  341. }
  342. else
  343. #endif // WINNT
  344. {
  345. DPFX(DPFPREP, 6, "Found worker thread ID %u/0x%x, max recursion = %u.",
  346. pWorkerThread->dwThreadID, pWorkerThread->dwThreadID,
  347. pWorkerThread->dwMaxRecursionCount);
  348. }
  349. DNASSERT(pWorkerThread->dwThreadID != GetCurrentThreadId());
  350. if (pWorkerThread->dwMaxRecursionCount > dwMaxRecursionCount)
  351. {
  352. dwMaxRecursionCount = pWorkerThread->dwMaxRecursionCount;
  353. }
  354. pBilink = pBilink->GetNext();
  355. }
  356. }
  357. else
  358. {
  359. DNASSERT(pWorkQueue->blThreadList.IsEmpty());
  360. }
  361. DNLeaveCriticalSection(&pWorkQueue->csListLock);
  362. #endif // DBG
  363. //
  364. // Stop all threads, if there were any.
  365. //
  366. if (pWorkQueue->dwNumRunningThreads > 0)
  367. {
  368. hr = StopThreads(pWorkQueue, pWorkQueue->dwNumRunningThreads);
  369. DNASSERT(hr == DPN_OK);
  370. }
  371. else
  372. {
  373. //
  374. // Make sure the count didn't go negative.
  375. //
  376. DNASSERT(pWorkQueue->dwNumRunningThreads == 0);
  377. }
  378. //
  379. // All threads should be gone by now. Technically, that isn't true because
  380. // they still have some minor cleanup code to run after decrementing
  381. // lNumRunningThreads. But for our purposes, the threads are gone.
  382. // We also know that they pull themselves out of blThreadList prior to
  383. // alerting us that they've left, so the list should be empty by now.
  384. //
  385. #ifdef DBG
  386. DNASSERT(pWorkQueue->blThreadList.IsEmpty());
  387. #endif // DBG
  388. #endif // ! DPNBUILD_ONLYONETHREAD
  389. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  390. //
  391. // Print our debugging/tuning statistics.
  392. //
  393. #ifdef DPNBUILD_ONLYONEPROCESSOR
  394. DPFX(DPFPREP, 7, "Work queue 0x%p work stats:", pWorkQueue);
  395. #else // ! DPNBUILD_ONLYONEPROCESSOR
  396. DPFX(DPFPREP, 7, "Work queue 0x%p (CPU %u) work stats:", pWorkQueue, pWorkQueue->dwCPUNum);
  397. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  398. DPFX(DPFPREP, 7, " TotalNumWorkItems = %u", pWorkQueue->dwTotalNumWorkItems);
  399. #ifndef WINCE
  400. DPFX(DPFPREP, 7, " TotalTimeSpentUnsignalled = %u", pWorkQueue->dwTotalTimeSpentUnsignalled);
  401. DPFX(DPFPREP, 7, " TotalTimeSpentInWorkCallbacks = %u", pWorkQueue->dwTotalTimeSpentInWorkCallbacks);
  402. #endif // ! WINCE
  403. #ifndef DPNBUILD_ONLYONETHREAD
  404. DPFX(DPFPREP, 7, " TotalNumTimerThreadAbdications = %u", pWorkQueue->dwTotalNumTimerThreadAbdications);
  405. #endif // ! DPNBUILD_ONLYONETHREAD
  406. DPFX(DPFPREP, 7, " TotalNumWakesWithoutWork = %u", pWorkQueue->dwTotalNumWakesWithoutWork);
  407. DPFX(DPFPREP, 7, " TotalNumContinuousWork = %u", pWorkQueue->dwTotalNumContinuousWork);
  408. DPFX(DPFPREP, 7, " TotalNumDoWorks = %u", pWorkQueue->dwTotalNumDoWorks);
  409. DPFX(DPFPREP, 7, " TotalNumDoWorksTimeLimit = %u", pWorkQueue->dwTotalNumDoWorksTimeLimit);
  410. DPFX(DPFPREP, 7, " TotalNumSimultaneousQueues = %u", pWorkQueue->dwTotalNumSimultaneousQueues);
  411. DPFX(DPFPREP, 7, " MaxRecursionCount = %u", dwMaxRecursionCount);
  412. #endif // DPNBUILD_THREADPOOLSTATISTICS
  413. #ifndef WINCE
  414. DeinitializeWorkQueueIoInfo(pWorkQueue);
  415. #endif // ! WINCE
  416. DeinitializeWorkQueueTimerInfo(pWorkQueue);
  417. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  418. fResult = DNCloseHandle(pWorkQueue->hIoCompletionPort);
  419. DNASSERT(fResult);
  420. pWorkQueue->hIoCompletionPort = NULL;
  421. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  422. fResult = DNCloseHandle(pWorkQueue->hAlertEvent);
  423. DNASSERT(fResult);
  424. pWorkQueue->hAlertEvent = NULL;
  425. DNASSERT(DNIsNBQueueEmpty(pWorkQueue->pvNBQueueWorkItems));
  426. DNDeinitializeNBQueueHead(pWorkQueue->pvNBQueueWorkItems);
  427. pWorkQueue->pvNBQueueWorkItems = NULL;
  428. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  429. #if ((! defined(WINCE)) || (defined(DBG)))
  430. DNDeleteCriticalSection(&pWorkQueue->csListLock);
  431. #endif // ! WINCE or DBG
  432. //
  433. // All of the NB queue nodes should be back in the pool, but there's no way
  434. // to tell if we have the correct amount.
  435. //
  436. DNASSERT(pWorkQueue->pWorkItemPool != NULL);
  437. pWorkQueue->pWorkItemPool->DeInitialize();
  438. DNFree(pWorkQueue->pWorkItemPool);
  439. pWorkQueue->pWorkItemPool = NULL;
  440. DPFX(DPFPREP, 6, "Leave");
  441. } // DeinitializeWorkQueue
  442. #undef DPF_MODNAME
  443. #define DPF_MODNAME "QueueWorkItem"
  444. //=============================================================================
  445. // QueueWorkItem
  446. //-----------------------------------------------------------------------------
  447. //
  448. // Description: Queues a new work item for processing.
  449. //
  450. // Arguments:
  451. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
  452. // PFNDPTNWORKCALLBACK pfnWorkCallback - Callback to execute as soon as
  453. // possible.
  454. // PVOID pvCallbackContext - User specified context to pass to
  455. // callback.
  456. //
  457. // Returns: BOOL
  458. // TRUE - Successfully queued the item.
  459. // FALSE - Failed to allocate memory for queueing the item.
  460. //=============================================================================
  461. BOOL QueueWorkItem(DPTPWORKQUEUE * const pWorkQueue,
  462. const PFNDPTNWORKCALLBACK pfnWorkCallback,
  463. PVOID const pvCallbackContext)
  464. {
  465. CWorkItem * pWorkItem;
  466. BOOL fResult;
  467. pWorkItem = (CWorkItem*) pWorkQueue->pWorkItemPool->Get(pWorkQueue);
  468. if (pWorkItem == NULL)
  469. {
  470. DPFX(DPFPREP, 0, "Couldn't get new work item from pool!");
  471. return FALSE;
  472. }
  473. DPFX(DPFPREP, 5, "Creating and queuing work item 0x%p (fn = 0x%p, context = 0x%p, queue = 0x%p).",
  474. pWorkItem, pfnWorkCallback, pvCallbackContext, pWorkQueue);
  475. pWorkItem->m_pfnWorkCallback = pfnWorkCallback;
  476. pWorkItem->m_pvCallbackContext = pvCallbackContext;
  477. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  478. pWorkItem->m_fCancelledOrCompleting = TRUE;
  479. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWorkItems));
  480. ThreadpoolStatsCreate(pWorkItem);
  481. ThreadpoolStatsQueue(pWorkItem);
  482. #endif // DPNBUILD_THREADPOOLSTATISTICS
  483. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  484. fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  485. 0,
  486. 0,
  487. &pWorkItem->m_Overlapped);
  488. if (! fResult)
  489. {
  490. #ifdef DBG
  491. DWORD dwError;
  492. dwError = GetLastError();
  493. DPFX(DPFPREP, 0, "Couldn't post queued completion status to port 0x%p (err = %u)!",
  494. pWorkQueue->hIoCompletionPort, dwError);
  495. #endif // DBG
  496. //
  497. // Careful, the item has been queued but it's possibly nobody knows...
  498. //
  499. }
  500. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  501. DNInsertTailNBQueue(pWorkQueue->pvNBQueueWorkItems,
  502. (ULONG64) pWorkItem);
  503. //
  504. // Alert the threads that there's a new item to process.
  505. //
  506. fResult = DNSetEvent(pWorkQueue->hAlertEvent);
  507. if (! fResult)
  508. {
  509. #ifdef DBG
  510. DWORD dwError;
  511. dwError = GetLastError();
  512. DPFX(DPFPREP, 0, "Couldn't set alert event 0x%p (err = %u)!",
  513. pWorkQueue->hAlertEvent, dwError);
  514. #endif // DBG
  515. //
  516. // Careful, the item has been queued but it's possibly nobody knows...
  517. //
  518. }
  519. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  520. return TRUE;
  521. } // QueueWorkItem
  522. #ifndef DPNBUILD_ONLYONETHREAD
  523. #undef DPF_MODNAME
  524. #define DPF_MODNAME "StartThreads"
  525. //=============================================================================
  526. // StartThreads
  527. //-----------------------------------------------------------------------------
  528. //
  529. // Description: Increases the number of threads for the work queue.
  530. //
  531. // It is assumed that only one thread will call this function
  532. // at a time, and no threads are currently being stopped.
  533. //
  534. // Arguments:
  535. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object.
  536. // DWORD dwNumThreads - Number of threads to start.
  537. //
  538. // Returns: HRESULT
  539. // DPN_OK - Increasing the number of threads was successful.
  540. // DPNERR_OUTOFMEMORY - Not enough memory to alter the number of threads.
  541. //=============================================================================
  542. HRESULT StartThreads(DPTPWORKQUEUE * const pWorkQueue,
  543. const DWORD dwNumThreads)
  544. {
  545. HRESULT hr = DPN_OK;
  546. DNHANDLE ahWaitObjects[MAX_SIMULTANEOUS_THREAD_START + 1];
  547. DWORD adwThreadID[MAX_SIMULTANEOUS_THREAD_START];
  548. DWORD dwNumThreadsExpected;
  549. DWORD dwTotalThreadsRemaining;
  550. DWORD dwTemp;
  551. DWORD dwResult;
  552. #ifdef DBG
  553. DWORD dwError;
  554. #endif // DBG
  555. DNASSERT(dwNumThreads > 0);
  556. //
  557. // Fill the entire array with NULL.
  558. //
  559. memset(ahWaitObjects, 0, sizeof(ahWaitObjects));
  560. //
  561. // Initialize the remaining count.
  562. //
  563. dwTotalThreadsRemaining = dwNumThreads;
  564. //
  565. // Create an event that will be set when an entire batch of threads has
  566. // successfully started.
  567. //
  568. ahWaitObjects[0] = DNCreateEvent(NULL, FALSE, FALSE, NULL);
  569. if (ahWaitObjects[0] == NULL)
  570. {
  571. #ifdef DBG
  572. dwError = GetLastError();
  573. DPFX(DPFPREP, 0, "Couldn't create event (err = %u)!", dwError);
  574. #endif // DBG
  575. hr = DPNERR_OUTOFMEMORY;
  576. goto Failure;
  577. }
  578. DNASSERT(pWorkQueue->hExpectedThreadsEvent == NULL);
  579. pWorkQueue->hExpectedThreadsEvent = ahWaitObjects[0];
  580. //
  581. // Keep adding batches of threads until we've started the requested amount.
  582. //
  583. while (dwTotalThreadsRemaining > 0)
  584. {
  585. //
  586. // Set the counter of how many threads we're starting in this batch.
  587. // WaitForSingleObjects can only handle a fixed amount of handles
  588. // at a time, so if we can't fit any more, we'll have to pick them
  589. // up again in the next loop.
  590. //
  591. //
  592. dwNumThreadsExpected = dwTotalThreadsRemaining;
  593. if (dwNumThreadsExpected > MAX_SIMULTANEOUS_THREAD_START)
  594. {
  595. dwNumThreadsExpected = MAX_SIMULTANEOUS_THREAD_START;
  596. }
  597. DNASSERT(pWorkQueue->dwNumThreadsExpected == 0);
  598. pWorkQueue->dwNumThreadsExpected = dwNumThreadsExpected;
  599. for(dwTemp = 1; dwTemp <= dwNumThreadsExpected; dwTemp++)
  600. {
  601. ahWaitObjects[dwTemp] = DNCreateThread(NULL,
  602. 0,
  603. DPTPWorkerThreadProc,
  604. pWorkQueue,
  605. 0,
  606. &adwThreadID[dwTemp - 1]);
  607. if (ahWaitObjects[dwTemp] == NULL)
  608. {
  609. #ifdef DBG
  610. dwError = GetLastError();
  611. DPFX(DPFPREP, 0, "Couldn't create thread (err = %u)!", dwError);
  612. #endif // DBG
  613. hr = DPNERR_OUTOFMEMORY;
  614. goto Failure;
  615. }
  616. dwTotalThreadsRemaining--;
  617. }
  618. //
  619. // Wait for either the successful start event or one of the threads to
  620. // die prematurely.
  621. //
  622. DPFX(DPFPREP, 4, "Waiting for %u threads for queue 0x%p to start.",
  623. dwNumThreadsExpected, pWorkQueue);
  624. dwResult = DNWaitForMultipleObjects((dwNumThreadsExpected + 1),
  625. ahWaitObjects,
  626. FALSE,
  627. INFINITE);
  628. if (dwResult != WAIT_OBJECT_0)
  629. {
  630. if ((dwResult > WAIT_OBJECT_0) &&
  631. (dwResult <= (WAIT_OBJECT_0 + (MAX_SIMULTANEOUS_THREAD_START - 1))))
  632. {
  633. #ifdef DBG
  634. dwResult -= WAIT_OBJECT_0;
  635. dwError = 0;
  636. GetExitCodeThread(ahWaitObjects[dwResult + 1], &dwError);
  637. DPFX(DPFPREP, 0, "Thread index %u (ID %u/0x%x shut down before starting successfully (err = %u)!",
  638. dwResult, adwThreadID[dwResult], adwThreadID[dwResult], dwError);
  639. #endif // DBG
  640. hr = DPNERR_OUTOFMEMORY;
  641. goto Failure;
  642. }
  643. #ifdef DBG
  644. dwError = GetLastError();
  645. DPFX(DPFPREP, 0, "Waiting for threads to start failed (err = %u)!",
  646. dwError);
  647. #endif // DBG
  648. hr = DPNERR_GENERIC;
  649. goto Failure;
  650. }
  651. //
  652. // All the threads we were expecting to start, did.
  653. //
  654. DPFX(DPFPREP, 4, "Successfully started %u threads for queue 0x%p.",
  655. dwNumThreadsExpected, pWorkQueue);
  656. //
  657. // Close the handles for those threads since we will never use them
  658. // again.
  659. //
  660. while (dwNumThreadsExpected > 0)
  661. {
  662. DNCloseHandle(ahWaitObjects[dwNumThreadsExpected]);
  663. ahWaitObjects[dwNumThreadsExpected] = NULL;
  664. dwNumThreadsExpected--;
  665. }
  666. } // end while (still more threads to add)
  667. DNCloseHandle(ahWaitObjects[0]);
  668. ahWaitObjects[0] = NULL;
  669. Exit:
  670. pWorkQueue->hExpectedThreadsEvent = NULL;
  671. return hr;
  672. Failure:
  673. for(dwTemp = 0; dwTemp <= MAX_SIMULTANEOUS_THREAD_START; dwTemp++)
  674. {
  675. if (ahWaitObjects[dwTemp] != NULL)
  676. {
  677. DNCloseHandle(ahWaitObjects[dwTemp]);
  678. ahWaitObjects[dwTemp] = NULL;
  679. }
  680. }
  681. //
  682. // Stop all threads that we successfully launched so that we end up back in
  683. // the same state as when we started. Ignore error because we're already
  684. // failing.
  685. //
  686. dwNumThreadsExpected = dwNumThreads - dwTotalThreadsRemaining;
  687. if (dwNumThreadsExpected > 0)
  688. {
  689. pWorkQueue->hExpectedThreadsEvent = NULL;
  690. StopThreads(pWorkQueue, dwNumThreadsExpected);
  691. }
  692. goto Exit;
  693. } // StartThreads
  694. #undef DPF_MODNAME
  695. #define DPF_MODNAME "StopThreads"
  696. //=============================================================================
  697. // StopThreads
  698. //-----------------------------------------------------------------------------
  699. //
  700. // Description: Decreases the number of threads for the work queue.
  701. //
  702. // It is assumed that only one thread will call this function
  703. // at a time, and no threads are currently being started.
  704. //
  705. // Arguments:
  706. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object.
  707. // DWORD dwNumThreads - Number of threads to stop.
  708. //
  709. // Returns: HRESULT
  710. // DPN_OK - Decreasing the number of threads was successful.
  711. // DPNERR_OUTOFMEMORY - Not enough memory to alter the number of threads.
  712. //=============================================================================
  713. HRESULT StopThreads(DPTPWORKQUEUE * const pWorkQueue,
  714. const DWORD dwNumThreads)
  715. {
  716. HRESULT hr;
  717. DWORD dwTemp;
  718. DWORD dwResult;
  719. DNASSERT(dwNumThreads > 0);
  720. //
  721. // Create an event that will be set when the desired number of threads has
  722. // started shutting down.
  723. //
  724. DNASSERT(pWorkQueue->hExpectedThreadsEvent == NULL);
  725. pWorkQueue->hExpectedThreadsEvent = DNCreateEvent(NULL, FALSE, FALSE, NULL);
  726. if (pWorkQueue->hExpectedThreadsEvent == NULL)
  727. {
  728. #ifdef DBG
  729. DWORD dwError;
  730. dwError = GetLastError();
  731. DPFX(DPFPREP, 0, "Couldn't create event (err = %u)!", dwError);
  732. #endif // DBG
  733. hr = DPNERR_OUTOFMEMORY;
  734. goto Failure;
  735. }
  736. DNASSERT(pWorkQueue->dwNumThreadsExpected == 0);
  737. pWorkQueue->dwNumThreadsExpected = dwNumThreads;
  738. for(dwTemp = 0; dwTemp < dwNumThreads; dwTemp++)
  739. {
  740. //
  741. // Queueing something with a NULL callback signifies "exit thread".
  742. //
  743. if (! QueueWorkItem(pWorkQueue, NULL, NULL))
  744. {
  745. DPFX(DPFPREP, 0, "Couldn't queue exit thread work item!");
  746. hr = DPNERR_OUTOFMEMORY;
  747. goto Failure;
  748. }
  749. } // end while (still more threads to remove)
  750. //
  751. // Wait for the last thread out to set the event.
  752. //
  753. DPFX(DPFPREP, 4, "Waiting for %u threads from queue 0x%p to stop.",
  754. dwNumThreads, pWorkQueue);
  755. dwResult = DNWaitForSingleObject(pWorkQueue->hExpectedThreadsEvent, INFINITE);
  756. DNASSERT(dwResult == WAIT_OBJECT_0);
  757. //
  758. // When the wait completes successfully, it means that the threads are on
  759. // their way out. It does *not* mean the threads have stopped completely.
  760. // We have to assume that the threads will have time to fully quit before
  761. // anything major happens, such as unloading this module.
  762. //
  763. hr = DPN_OK;
  764. Exit:
  765. if (pWorkQueue->hExpectedThreadsEvent != NULL)
  766. {
  767. DNCloseHandle(pWorkQueue->hExpectedThreadsEvent);
  768. pWorkQueue->hExpectedThreadsEvent = NULL;
  769. }
  770. return hr;
  771. Failure:
  772. goto Exit;
  773. } // StopThreads
  774. #endif // ! DPNBUILD_ONLYONETHREAD
  775. #undef DPF_MODNAME
  776. #define DPF_MODNAME "DoWork"
  777. //=============================================================================
  778. // DoWork
  779. //-----------------------------------------------------------------------------
  780. //
  781. // Description: Performs any work that is currently scheduled for the given
  782. // queue. If dwMaxDoWorkTime is not INFINITE, work is started up
  783. // until that time. At least one work item (if there is one) will
  784. // always be executed.
  785. //
  786. // Arguments:
  787. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
  788. // DWORD dwMaxDoWorkTime - Maximum time at which a new job can be
  789. // started, or INFINITE if all jobs should be
  790. // processed before returning.
  791. //
  792. // Returns: Nothing.
  793. //=============================================================================
  794. void DoWork(DPTPWORKQUEUE * const pWorkQueue,
  795. const DWORD dwMaxDoWorkTime)
  796. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  797. {
  798. BOOL fNeedToServiceTimers;
  799. CWorkItem * pWorkItem;
  800. BOOL fResult;
  801. DWORD dwBytesTransferred;
  802. DWORD dwCompletionKey;
  803. OVERLAPPED * pOverlapped;
  804. UINT uiOriginalUniqueID;
  805. DPFX(DPFPREP, 8, "Parameters: (0x%p, %i)", pWorkQueue, dwMaxDoWorkTime);
  806. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  807. //
  808. // Update the debugging/tuning statistics.
  809. //
  810. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorks));
  811. #endif // DPNBUILD_THREADPOOLSTATISTICS
  812. //
  813. // See if no one is processing timers.
  814. //
  815. fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  816. FALSE);
  817. //
  818. // If need be, handle any expired or cancelled timer entries.
  819. //
  820. if (fNeedToServiceTimers)
  821. {
  822. ProcessTimers(pWorkQueue);
  823. DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities because of DoWork.");
  824. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  825. //
  826. // Update the debugging/tuning statistics.
  827. //
  828. pWorkQueue->dwTotalNumTimerThreadAbdications++;
  829. #endif // DPNBUILD_THREADPOOLSTATISTICS
  830. DNASSERT(! pWorkQueue->fTimerThreadNeeded);
  831. #pragma TODO(vanceo, "Does this truly need to be interlocked?")
  832. fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  833. (LONG) TRUE);
  834. DNASSERT(! fNeedToServiceTimers); // there had better not be more than one timer thread
  835. }
  836. //
  837. // Keep looping until we run out of items to do. Note that this thread
  838. // does not try to count itself as busy. Either we are in DoWork mode
  839. // proper where the busy thread concept has no meaning, or we are a worker
  840. // thread processing a job that called WaitWhileWorking where
  841. // pWorkQueue->lNumBusyThreads would already have been incremented.
  842. //
  843. while ((GetQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  844. &dwBytesTransferred,
  845. &dwCompletionKey,
  846. &pOverlapped,
  847. 0)) ||
  848. (pOverlapped != NULL))
  849. {
  850. pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped);
  851. DNASSERT(pWorkItem->IsValid());
  852. //
  853. // Call the user's function or note that we may need to stop running.
  854. //
  855. if (pWorkItem->m_pfnWorkCallback != NULL)
  856. {
  857. //
  858. // Save the uniqueness ID to determine if this item was a timer
  859. // that got rescheduled.
  860. //
  861. uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
  862. DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p).",
  863. pWorkItem, pWorkItem->m_pfnWorkCallback,
  864. pWorkItem->m_pvCallbackContext, uiOriginalUniqueID,
  865. pWorkQueue);
  866. ThreadpoolStatsBeginExecuting(pWorkItem);
  867. pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext,
  868. pWorkItem,
  869. uiOriginalUniqueID);
  870. //
  871. // Return the item to the pool unless it got rescheduled. This
  872. // assumes that the actual pWorkItem memory remains valid even
  873. // though it may have been rescheduled and then completed/cancelled
  874. // by the time we perform this test. See CancelTimer.
  875. //
  876. if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID)
  877. {
  878. ThreadpoolStatsEndExecuting(pWorkItem);
  879. DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem);
  880. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  881. }
  882. else
  883. {
  884. ThreadpoolStatsEndExecutingRescheduled(pWorkItem);
  885. DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem);
  886. }
  887. }
  888. else
  889. {
  890. DPFX(DPFPREP, 3, "Requeuing exit thread work item 0x%p for other threads.",
  891. pWorkItem);
  892. fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  893. 0,
  894. 0,
  895. &pWorkItem->m_Overlapped);
  896. DNASSERT(fResult);
  897. #pragma BUGBUG(vanceo, "May not have processed everything we want")
  898. break;
  899. }
  900. //
  901. // Make sure we haven't exceeded our time limit (if we have one).
  902. //
  903. if ((dwMaxDoWorkTime != INFINITE) &&
  904. ((int) (dwMaxDoWorkTime - GETTIMESTAMP()) < 0))
  905. {
  906. DPFX(DPFPREP, 5, "Exceeded time limit, not processing any more work.");
  907. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  908. //
  909. // Update the debugging/tuning statistics.
  910. //
  911. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorksTimeLimit));
  912. #endif // DPNBUILD_THREADPOOLSTATISTICS
  913. break;
  914. }
  915. }
  916. DPFX(DPFPREP, 8, "Leave");
  917. } // DoWork
  918. //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  919. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  920. //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  921. {
  922. DNSLIST_ENTRY * pSlistEntryHead = NULL;
  923. USHORT usCount = 0;
  924. DNSLIST_ENTRY * pSlistEntryTail;
  925. CWorkItem * pWorkItem;
  926. UINT uiOriginalUniqueID;
  927. #ifndef DPNBUILD_ONLYONETHREAD
  928. BOOL fNeedToServiceTimers;
  929. #endif // ! DPNBUILD_ONLYONETHREAD
  930. DPFX(DPFPREP, 8, "Parameters: (0x%p, %i)", pWorkQueue, dwMaxDoWorkTime);
  931. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  932. //
  933. // Update the debugging/tuning statistics.
  934. //
  935. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorks));
  936. #endif // DPNBUILD_THREADPOOLSTATISTICS
  937. #ifndef WINCE
  938. //
  939. // Handle any I/O completions.
  940. //
  941. ProcessIo(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount);
  942. #endif // ! WINCE
  943. #ifndef DPNBUILD_ONLYONETHREAD
  944. //
  945. // See if no one is processing timers.
  946. //
  947. fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  948. FALSE);
  949. //
  950. // If need be, handle any expired or cancelled timer entries.
  951. //
  952. if (fNeedToServiceTimers)
  953. #endif // ! DPNBUILD_ONLYONETHREAD
  954. {
  955. ProcessTimers(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount);
  956. }
  957. //
  958. // Queue any work items we accumulated in one fell swoop.
  959. //
  960. if (pSlistEntryHead != NULL)
  961. {
  962. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  963. if (usCount > 1)
  964. {
  965. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumSimultaneousQueues));
  966. }
  967. #ifdef WINCE
  968. LONG lCount;
  969. lCount = usCount;
  970. while (lCount > 0)
  971. {
  972. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWorkItems));
  973. lCount--;
  974. }
  975. #else // ! WINCE
  976. DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalNumWorkItems), usCount);
  977. #endif // ! WINCE
  978. #endif // DPNBUILD_THREADPOOLSTATISTICS
  979. DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems,
  980. pSlistEntryHead,
  981. OFFSETOF(CWorkItem, m_SlistEntry));
  982. }
  983. #ifndef DPNBUILD_ONLYONETHREAD
  984. //
  985. // In case other threads are running, one of them should become a timer
  986. // thread. We will need to kick them via the alert event so that they
  987. // notice.
  988. //
  989. if (fNeedToServiceTimers)
  990. {
  991. DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities because of DoWork.");
  992. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  993. //
  994. // Update the debugging/tuning statistics.
  995. //
  996. pWorkQueue->dwTotalNumTimerThreadAbdications++;
  997. #endif // DPNBUILD_THREADPOOLSTATISTICS
  998. DNASSERT(! pWorkQueue->fTimerThreadNeeded);
  999. #pragma TODO(vanceo, "Does this truly need to be interlocked?")
  1000. fNeedToServiceTimers = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  1001. (LONG) TRUE);
  1002. DNASSERT(! fNeedToServiceTimers); // there had better not be more than one timer thread
  1003. DNSetEvent(pWorkQueue->hAlertEvent);
  1004. }
  1005. //
  1006. // Reset the tracking variables.
  1007. //
  1008. pSlistEntryHead = NULL;
  1009. usCount = 0;
  1010. #endif // ! DPNBUILD_ONLYONETHREAD
  1011. //
  1012. // Keep looping until we run out of items to do. Note that this thread
  1013. // does not try to count itself as busy. Either we are in DoWork mode
  1014. // proper where the busy thread concept has no meaning, or we are a worker
  1015. // thread processing a job that called WaitWhileWorking where
  1016. // pWorkQueue->lNumBusyThreads would already have been incremented.
  1017. //
  1018. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  1019. while (pWorkItem != NULL)
  1020. {
  1021. //
  1022. // Call the user's function or note that we need to stop running.
  1023. //
  1024. if (pWorkItem->m_pfnWorkCallback != NULL)
  1025. {
  1026. //
  1027. // Save the uniqueness ID to determine if this item was a timer
  1028. // that got rescheduled.
  1029. //
  1030. uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
  1031. DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p).",
  1032. pWorkItem, pWorkItem->m_pfnWorkCallback,
  1033. pWorkItem->m_pvCallbackContext, uiOriginalUniqueID,
  1034. pWorkQueue);
  1035. ThreadpoolStatsBeginExecuting(pWorkItem);
  1036. pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext,
  1037. pWorkItem,
  1038. uiOriginalUniqueID);
  1039. //
  1040. // Return the item to the pool unless it got rescheduled. This
  1041. // assumes that the actual pWorkItem memory remains valid even
  1042. // though it may have been rescheduled and then completed/cancelled
  1043. // by the time we perform this test. See CancelTimer.
  1044. //
  1045. if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID)
  1046. {
  1047. ThreadpoolStatsEndExecuting(pWorkItem);
  1048. DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem);
  1049. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  1050. }
  1051. else
  1052. {
  1053. ThreadpoolStatsEndExecutingRescheduled(pWorkItem);
  1054. DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem);
  1055. }
  1056. }
  1057. else
  1058. {
  1059. DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p.", pWorkItem);
  1060. //
  1061. // This thread shouldn't get told to exit when either in DoWork
  1062. // mode proper, or while performing work while waiting. However,
  1063. // it is possible in the latter case to pick up a quit event
  1064. // "intended" (in a sense) for another thread. We need to resubmit
  1065. // the exit thread request so that another thread that can process
  1066. // it, will.
  1067. //
  1068. // If we just put it back on the queue now, our while loop would
  1069. // probably just pull it off again getting us nowhere. Instead, we
  1070. // will save up all of the exit requests we mistakenly receive and
  1071. // dump them all back on the queue once we've run out of things to
  1072. // do.
  1073. //
  1074. #ifdef DPNBUILD_ONLYONETHREAD
  1075. DNASSERT(FALSE);
  1076. #else // ! DPNBUILD_ONLYONETHREAD
  1077. DNASSERT(pWorkQueue->dwNumRunningThreads > 1);
  1078. if (pSlistEntryHead == NULL)
  1079. {
  1080. pSlistEntryTail = pSlistEntryHead;
  1081. }
  1082. pWorkItem->m_SlistEntry.Next = pSlistEntryHead;
  1083. pSlistEntryHead = &pWorkItem->m_SlistEntry;
  1084. usCount++;
  1085. #endif // ! DPNBUILD_ONLYONETHREAD
  1086. }
  1087. //
  1088. // Make sure we haven't exceeded our time limit (if we have one).
  1089. //
  1090. if ((dwMaxDoWorkTime != INFINITE) &&
  1091. ((int) (dwMaxDoWorkTime - GETTIMESTAMP()) < 0))
  1092. {
  1093. DPFX(DPFPREP, 5, "Exceeded time limit, not processing any more work.");
  1094. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  1095. //
  1096. // Update the debugging/tuning statistics.
  1097. //
  1098. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumDoWorksTimeLimit));
  1099. #endif // DPNBUILD_THREADPOOLSTATISTICS
  1100. break;
  1101. }
  1102. //
  1103. // Try to get the next work item.
  1104. //
  1105. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  1106. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  1107. if (pWorkItem != NULL)
  1108. {
  1109. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumContinuousWork));
  1110. }
  1111. #endif // DPNBUILD_THREADPOOLSTATISTICS
  1112. } // end while (more items)
  1113. #ifndef DPNBUILD_ONLYONETHREAD
  1114. //
  1115. // Re-queue any exit thread work items we accumulated.
  1116. //
  1117. if (pSlistEntryHead != NULL)
  1118. {
  1119. DPFX(DPFPREP, 1, "Re-queuing %u exit thread work items for queue 0x%p.",
  1120. usCount, pWorkQueue);
  1121. DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems,
  1122. pSlistEntryHead,
  1123. OFFSETOF(CWorkItem, m_SlistEntry));
  1124. }
  1125. else
  1126. {
  1127. DNASSERT(usCount == 0);
  1128. }
  1129. #endif // ! DPNBUILD_ONLYONETHREAD
  1130. DPFX(DPFPREP, 8, "Leave");
  1131. } // DoWork
  1132. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  1133. #ifndef DPNBUILD_ONLYONETHREAD
  1134. #undef DPF_MODNAME
  1135. #define DPF_MODNAME "DPTPWorkerThreadProc"
  1136. //=============================================================================
  1137. // DPTPWorkerThreadProc
  1138. //-----------------------------------------------------------------------------
  1139. //
  1140. // Description: The standard worker thread function for executing work
  1141. // items.
  1142. //
  1143. // Arguments:
  1144. // PVOID pvParameter - Pointer to thread parameter data.
  1145. //
  1146. // Returns: DWORD
  1147. //=============================================================================
  1148. DWORD WINAPI DPTPWorkerThreadProc(PVOID pvParameter)
  1149. {
  1150. DPTPWORKQUEUE * pWorkQueue = (DPTPWORKQUEUE*) pvParameter;
  1151. DPTPWORKERTHREAD WorkerThread;
  1152. BOOL fUninitializeCOM = TRUE;
  1153. PFNDPNMESSAGEHANDLER pfnMsgHandler;
  1154. PVOID pvMsgHandlerContext;
  1155. PVOID pvUserThreadContext;
  1156. HRESULT hr;
  1157. DWORD dwResult;
  1158. #ifndef DPNBUILD_ONLYONEPROCESSOR
  1159. #if ((! defined(DPNBUILD_SOFTTHREADAFFINITY)) && (! defined(DPNBUILD_USEIOCOMPLETIONPORTS)))
  1160. DWORD_PTR dwpAffinityMask;
  1161. #endif // ! DPNBUILD_SOFTTHREADAFFINITY and ! DPNBUILD_USEIOCOMPLETIONPORTS
  1162. #ifdef DBG
  1163. SYSTEM_INFO SystemInfo;
  1164. #endif // DBG
  1165. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  1166. DPFX(DPFPREP, 6, "Parameters: (0x%p)", pvParameter);
  1167. #ifndef DPNBUILD_ONLYONEPROCESSOR
  1168. //
  1169. // Bind to a specific CPU. First, assert that the CPU number is valid in
  1170. // debug builds.
  1171. //
  1172. #ifdef DBG
  1173. GetSystemInfo(&SystemInfo);
  1174. DNASSERT(pWorkQueue->dwCPUNum < SystemInfo.dwNumberOfProcessors);
  1175. #endif // DBG
  1176. #ifndef DPNBUILD_USEIOCOMPLETIONPORTS
  1177. SetThreadIdealProcessor(GetCurrentThread(), pWorkQueue->dwCPUNum);
  1178. #ifndef DPNBUILD_SOFTTHREADAFFINITY
  1179. DNASSERT(pWorkQueue->dwCPUNum < (sizeof(dwpAffinityMask) * 8));
  1180. dwpAffinityMask = 1 << pWorkQueue->dwCPUNum;
  1181. SetThreadAffinityMask(GetCurrentThread(), dwpAffinityMask);
  1182. #endif // ! DPNBUILD_SOFTTHREADAFFINITY
  1183. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  1184. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  1185. //
  1186. // Boost the thread priority.
  1187. //
  1188. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
  1189. //
  1190. // Init COM.
  1191. //
  1192. hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  1193. if (FAILED(hr))
  1194. {
  1195. DPFX(DPFPREP, 0, "Failed to initialize COM (err = 0x%lx)! Continuing.", hr);
  1196. fUninitializeCOM = FALSE;
  1197. //
  1198. // Continue...
  1199. //
  1200. }
  1201. //
  1202. // Initialize the worker thread data.
  1203. //
  1204. memset(&WorkerThread, 0, sizeof(WorkerThread));
  1205. WorkerThread.Sig[0] = 'W';
  1206. WorkerThread.Sig[1] = 'K';
  1207. WorkerThread.Sig[2] = 'T';
  1208. WorkerThread.Sig[3] = 'D';
  1209. WorkerThread.pWorkQueue = pWorkQueue;
  1210. #ifdef DBG
  1211. WorkerThread.dwThreadID = GetCurrentThreadId();
  1212. WorkerThread.blList.Initialize();
  1213. DNEnterCriticalSection(&pWorkQueue->csListLock);
  1214. WorkerThread.blList.InsertBefore(&pWorkQueue->blThreadList);
  1215. DNLeaveCriticalSection(&pWorkQueue->csListLock);
  1216. #endif // DBG
  1217. //
  1218. // Save the worker thread data.
  1219. //
  1220. TlsSetValue(pWorkQueue->dwWorkerThreadTlsIndex, &WorkerThread);
  1221. dwResult = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumRunningThreads));
  1222. DPFX(DPFPREP, 7, "Thread %u/0x%x from queue 0x%p started, num running threads is now %u.",
  1223. GetCurrentThreadId(), GetCurrentThreadId(), pWorkQueue, dwResult);
  1224. //
  1225. // Save the current user message handler. If there is one, call it now
  1226. // with thread initialization information.
  1227. //
  1228. pfnMsgHandler = pWorkQueue->pfnMsgHandler;
  1229. if (pfnMsgHandler != NULL)
  1230. {
  1231. DPNMSG_CREATE_THREAD MsgCreateThread;
  1232. //
  1233. // Save the message handler context.
  1234. //
  1235. pvMsgHandlerContext = pWorkQueue->pvMsgHandlerContext;
  1236. //
  1237. // Call the user's message handler with a CREATE_THREAD message.
  1238. //
  1239. MsgCreateThread.dwSize = sizeof(MsgCreateThread);
  1240. MsgCreateThread.dwFlags = 0;
  1241. #ifdef DPNBUILD_ONLYONEPROCESSOR
  1242. MsgCreateThread.dwProcessorNum = 0;
  1243. #else // ! DPNBUILD_ONLYONEPROCESSOR
  1244. MsgCreateThread.dwProcessorNum = pWorkQueue->dwCPUNum;
  1245. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  1246. MsgCreateThread.pvUserContext = NULL;
  1247. hr = pfnMsgHandler(pvMsgHandlerContext,
  1248. DPN_MSGID_CREATE_THREAD,
  1249. &MsgCreateThread);
  1250. #ifdef DBG
  1251. if (hr != DPN_OK)
  1252. {
  1253. DPFX(DPFPREP, 0, "User returned error 0x%08x from CREATE_THREAD indication!",
  1254. 1, hr);
  1255. }
  1256. #endif // DBG
  1257. //
  1258. // Save what the user returned for a thread context.
  1259. //
  1260. pvUserThreadContext = MsgCreateThread.pvUserContext;
  1261. }
  1262. //
  1263. // The user (if any) now knows about the thread.
  1264. //
  1265. WorkerThread.fThreadIndicated = TRUE;
  1266. DNASSERT(pWorkQueue->dwNumThreadsExpected > 0);
  1267. dwResult = DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumThreadsExpected));
  1268. if (dwResult == 0)
  1269. {
  1270. DPFX(DPFPREP, 9, "All threads expected to start have, setting event.");
  1271. DNASSERT(pWorkQueue->hExpectedThreadsEvent != NULL);
  1272. DNSetEvent(pWorkQueue->hExpectedThreadsEvent); // ignore error
  1273. }
  1274. else
  1275. {
  1276. DPFX(DPFPREP, 9, "Number of threads expected to start is now %u.", dwResult);
  1277. }
  1278. //
  1279. // Perform the work loop.
  1280. //
  1281. DPTPWorkerLoop(pWorkQueue);
  1282. //
  1283. // The user (if any) is about to be told about the thread's destruction.
  1284. //
  1285. WorkerThread.fThreadIndicated = FALSE;
  1286. //
  1287. // If there was a user message handler, call it now with thread shutdown
  1288. // information.
  1289. //
  1290. if (pfnMsgHandler != NULL)
  1291. {
  1292. DPNMSG_DESTROY_THREAD MsgDestroyThread;
  1293. //
  1294. // Call the user's message handler with a DESTROY_THREAD message.
  1295. //
  1296. MsgDestroyThread.dwSize = sizeof(MsgDestroyThread);
  1297. #ifdef DPNBUILD_ONLYONEPROCESSOR
  1298. MsgDestroyThread.dwProcessorNum = 0;
  1299. #else // ! DPNBUILD_ONLYONEPROCESSOR
  1300. MsgDestroyThread.dwProcessorNum = pWorkQueue->dwCPUNum;
  1301. #endif // ! DPNBUILD_ONLYONEPROCESSOR
  1302. MsgDestroyThread.pvUserContext = pvUserThreadContext;
  1303. hr = pfnMsgHandler(pvMsgHandlerContext,
  1304. DPN_MSGID_DESTROY_THREAD,
  1305. &MsgDestroyThread);
  1306. #ifdef DBG
  1307. if (hr != DPN_OK)
  1308. {
  1309. DPFX(DPFPREP, 0, "User returned error 0x%08x from DESTROY_THREAD indication!",
  1310. 1, hr);
  1311. }
  1312. #endif // DBG
  1313. pfnMsgHandler = NULL;
  1314. pvMsgHandlerContext = NULL;
  1315. pvUserThreadContext = NULL;
  1316. }
  1317. #ifdef DBG
  1318. DNEnterCriticalSection(&pWorkQueue->csListLock);
  1319. WorkerThread.blList.RemoveFromList();
  1320. DNLeaveCriticalSection(&pWorkQueue->csListLock);
  1321. DNASSERT(WorkerThread.dwRecursionCount == 0);
  1322. DNASSERT(TlsGetValue(pWorkQueue->dwWorkerThreadTlsIndex) == &WorkerThread);
  1323. #endif // DBG
  1324. DNASSERT(pWorkQueue->dwNumRunningThreads > 0);
  1325. dwResult = DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumRunningThreads));
  1326. DPFX(DPFPREP, 7, "Thread %u/0x%x from queue 0x%p done, num running threads is now %u.",
  1327. GetCurrentThreadId(), GetCurrentThreadId(), pWorkQueue, dwResult);
  1328. #ifndef WINCE
  1329. CancelIoForThisThread(pWorkQueue);
  1330. #endif // ! WINCE
  1331. if (fUninitializeCOM)
  1332. {
  1333. CoUninitialize();
  1334. fUninitializeCOM = FALSE;
  1335. }
  1336. DNASSERT(pWorkQueue->dwNumThreadsExpected > 0);
  1337. dwResult = DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumThreadsExpected));
  1338. if (dwResult == 0)
  1339. {
  1340. DPFX(DPFPREP, 9, "All threads expected to stop have, setting event.");
  1341. DNASSERT(pWorkQueue->hExpectedThreadsEvent != NULL);
  1342. DNSetEvent(pWorkQueue->hExpectedThreadsEvent); // ignore error
  1343. }
  1344. else
  1345. {
  1346. DPFX(DPFPREP, 9, "Number of threads expected to stop is now %u.", dwResult);
  1347. }
  1348. //
  1349. // Since we have decremented dwNumRunningThreads and possibly set the
  1350. // event, any other threads waiting on that will think we are gone.
  1351. // Therefore, we cannot use pWorkQueue after this as it may have been
  1352. // deallocated. We must also endeavor to do as little work as possible
  1353. // because there is a race condition where the module in which this code
  1354. // resides could be unloaded.
  1355. //
  1356. DPFX(DPFPREP, 6, "Leave");
  1357. return 0;
  1358. } // DPTPWorkerThreadProc
  1359. #ifdef DPNBUILD_MANDATORYTHREADS
  1360. #undef DPF_MODNAME
  1361. #define DPF_MODNAME "DPTPMandatoryThreadProc"
  1362. //=============================================================================
  1363. // DPTPMandatoryThreadProc
  1364. //-----------------------------------------------------------------------------
  1365. //
  1366. // Description: The standard worker thread function for executing work
  1367. // items.
  1368. //
  1369. // Arguments:
  1370. // PVOID pvParameter - Pointer to thread parameter data.
  1371. //
  1372. // Returns: DWORD
  1373. //=============================================================================
  1374. DWORD WINAPI DPTPMandatoryThreadProc(PVOID pvParameter)
  1375. {
  1376. DPTPMANDATORYTHREAD * pMandatoryThread = (DPTPMANDATORYTHREAD*) pvParameter;
  1377. DPTHREADPOOLOBJECT * pDPTPObject;
  1378. PFNDPNMESSAGEHANDLER pfnMsgHandler;
  1379. PVOID pvMsgHandlerContext;
  1380. PVOID pvUserThreadContext;
  1381. HRESULT hr;
  1382. DWORD dwResult;
  1383. DPFX(DPFPREP, 6, "Parameters: (0x%p)", pvParameter);
  1384. //
  1385. // Save a copy of the local object.
  1386. //
  1387. pDPTPObject = pMandatoryThread->pDPTPObject;
  1388. #ifdef DBG
  1389. //
  1390. // Store the thread ID.
  1391. //
  1392. pMandatoryThread->dwThreadID = GetCurrentThreadId();
  1393. #endif // DBG
  1394. //
  1395. // Take the lock, and then ensure we aren't in DoWork mode.
  1396. //
  1397. DNEnterCriticalSection(&pDPTPObject->csLock);
  1398. if (pDPTPObject->dwTotalUserThreadCount == 0)
  1399. {
  1400. //
  1401. // Bail out of this function. The thread starting us still owns the
  1402. // pMandatoryThread memory and will handle this failure correctly.
  1403. //
  1404. DNLeaveCriticalSection(&pDPTPObject->csLock);
  1405. return DPNERR_NOTALLOWED;
  1406. }
  1407. //
  1408. // Increment the thread count. We use interlocked increment because we can
  1409. // touch the counter outside of the lock below.
  1410. //
  1411. DNASSERT(pDPTPObject->dwMandatoryThreadCount != -1);
  1412. DNInterlockedIncrement((LPLONG) (&pDPTPObject->dwMandatoryThreadCount));
  1413. #ifdef DBG
  1414. //
  1415. // Add this thread to the list of tracked threads.
  1416. //
  1417. pMandatoryThread->blList.InsertBefore(&pDPTPObject->blMandatoryThreads);
  1418. #endif // DBG
  1419. DNLeaveCriticalSection(&pDPTPObject->csLock);
  1420. //
  1421. // Save the current user message handler. If there is one, call it now
  1422. // with thread initialization information.
  1423. //
  1424. pfnMsgHandler = pMandatoryThread->pfnMsgHandler;
  1425. if (pfnMsgHandler != NULL)
  1426. {
  1427. DPNMSG_CREATE_THREAD MsgCreateThread;
  1428. //
  1429. // Save the message handler context.
  1430. //
  1431. pvMsgHandlerContext = pMandatoryThread->pvMsgHandlerContext;
  1432. //
  1433. // Call the user's message handler with a CREATE_THREAD message.
  1434. //
  1435. MsgCreateThread.dwSize = sizeof(MsgCreateThread);
  1436. MsgCreateThread.dwFlags = DPNTHREAD_MANDATORY;
  1437. MsgCreateThread.dwProcessorNum = -1;
  1438. MsgCreateThread.pvUserContext = NULL;
  1439. hr = pfnMsgHandler(pvMsgHandlerContext,
  1440. DPN_MSGID_CREATE_THREAD,
  1441. &MsgCreateThread);
  1442. #ifdef DBG
  1443. if (hr != DPN_OK)
  1444. {
  1445. DPFX(DPFPREP, 0, "User returned error 0x%08x from CREATE_THREAD indication!",
  1446. 1, hr);
  1447. }
  1448. #endif // DBG
  1449. //
  1450. // Save what the user returned for a thread context.
  1451. //
  1452. pvUserThreadContext = MsgCreateThread.pvUserContext;
  1453. }
  1454. //
  1455. // Alert the creator thread. After this call, we own the pMandatoryThread
  1456. // memory.
  1457. //
  1458. DPFX(DPFPREP, 9, "Thread created successfully, setting event.");
  1459. DNASSERT(pMandatoryThread->hStartedEvent != NULL);
  1460. DNSetEvent(pMandatoryThread->hStartedEvent); // ignore error
  1461. //
  1462. // Call the user's thread proc function.
  1463. //
  1464. DPFX(DPFPREP, 2, "Calling user thread function 0x%p with parameter 0x%p.",
  1465. pMandatoryThread->lpStartAddress, pMandatoryThread->lpParameter);
  1466. dwResult = pMandatoryThread->lpStartAddress(pMandatoryThread->lpParameter);
  1467. DPFX(DPFPREP, 2, "Returning from user thread with result %u/0x%x.",
  1468. dwResult, dwResult);
  1469. //
  1470. // If there was a user message handler, call it now with thread shutdown
  1471. // information.
  1472. //
  1473. if (pfnMsgHandler != NULL)
  1474. {
  1475. DPNMSG_DESTROY_THREAD MsgDestroyThread;
  1476. //
  1477. // Call the user's message handler with a DESTROY_THREAD message.
  1478. //
  1479. MsgDestroyThread.dwSize = sizeof(MsgDestroyThread);
  1480. MsgDestroyThread.dwProcessorNum = -1;
  1481. MsgDestroyThread.pvUserContext = pvUserThreadContext;
  1482. hr = pfnMsgHandler(pvMsgHandlerContext,
  1483. DPN_MSGID_DESTROY_THREAD,
  1484. &MsgDestroyThread);
  1485. #ifdef DBG
  1486. if (hr != DPN_OK)
  1487. {
  1488. DPFX(DPFPREP, 0, "User returned error 0x%08x from DESTROY_THREAD indication!",
  1489. 1, hr);
  1490. }
  1491. #endif // DBG
  1492. pfnMsgHandler = NULL;
  1493. pvMsgHandlerContext = NULL;
  1494. pvUserThreadContext = NULL;
  1495. }
  1496. //
  1497. // Assert that the thread pool object is still valid.
  1498. //
  1499. DNASSERT((pDPTPObject->Sig[0] == 'D') && (pDPTPObject->Sig[1] == 'P') && (pDPTPObject->Sig[2] == 'T') && (pDPTPObject->Sig[3] == 'P'));
  1500. #ifdef DBG
  1501. //
  1502. // Remove this thread from the list of tracked threads.
  1503. //
  1504. DNEnterCriticalSection(&pDPTPObject->csLock);
  1505. pMandatoryThread->blList.RemoveFromList();
  1506. DNLeaveCriticalSection(&pDPTPObject->csLock);
  1507. #endif // DBG
  1508. //
  1509. // Release the object resources.
  1510. //
  1511. DNFree(pMandatoryThread);
  1512. pMandatoryThread = NULL;
  1513. DPFX(DPFPREP, 6, "Leave (mandatory thread count was approximately %u)",
  1514. pDPTPObject->dwMandatoryThreadCount);
  1515. //
  1516. // Signal to the object that this thread is gone. There is a race
  1517. // condition because we've set the counter, but there are a non-zero number
  1518. // of instructions we still have to execute before this thread is truly
  1519. // gone. COM has a similar race condition, and if the live with it, so can
  1520. // we. Because we want to minimize the number of instructions afterward,
  1521. // note that we use interlocked decrement outside of the critical section.
  1522. //
  1523. DNASSERT(pDPTPObject->dwMandatoryThreadCount > 0);
  1524. DNInterlockedDecrement((LPLONG) (&pDPTPObject->dwMandatoryThreadCount));
  1525. return dwResult;
  1526. } // DPTPMandatoryThreadProc
  1527. #endif // DPNBUILD_MANDATORYTHREADS
  1528. #undef DPF_MODNAME
  1529. #define DPF_MODNAME "DPTPWorkerLoop"
  1530. //=============================================================================
  1531. // DPTPWorkerLoop
  1532. //-----------------------------------------------------------------------------
  1533. //
  1534. // Description: The worker thread looping function for executing work items.
  1535. //
  1536. // Arguments:
  1537. // DPTPWORKQUEUE * pWorkQueue - Pointer to work queue object to use.
  1538. //
  1539. // Returns: None.
  1540. //=============================================================================
  1541. void DPTPWorkerLoop(DPTPWORKQUEUE * const pWorkQueue)
  1542. #ifdef DPNBUILD_USEIOCOMPLETIONPORTS
  1543. {
  1544. BOOL fShouldRun = TRUE;
  1545. BOOL fRunningAsTimerThread = FALSE;
  1546. DWORD dwBytesTransferred;
  1547. DWORD dwCompletionKey;
  1548. OVERLAPPED * pOverlapped;
  1549. CWorkItem * pWorkItem;
  1550. DWORD dwNumBusyThreads;
  1551. DWORD dwNumRunningThreads;
  1552. BOOL fResult;
  1553. UINT uiOriginalUniqueID;
  1554. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1555. DWORD dwStartTime;
  1556. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1557. DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
  1558. //
  1559. // Keep looping until we're told to exit.
  1560. //
  1561. while (fShouldRun)
  1562. {
  1563. //
  1564. // If we're not already running as a timer thread, atomically see if
  1565. // there is currently a timer thread, and become it if not.
  1566. // There can be only one.
  1567. //
  1568. if (! fRunningAsTimerThread)
  1569. {
  1570. fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  1571. (LONG) FALSE);
  1572. #ifdef DBG
  1573. if (fRunningAsTimerThread)
  1574. {
  1575. DPFX(DPFPREP, 9, "Becoming timer thread.");
  1576. }
  1577. #endif // DBG
  1578. }
  1579. //
  1580. // If we're the timer thread, wake up periodically to service timers by
  1581. // waiting on a waitable timer object. Otherwise, wait for a queue
  1582. // item.
  1583. //
  1584. if (fRunningAsTimerThread)
  1585. {
  1586. DWORD dwResult;
  1587. //DPFX(DPFPREP, 9, "Waiting on timer handle 0x%p.", pWorkQueue->hTimer);
  1588. dwResult = DNWaitForSingleObject(pWorkQueue->hTimer, INFINITE);
  1589. DNASSERT(dwResult == WAIT_OBJECT_0);
  1590. //
  1591. // Handle any expired or cancelled timer entries.
  1592. //
  1593. ProcessTimers(pWorkQueue);
  1594. //
  1595. // Increase the number of busy threads.
  1596. //
  1597. dwNumBusyThreads = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
  1598. //
  1599. // Grab the current total thread count.
  1600. //
  1601. dwNumRunningThreads = *((volatile DWORD *) (&pWorkQueue->dwNumRunningThreads));
  1602. //
  1603. // If there are other threads, we won't attempt to process work
  1604. // unless they're all busy.
  1605. //
  1606. if ((dwNumRunningThreads == 1) || (dwNumBusyThreads == dwNumRunningThreads))
  1607. {
  1608. #ifdef DBG
  1609. if (dwNumRunningThreads > 1) // reduce spew when only running 1 thread
  1610. {
  1611. DPFX(DPFPREP, 9, "No idle threads (busy = %u), may become worker thread.",
  1612. dwNumBusyThreads);
  1613. }
  1614. #endif // DBG
  1615. #pragma TODO(vanceo, "Optimize single thread case (right now we only process one item per timer bucket)")
  1616. //
  1617. // See if there's any work to do at the moment.
  1618. //
  1619. fResult = GetQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  1620. &dwBytesTransferred,
  1621. &dwCompletionKey,
  1622. &pOverlapped,
  1623. 0);
  1624. if ((fResult) || (pOverlapped != NULL))
  1625. {
  1626. pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped);
  1627. DNASSERT(pWorkItem->IsValid());
  1628. DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities.");
  1629. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  1630. //
  1631. // Update the debugging/tuning statistics.
  1632. //
  1633. pWorkQueue->dwTotalNumTimerThreadAbdications++;
  1634. #endif // DPNBUILD_THREADPOOLSTATISTICS
  1635. DNASSERT(! pWorkQueue->fTimerThreadNeeded);
  1636. #pragma TODO(vanceo, "Does this truly need to be interlocked?")
  1637. fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  1638. (LONG) TRUE);
  1639. DNASSERT(! fRunningAsTimerThread); // there had better not be more than one timer thread
  1640. //
  1641. // Call the user's function or note that we may need to
  1642. // stop running.
  1643. //
  1644. if (pWorkItem->m_pfnWorkCallback != NULL)
  1645. {
  1646. //
  1647. // Save the uniqueness ID to determine if this item was
  1648. // a timer that got rescheduled.
  1649. //
  1650. uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
  1651. DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p) as previous timer thread.",
  1652. pWorkItem, pWorkItem->m_pfnWorkCallback,
  1653. pWorkItem->m_pvCallbackContext, uiOriginalUniqueID,
  1654. pWorkQueue);
  1655. ThreadpoolStatsBeginExecuting(pWorkItem);
  1656. pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext,
  1657. pWorkItem,
  1658. uiOriginalUniqueID);
  1659. //
  1660. // Return the item to the pool unless it got
  1661. // rescheduled. This assumes that the actual pWorkItem
  1662. // memory remains valid even though it may have been
  1663. // rescheduled and then completed/cancelled by the time
  1664. // we perform this test. See CancelTimer.
  1665. //
  1666. if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID)
  1667. {
  1668. ThreadpoolStatsEndExecuting(pWorkItem);
  1669. DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem);
  1670. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  1671. }
  1672. else
  1673. {
  1674. ThreadpoolStatsEndExecutingRescheduled(pWorkItem);
  1675. DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem);
  1676. }
  1677. }
  1678. else
  1679. {
  1680. //
  1681. // If there are any other threads, requeue the kill
  1682. // thread work item, otherwise, quit.
  1683. //
  1684. if (dwNumRunningThreads > 1)
  1685. {
  1686. DPFX(DPFPREP, 3, "Requeuing exit thread work item 0x%p for other threads.",
  1687. pWorkItem);
  1688. fResult = PostQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  1689. 0,
  1690. 0,
  1691. &pWorkItem->m_Overlapped);
  1692. DNASSERT(fResult);
  1693. }
  1694. else
  1695. {
  1696. DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p as previous timer thread.",
  1697. pWorkItem);
  1698. //
  1699. // Return the item to the pool.
  1700. //
  1701. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  1702. //
  1703. // Bail out of the 'while' loop.
  1704. //
  1705. fShouldRun = FALSE;
  1706. }
  1707. }
  1708. }
  1709. else
  1710. {
  1711. //
  1712. // No queued work at the moment.
  1713. //
  1714. }
  1715. }
  1716. else
  1717. {
  1718. //DPFX(DPFPREP, 9, "Other non-busy threads exist, staying as timer thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads);
  1719. }
  1720. }
  1721. else
  1722. {
  1723. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1724. dwStartTime = GETTIMESTAMP();
  1725. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1726. //DPFX(DPFPREP, 9, "Getting queued packet on completion port 0x%p.", pWorkQueue->hIoCompletionPort);
  1727. fResult = GetQueuedCompletionStatus(HANDLE_FROM_DNHANDLE(pWorkQueue->hIoCompletionPort),
  1728. &dwBytesTransferred,
  1729. &dwCompletionKey,
  1730. &pOverlapped,
  1731. INFINITE);
  1732. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1733. DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalTimeSpentUnsignalled),
  1734. (GETTIMESTAMP() - dwStartTime));
  1735. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1736. DNASSERT(pOverlapped != NULL);
  1737. pWorkItem = CONTAINING_OBJECT(pOverlapped, CWorkItem, m_Overlapped);
  1738. DNASSERT(pWorkItem->IsValid());
  1739. //
  1740. // Increase the number of busy threads.
  1741. //
  1742. dwNumBusyThreads = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
  1743. //
  1744. // Call the user's function or note that we need to stop running.
  1745. //
  1746. if (pWorkItem->m_pfnWorkCallback != NULL)
  1747. {
  1748. //
  1749. // Save the uniqueness ID to determine if this item was a timer
  1750. // that got rescheduled.
  1751. //
  1752. uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
  1753. DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p) as worker thread.",
  1754. pWorkItem, pWorkItem->m_pfnWorkCallback,
  1755. pWorkItem->m_pvCallbackContext, uiOriginalUniqueID,
  1756. pWorkQueue);
  1757. ThreadpoolStatsBeginExecuting(pWorkItem);
  1758. pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext,
  1759. pWorkItem,
  1760. uiOriginalUniqueID);
  1761. //
  1762. // Return the item to the pool unless it got rescheduled. This
  1763. // assumes that the actual pWorkItem memory remains valid even
  1764. // though it may have been rescheduled and then completed/
  1765. // cancelled by the time we perform this test. See
  1766. // CancelTimer.
  1767. //
  1768. if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID)
  1769. {
  1770. ThreadpoolStatsEndExecuting(pWorkItem);
  1771. DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem);
  1772. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  1773. }
  1774. else
  1775. {
  1776. ThreadpoolStatsEndExecutingRescheduled(pWorkItem);
  1777. DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem);
  1778. }
  1779. }
  1780. else
  1781. {
  1782. DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p as worker thread.",
  1783. pWorkItem);
  1784. //
  1785. // Return the item to the pool.
  1786. //
  1787. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  1788. //
  1789. // Bail out of the 'while' loop.
  1790. //
  1791. fShouldRun = FALSE;
  1792. }
  1793. }
  1794. //
  1795. // Revert the busy count.
  1796. //
  1797. DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
  1798. } // end while (should keep running)
  1799. DPFX(DPFPREP, 6, "Leave");
  1800. } // DPTPWorkerLoop
  1801. //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1802. #else // ! DPNBUILD_USEIOCOMPLETIONPORTS
  1803. //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1804. {
  1805. BOOL fShouldRun = TRUE;
  1806. BOOL fRunningAsTimerThread = FALSE;
  1807. BOOL fSetAndWait = FALSE;
  1808. DNSLIST_ENTRY * pSlistEntryHead;
  1809. DNSLIST_ENTRY * pSlistEntryTail;
  1810. USHORT usCount;
  1811. DWORD dwNumBusyThreads;
  1812. DWORD dwNumRunningThreads;
  1813. CWorkItem * pWorkItem;
  1814. DWORD dwResult;
  1815. DWORD dwWaitTimeout;
  1816. DNHANDLE hWaitObject;
  1817. UINT uiOriginalUniqueID;
  1818. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1819. DWORD dwStartTime;
  1820. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1821. DPFX(DPFPREP, 6, "Parameters: (0x%p)", pWorkQueue);
  1822. //
  1823. // When we can't use waitable timers, we always wait on the same object,
  1824. // but the timeout can differ. If we can use waitable timers, the timeout
  1825. // is always INFINITE.
  1826. //
  1827. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  1828. dwWaitTimeout = INFINITE;
  1829. #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  1830. hWaitObject = pWorkQueue->hAlertEvent;
  1831. #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  1832. //
  1833. // Keep looping until we're told to exit.
  1834. //
  1835. while (fShouldRun)
  1836. {
  1837. //
  1838. // We also have an additional optimization technique that attempts to
  1839. // minimize the amount that the timer responsibility passes from thread
  1840. // to thread. It only truly works on NT, because of the atomic
  1841. // SignalObjectAndWait function. Having separate SetEvent and Wait
  1842. // operations could mean an extra context switch: SetEvent causes a
  1843. // waiting thread to take over, then when the timer thread is activated
  1844. // again, it just goes back to waiting.
  1845. //
  1846. // But we'll still execute the code on 9x (when we can use waitable
  1847. // timers). The real problem is on CE, where the event to set and the
  1848. // event on which the timer thread waits are the same, so it would
  1849. // almost certainly just be waking itself up. So if we're not using
  1850. // waitable timers, we set the event at a different location in an
  1851. // attempt to reduce this possibility.
  1852. //
  1853. // You'll probably need to understand the rest of this function for
  1854. // this "optimization" to make much sense.
  1855. //
  1856. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  1857. if (fSetAndWait)
  1858. {
  1859. DNASSERT(fRunningAsTimerThread);
  1860. DPFX(DPFPREP, 8, "Signalling event 0x%p and waiting on timer 0x%p.",
  1861. pWorkQueue->hAlertEvent, pWorkQueue->hTimer);
  1862. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1863. dwStartTime = GETTIMESTAMP();
  1864. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1865. dwResult = DNSignalObjectAndWait(pWorkQueue->hAlertEvent,
  1866. pWorkQueue->hTimer,
  1867. INFINITE,
  1868. FALSE);
  1869. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1870. DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalTimeSpentUnsignalled),
  1871. (GETTIMESTAMP() - dwStartTime));
  1872. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1873. //
  1874. // Clear the flag.
  1875. //
  1876. fSetAndWait = FALSE;
  1877. }
  1878. else
  1879. #endif // WINNT or (WIN95 and ! DPNBUILD_NOWAITABLETIMERSON9X)
  1880. {
  1881. //
  1882. // If we're not already running as a timer thread, atomically see
  1883. // if there is currently a timer thread, and become it if not.
  1884. // There can be only one.
  1885. //
  1886. if (! fRunningAsTimerThread)
  1887. {
  1888. fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  1889. (LONG) FALSE);
  1890. #ifdef DBG
  1891. if (fRunningAsTimerThread)
  1892. {
  1893. DPFX(DPFPREP, 9, "Becoming timer thread.");
  1894. }
  1895. #endif // DBG
  1896. }
  1897. //
  1898. // Normally we wait for a work item. If we're the timer thread, we
  1899. // need to wake up at regular intervals to service any timer
  1900. // entries. On CE, that is done by using a timeout value for our
  1901. // Wait operation. On desktop, we use a waitable timer object.
  1902. //
  1903. if (fRunningAsTimerThread)
  1904. {
  1905. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  1906. hWaitObject = pWorkQueue->hTimer;
  1907. #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  1908. dwWaitTimeout = TIMER_BUCKET_GRANULARITY(pWorkQueue);
  1909. #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  1910. }
  1911. else
  1912. {
  1913. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  1914. hWaitObject = pWorkQueue->hAlertEvent;
  1915. #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  1916. dwWaitTimeout = INFINITE;
  1917. #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  1918. }
  1919. //DPFX(DPFPREP, 9, "Waiting on handle 0x%p for %i ms.", hWaitObject, (int) dwWaitTimeout);
  1920. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1921. dwStartTime = GETTIMESTAMP();
  1922. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1923. dwResult = DNWaitForSingleObject(hWaitObject, dwWaitTimeout);
  1924. #if ((defined(DPNBUILD_THREADPOOLSTATISTICS)) && (! defined(WINCE)))
  1925. DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalTimeSpentUnsignalled),
  1926. (GETTIMESTAMP() - dwStartTime));
  1927. #endif // DPNBUILD_THREADPOOLSTATISTICS and ! WINCE
  1928. }
  1929. DNASSERT((dwResult == WAIT_OBJECT_0) || (dwResult == WAIT_TIMEOUT));
  1930. //
  1931. // Prepare to collect some work items that need to be queued.
  1932. //
  1933. pSlistEntryHead = NULL;
  1934. usCount = 0;
  1935. #ifndef WINCE
  1936. //
  1937. // Handle any I/O completions.
  1938. //
  1939. ProcessIo(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount);
  1940. #endif // ! WINCE
  1941. //
  1942. // If we're acting as the timer thread, retrieve any expired or
  1943. // cancelled timer entries.
  1944. //
  1945. if (fRunningAsTimerThread)
  1946. {
  1947. ProcessTimers(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount);
  1948. }
  1949. //
  1950. // Queue any work items we accumulated in one fell swoop.
  1951. //
  1952. if (pSlistEntryHead != NULL)
  1953. {
  1954. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  1955. if (usCount > 1)
  1956. {
  1957. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumSimultaneousQueues));
  1958. }
  1959. #ifdef WINCE
  1960. LONG lCount;
  1961. lCount = usCount;
  1962. while (lCount > 0)
  1963. {
  1964. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWorkItems));
  1965. lCount--;
  1966. }
  1967. #else // ! WINCE
  1968. DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalNumWorkItems), usCount);
  1969. #endif // ! WINCE
  1970. #endif // DPNBUILD_THREADPOOLSTATISTICS
  1971. DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems,
  1972. pSlistEntryHead,
  1973. OFFSETOF(CWorkItem, m_SlistEntry));
  1974. }
  1975. //
  1976. // Increase the number of busy threads.
  1977. //
  1978. dwNumBusyThreads = DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
  1979. //
  1980. // We may be able to optimize context switching for the timer thread,
  1981. // so treat it differently. Worker threads should just go straight to
  1982. // running through the queue (if possible).
  1983. //
  1984. if (fRunningAsTimerThread)
  1985. {
  1986. //
  1987. // Grab the current total thread count.
  1988. //
  1989. dwNumRunningThreads = *((volatile DWORD *) (&pWorkQueue->dwNumRunningThreads));
  1990. //
  1991. // If we didn't add any work items, there's no point in this timer
  1992. // thread looking for jobs to execute. If we found one, we'd have
  1993. // to wake another thread so it could become the timer thread while
  1994. // we processed the item, and thus incur a context switch.
  1995. // Besides, if someone other than us added any work, they would
  1996. // have set the alert event already.
  1997. //
  1998. // There are two exceptions to that rule. One is if there is only
  1999. // this one thread, in which case we must process any work items
  2000. // because there's no one else who can. The other is on platforms
  2001. // without waitable timers. In that case, we may have dropped
  2002. // through not because the time elapsed, but rather because the
  2003. // event was set. So to prevent us from eating these alerts, we
  2004. // will check for any work to do.
  2005. //
  2006. if ((usCount == 0) && (dwNumRunningThreads > 1))
  2007. {
  2008. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  2009. //DPFX(DPFPREP, 9, "No work items added, staying as timer thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads);
  2010. //
  2011. // Don't execute any work.
  2012. //
  2013. pWorkItem = NULL;
  2014. #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  2015. //
  2016. // See the comments above and below. If we find something to
  2017. // do, we need to try to make another thread the timer thread
  2018. // while we work on this item.
  2019. //
  2020. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  2021. if (pWorkItem != NULL)
  2022. {
  2023. DPFX(DPFPREP, 9, "No work items added, but there's some in the queue, becoming worker thread (busy = %u, total = %u).",
  2024. dwNumBusyThreads, dwNumRunningThreads);
  2025. fSetAndWait = TRUE;
  2026. }
  2027. else
  2028. {
  2029. //DPFX(DPFPREP, 9, "No work items, staying as timer thread (busy = %u, total = %u).", dwNumBusyThreads, dwNumRunningThreads);
  2030. }
  2031. #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  2032. }
  2033. else
  2034. {
  2035. //
  2036. // We added work, so we may need to notify other threads.
  2037. //
  2038. // If it appears that there are enough idle threads to handle
  2039. // the work that we added, don't bother executing any work in
  2040. // this timer thread. We will need to alert other threads
  2041. // about the work, but as stated above, we would need to wake
  2042. // another thread anyway even if we did execute it in this
  2043. // thread. On NT, we get a nice optimization that allows us to
  2044. // alert a thread and go to sleep atomically (see above), so
  2045. // it's not too painful.
  2046. //
  2047. // Otherwise, all other threads are busy, so we should just
  2048. // execute the work in this thread. The first thread to
  2049. // complete the work will become the new timer thread.
  2050. //
  2051. if (dwNumBusyThreads < dwNumRunningThreads)
  2052. {
  2053. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  2054. DPFX(DPFPREP, 9, "Other non-busy threads exist, staying as timer thread (busy = %u, total = %u, added %u items).",
  2055. dwNumBusyThreads,
  2056. dwNumRunningThreads,
  2057. usCount);
  2058. //
  2059. // Don't execute any work, and set the event up top.
  2060. //
  2061. pWorkItem = NULL;
  2062. fSetAndWait = TRUE;
  2063. #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  2064. //
  2065. // Because CE sets and waits on the same event, we opt to
  2066. // make this timer thread a worker thread anyway, and set
  2067. // the alert event sooner. See comments at the top and
  2068. // down below for more details.
  2069. //
  2070. // So attempt to pop the next work item off the stack.
  2071. // Note that theoretically all of the work we added could
  2072. // have been processed already, so we might still end up
  2073. // with nothing to do. Don't enable fSetAndWait if that's
  2074. // the case.
  2075. //
  2076. DPFX(DPFPREP, 9, "Trying to become worker thread (busy = %u, total = %u, added %u items).",
  2077. dwNumBusyThreads,
  2078. dwNumRunningThreads,
  2079. usCount);
  2080. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  2081. if (pWorkItem != NULL)
  2082. {
  2083. fSetAndWait = TRUE;
  2084. }
  2085. #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  2086. }
  2087. else
  2088. {
  2089. #ifdef DBG
  2090. if (dwNumRunningThreads > 1) // reduce spew when only running 1 thread
  2091. {
  2092. DPFX(DPFPREP, 9, "No idle threads (busy = %u) after adding %u items, may become worker thread.",
  2093. dwNumBusyThreads, usCount);
  2094. }
  2095. #endif // DBG
  2096. //
  2097. // Attempt to pop the next work item off the stack.
  2098. // Note that theoretically all of the work we added could
  2099. // have been processed already, so we could end up with
  2100. // nothing to do.
  2101. //
  2102. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  2103. }
  2104. } // end else (added work from timers or I/O)
  2105. }
  2106. else
  2107. {
  2108. //
  2109. // Not a timer thread. Attempt to pop the next work item off the
  2110. // stack, if any.
  2111. //
  2112. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  2113. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  2114. if (pWorkItem == NULL)
  2115. {
  2116. DPFX(DPFPREP, 7, "No items for worker thread (busy = %u, had added %u items to queue 0x%p).",
  2117. dwNumBusyThreads, usCount, pWorkQueue);
  2118. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumWakesWithoutWork));
  2119. }
  2120. #endif // DPNBUILD_THREADPOOLSTATISTICS
  2121. }
  2122. //
  2123. // Execute as many work items in a row as possible.
  2124. //
  2125. while (pWorkItem != NULL)
  2126. {
  2127. //
  2128. // If we were acting as the timer thread, we need another thread to
  2129. // take over that responsibility while we're busy processing what
  2130. // could be a long work item.
  2131. //
  2132. if (fRunningAsTimerThread)
  2133. {
  2134. DPFX(DPFPREP, 8, "Abdicating timer thread responsibilities.");
  2135. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  2136. //
  2137. // Update the debugging/tuning statistics.
  2138. //
  2139. pWorkQueue->dwTotalNumTimerThreadAbdications++;
  2140. #endif // DPNBUILD_THREADPOOLSTATISTICS
  2141. DNASSERT(! pWorkQueue->fTimerThreadNeeded);
  2142. #pragma TODO(vanceo, "Does this truly need to be interlocked?")
  2143. fRunningAsTimerThread = DNInterlockedExchange((LPLONG) (&pWorkQueue->fTimerThreadNeeded),
  2144. (LONG) TRUE);
  2145. DNASSERT(! fRunningAsTimerThread); // there had better not be more than one timer thread
  2146. //
  2147. // On CE (or 9x, if we're not using waitable timers), we may
  2148. // also need to kick other threads so that they notice there's
  2149. // no timer thread any more. We endure SetEvent's unfortunate
  2150. // context switch because if this turned out to be a really
  2151. // long job, and no I/O or newly queued work items jolt the
  2152. // other threads out of their Waits, timers could go unserviced
  2153. // until this thread finishes. We choose to have more accurate
  2154. // timers.
  2155. //
  2156. // We do this now because it's more efficient to switch to
  2157. // another thread now than run the risk of just alerting
  2158. // ourselves (see the comments at the top of this function).
  2159. //
  2160. // On desktop, we should only get in here if we couldn't alert
  2161. // any other threads, so it wouldn't make sense to SetEvent.
  2162. //
  2163. #pragma TODO(vanceo, "We may be able to live with some possible loss of timer response, if we could assume we'll get I/O or queued work items")
  2164. #if ((defined(WINNT)) || ((defined(WIN95)) && (! defined(DPNBUILD_NOWAITABLETIMERSON9X))))
  2165. DNASSERT(! fSetAndWait);
  2166. #else // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  2167. if (fSetAndWait)
  2168. {
  2169. DNSetEvent(pWorkQueue->hAlertEvent); // ignore potential failure, there's nothing we could do
  2170. fSetAndWait = FALSE;
  2171. }
  2172. #endif // ! WINNT and (! WIN95 or DPNBUILD_NOWAITABLETIMERSON9X)
  2173. }
  2174. //
  2175. // Call the user's function or note that we need to stop running.
  2176. //
  2177. if (pWorkItem->m_pfnWorkCallback != NULL)
  2178. {
  2179. //
  2180. // Save the uniqueness ID to determine if this item was a timer
  2181. // that got rescheduled.
  2182. //
  2183. uiOriginalUniqueID = pWorkItem->m_uiUniqueID;
  2184. DPFX(DPFPREP, 8, "Begin executing work item 0x%p (fn = 0x%p, context = 0x%p, unique = %u, queue = 0x%p).",
  2185. pWorkItem, pWorkItem->m_pfnWorkCallback,
  2186. pWorkItem->m_pvCallbackContext, uiOriginalUniqueID,
  2187. pWorkQueue);
  2188. ThreadpoolStatsBeginExecuting(pWorkItem);
  2189. pWorkItem->m_pfnWorkCallback(pWorkItem->m_pvCallbackContext,
  2190. pWorkItem,
  2191. uiOriginalUniqueID);
  2192. //
  2193. // Return the item to the pool unless it got rescheduled. This
  2194. // assumes that the actual pWorkItem memory remains valid even
  2195. // though it may have been rescheduled and then completed/
  2196. // cancelled by the time we perform this test. See
  2197. // CancelTimer.
  2198. //
  2199. if (uiOriginalUniqueID == pWorkItem->m_uiUniqueID)
  2200. {
  2201. ThreadpoolStatsEndExecuting(pWorkItem);
  2202. DPFX(DPFPREP, 8, "Done executing work item 0x%p, returning to pool.", pWorkItem);
  2203. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  2204. }
  2205. else
  2206. {
  2207. ThreadpoolStatsEndExecutingRescheduled(pWorkItem);
  2208. DPFX(DPFPREP, 8, "Done executing work item 0x%p, it was rescheduled.", pWorkItem);
  2209. }
  2210. }
  2211. else
  2212. {
  2213. DPFX(DPFPREP, 3, "Recognized exit thread work item 0x%p.",
  2214. pWorkItem);
  2215. //
  2216. // Return the item to the pool.
  2217. //
  2218. pWorkQueue->pWorkItemPool->Release(pWorkItem);
  2219. //
  2220. // We're about to bail out of the processing loop, so we need
  2221. // other threads to notice. It's possible that all threads
  2222. // were busy so that the alert event was set twice before any
  2223. // thread noticed it, and only this thread was released. In
  2224. // that case, we need the other threads to start processing.
  2225. //
  2226. if (! DNIsNBQueueEmpty(pWorkQueue->pvNBQueueWorkItems))
  2227. {
  2228. DNSetEvent(pWorkQueue->hAlertEvent); // ignore potential failure, there's nothing we could do
  2229. }
  2230. //
  2231. // Bail out of both 'while' loops.
  2232. //
  2233. fShouldRun = FALSE;
  2234. break;
  2235. }
  2236. #ifndef WINCE
  2237. //
  2238. // Handle any I/O completions that came in while we were working.
  2239. //
  2240. pSlistEntryHead = NULL;
  2241. usCount = 0;
  2242. ProcessIo(pWorkQueue, &pSlistEntryHead, &pSlistEntryTail, &usCount);
  2243. //
  2244. // Queue any I/O operations that completed.
  2245. //
  2246. if (pSlistEntryHead != NULL)
  2247. {
  2248. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  2249. if (usCount > 1)
  2250. {
  2251. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumSimultaneousQueues));
  2252. }
  2253. DNInterlockedExchangeAdd((LPLONG) (&pWorkQueue->dwTotalNumWorkItems), usCount);
  2254. #endif // DPNBUILD_THREADPOOLSTATISTICS
  2255. DNAppendListNBQueue(pWorkQueue->pvNBQueueWorkItems,
  2256. pSlistEntryHead,
  2257. OFFSETOF(CWorkItem, m_SlistEntry));
  2258. }
  2259. #endif // ! WINCE
  2260. //
  2261. // Look for another work item.
  2262. //
  2263. pWorkItem = (CWorkItem*) DNRemoveHeadNBQueue(pWorkQueue->pvNBQueueWorkItems);
  2264. #ifdef DPNBUILD_THREADPOOLSTATISTICS
  2265. if (pWorkItem != NULL)
  2266. {
  2267. DNInterlockedIncrement((LPLONG) (&pWorkQueue->dwTotalNumContinuousWork));
  2268. }
  2269. #endif // DPNBUILD_THREADPOOLSTATISTICS
  2270. } // end while (more work items)
  2271. //
  2272. // Revert the busy count.
  2273. //
  2274. DNInterlockedDecrement((LPLONG) (&pWorkQueue->dwNumBusyThreads));
  2275. } // end while (should keep running)
  2276. DPFX(DPFPREP, 6, "Leave");
  2277. } // DPTPWorkerLoop
  2278. #endif // ! DPNBUILD_USEIOCOMPLETIONPORTS
  2279. #endif // ! DPNBUILD_ONLYONETHREAD