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.

667 lines
16 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name:
  4. tpswork.h
  5. Abstract:
  6. Worker thread classes. Moved out of tpsclass.h
  7. Contents:
  8. CIoWorkerThreadInfo
  9. CIoWorkerRequest
  10. CThreadPool
  11. Author:
  12. Richard L Firth (rfirth) 08-Aug-1998
  13. Revision History:
  14. 08-Aug-1998 rfirth
  15. Created
  16. --*/
  17. //
  18. // manifests
  19. //
  20. #define THREAD_CREATION_DAMPING_TIME 5000
  21. #define NEW_THREAD_THRESHOLD 10
  22. #define MIN_WORKER_THREADS 1
  23. #define MAX_WORKER_THREADS 128
  24. #define MAX_IO_WORKER_THREADS 256
  25. #define MAX_QUEUE_DEPTH 0
  26. #define THREAD_IDLE_TIMEOUT 60000
  27. #define TPS_ID 0x80000000
  28. //
  29. // external data
  30. //
  31. extern DWORD g_dwWorkItemId;
  32. //
  33. // classes
  34. //
  35. //
  36. // CIoWorkerThreadInfo
  37. //
  38. class CIoWorkerThreadInfo : public CDoubleLinkedListEntry {
  39. private:
  40. HANDLE m_hThread;
  41. public:
  42. CIoWorkerThreadInfo(CDoubleLinkedList * pList) {
  43. m_hThread = (HANDLE)-1;
  44. InsertHead(pList);
  45. }
  46. ~CIoWorkerThreadInfo() {
  47. ASSERT(m_hThread == NULL);
  48. }
  49. VOID SetHandle(HANDLE hThread) {
  50. m_hThread = hThread;
  51. }
  52. HANDLE GetHandle(VOID) const {
  53. return m_hThread;
  54. }
  55. };
  56. //
  57. // CIoWorkerRequest
  58. //
  59. class CIoWorkerRequest {
  60. private:
  61. LPTHREAD_START_ROUTINE m_pfnCallback;
  62. LPVOID m_pContext;
  63. public:
  64. CIoWorkerRequest(LPTHREAD_START_ROUTINE pfnCallback, LPVOID pContext) {
  65. m_pfnCallback = pfnCallback;
  66. m_pContext = pContext;
  67. }
  68. LPTHREAD_START_ROUTINE GetCallback(VOID) const {
  69. return m_pfnCallback;
  70. }
  71. LPVOID GetContext(VOID) const {
  72. return m_pContext;
  73. }
  74. };
  75. //
  76. // CThreadPool - maintains lists of work items, non-IO worker threads and
  77. // IO worker threads
  78. //
  79. class CThreadPool {
  80. private:
  81. //
  82. // private classes
  83. //
  84. //
  85. // CWorkItem - queued app-supplied functions, ordered by priority
  86. //
  87. class CWorkItem : public CPrioritizedListEntry {
  88. public:
  89. FARPROC m_function;
  90. ULONG_PTR m_context;
  91. DWORD_PTR m_tag;
  92. DWORD_PTR m_id;
  93. DWORD m_flags;
  94. HINSTANCE m_hInstModule;
  95. CWorkItem(FARPROC lpfn,
  96. ULONG_PTR context,
  97. LONG priority,
  98. DWORD_PTR tag,
  99. DWORD_PTR * pid,
  100. LPCSTR pszModule,
  101. DWORD flags
  102. ) : CPrioritizedListEntry(priority)
  103. {
  104. m_function = lpfn;
  105. m_context = context;
  106. m_tag = tag;
  107. m_id = (DWORD_PTR)0;
  108. m_flags = flags;
  109. if (pszModule && *pszModule)
  110. {
  111. m_hInstModule = LoadLibrary(pszModule);
  112. if (!m_hInstModule)
  113. {
  114. TraceMsg(TF_WARNING, TEXT("CWorkItem::CWorkItem - faild to load %hs (error = %d), worker thread could be abanonded!!"), pszModule, GetLastError());
  115. }
  116. }
  117. else
  118. {
  119. m_hInstModule = NULL;
  120. }
  121. if (pid) {
  122. m_id = (DWORD_PTR)++g_dwWorkItemId;
  123. *pid = m_id;
  124. m_flags |= TPS_ID;
  125. }
  126. }
  127. ~CWorkItem()
  128. {
  129. // we used to call FreeLibrary(m_hInstModule) here but we delete the workitem
  130. // when we grab it off of the queue (in RemoveWorkItem). so we have to wait until
  131. // we are actually done running the task before we call FreeLibaray()
  132. }
  133. BOOL Match(DWORD_PTR Tag, BOOL IsTag) {
  134. return IsTag
  135. ? ((m_flags & TPS_TAGGEDITEM) && (m_tag == Tag))
  136. : ((m_flags & TPS_ID) && (m_id == Tag));
  137. }
  138. BOOL IsLongExec(VOID) {
  139. return (m_flags & TPS_LONGEXECTIME) ? TRUE : FALSE;
  140. }
  141. };
  142. //
  143. // work item queue variables
  144. //
  145. CPrioritizedList m_queue;
  146. CCriticalSection_NoCtor m_qlock;
  147. HANDLE m_event;
  148. DWORD m_error;
  149. DWORD m_queueSize;
  150. DWORD m_qFactor;
  151. DWORD m_minWorkerThreads;
  152. DWORD m_maxWorkerThreads;
  153. DWORD m_maxQueueDepth;
  154. DWORD m_workerIdleTimeout;
  155. DWORD m_creationDelta;
  156. DWORD m_totalWorkerThreads;
  157. DWORD m_availableWorkerThreads;
  158. #if DBG
  159. DWORD m_queueSizeMax;
  160. DWORD m_qFactorMax;
  161. DWORD m_maxWorkerThreadsCreated;
  162. #endif
  163. //
  164. // private member functions
  165. //
  166. CWorkItem * DequeueWorkItem(VOID) {
  167. CWorkItem * pItem = NULL;
  168. if (!m_queue.IsEmpty()) {
  169. pItem = (CWorkItem *)m_queue.RemoveHead();
  170. --m_queueSize;
  171. }
  172. return pItem;
  173. }
  174. VOID
  175. Worker(
  176. VOID
  177. );
  178. public:
  179. static
  180. VOID
  181. WorkerThread(
  182. VOID
  183. );
  184. BOOL Init(VOID) {
  185. m_queue.Init();
  186. m_qlock.Init();
  187. //
  188. // create auto-reset, initially unsignalled event
  189. //
  190. m_event = CreateEvent(NULL, FALSE, FALSE, NULL);
  191. m_error = (m_event != NULL) ? ERROR_SUCCESS : GetLastError();
  192. m_queueSize = 0;
  193. m_qFactor = 0;
  194. m_minWorkerThreads = MIN_WORKER_THREADS;
  195. m_maxWorkerThreads = MAX_WORKER_THREADS;
  196. m_maxQueueDepth = MAX_QUEUE_DEPTH;
  197. m_workerIdleTimeout = THREAD_IDLE_TIMEOUT;
  198. m_creationDelta = THREAD_CREATION_DAMPING_TIME;
  199. m_totalWorkerThreads = 0;
  200. m_availableWorkerThreads = 0;
  201. #if DBG
  202. m_queueSizeMax = 0;
  203. m_qFactorMax = 0;
  204. m_maxWorkerThreadsCreated = 0;
  205. #endif
  206. return m_error == ERROR_SUCCESS;
  207. }
  208. VOID Terminate(DWORD Limit) {
  209. PurgeWorkItems();
  210. TerminateThreads(Limit);
  211. if (m_event != NULL) {
  212. BOOL bOk = CloseHandle(m_event);
  213. ASSERT(bOk);
  214. m_event = NULL;
  215. }
  216. m_qlock.Terminate();
  217. ASSERT(m_queue.IsEmpty());
  218. //#if DBG
  219. //char buf[256];
  220. //wsprintf(buf,
  221. // "CThreadPool::Terminate(): m_queueSizeMax = %d, m_maxWorkerThreadsCreated = %d, m_qFactorMax = %d\n",
  222. // m_queueSizeMax,
  223. // m_maxWorkerThreadsCreated,
  224. // m_qFactorMax
  225. // );
  226. //OutputDebugString(buf);
  227. //#endif
  228. }
  229. DWORD GetError() const {
  230. return m_error;
  231. }
  232. VOID
  233. SetLimits(
  234. IN DWORD dwMinimumThreads,
  235. IN DWORD dwMaximumThreads,
  236. IN DWORD dwMaximumQueueDepth,
  237. IN DWORD dwThreadIdleTimeout,
  238. IN DWORD dwThreadCreationDelta
  239. )
  240. {
  241. m_minWorkerThreads = dwMinimumThreads;
  242. m_maxWorkerThreads = dwMaximumThreads;
  243. m_maxQueueDepth = dwMaximumQueueDepth;
  244. m_workerIdleTimeout = dwThreadIdleTimeout;
  245. m_creationDelta = dwThreadCreationDelta;
  246. }
  247. VOID MakeAvailable(VOID) {
  248. InterlockedIncrement((LPLONG)&m_availableWorkerThreads);
  249. if (m_qFactor == 0) {
  250. m_qFactor = 1;
  251. } else {
  252. m_qFactor <<= 1;
  253. }
  254. #if DBG
  255. if (m_qFactor > m_qFactorMax) {
  256. m_qFactorMax = m_qFactor;
  257. }
  258. #endif
  259. }
  260. VOID MakeUnavailable(VOID) {
  261. InterlockedDecrement((LPLONG)&m_availableWorkerThreads);
  262. m_qFactor >>= 1;
  263. if ((m_qFactor == 0) && (m_availableWorkerThreads != 0)) {
  264. m_qFactor = 1;
  265. }
  266. }
  267. DWORD
  268. QueueWorkItem(
  269. FARPROC pfnFunction,
  270. ULONG_PTR pContext,
  271. LONG lPriority,
  272. DWORD_PTR dwTag,
  273. DWORD_PTR * pdwId,
  274. LPCSTR pszModule,
  275. DWORD dwFlags
  276. )
  277. {
  278. //
  279. // add a work item to the queue at the appropriate place and create a
  280. // thread to handle it if necessary
  281. //
  282. CWorkItem * pItem = new CWorkItem(pfnFunction,
  283. pContext,
  284. lPriority,
  285. dwTag,
  286. pdwId,
  287. pszModule,
  288. dwFlags
  289. );
  290. if (pItem == NULL) {
  291. return ERROR_NOT_ENOUGH_MEMORY;
  292. }
  293. m_qlock.Acquire();
  294. //
  295. // demand-thread work-items have the highest priority. Put at head of
  296. // queue, else insert based on priority
  297. //
  298. if (dwFlags & TPS_DEMANDTHREAD) {
  299. pItem->InsertHead(&m_queue);
  300. } else {
  301. m_queue.insert(pItem);
  302. }
  303. ++m_queueSize;
  304. #if DBG
  305. if (m_queueSize > m_queueSizeMax) {
  306. m_queueSizeMax = m_queueSize;
  307. }
  308. #endif
  309. //
  310. // determine whether we need to create a new thread:
  311. //
  312. // * no available threads
  313. // * work queue growing too fast
  314. // * all available threads about to be taken by long-exec work items
  315. //
  316. BOOL bCreate = FALSE;
  317. DWORD error = ERROR_SUCCESS;
  318. if (m_queueSize > (m_availableWorkerThreads * m_qFactor)) {
  319. bCreate = TRUE;
  320. } else {
  321. DWORD i = 0;
  322. DWORD n = 0;
  323. CWorkItem * pItem = (CWorkItem *)m_queue.Next();
  324. while ((pItem != m_queue.Head()) && (i < m_availableWorkerThreads)) {
  325. if (pItem->IsLongExec()) {
  326. ++n;
  327. }
  328. pItem = (CWorkItem *)pItem->Next();
  329. ++i;
  330. }
  331. if (n == m_availableWorkerThreads) {
  332. bCreate = TRUE;
  333. }
  334. }
  335. m_qlock.Release();
  336. if (bCreate) {
  337. // if the CreateWorkerThread fails, do NOT pass back an error code to the caller
  338. // since we've already added the workitem to the queue. An error code will
  339. // likely result in the caller freeing the data for the work item. (saml 081799)
  340. CreateWorkerThread();
  341. }
  342. SetEvent(m_event);
  343. return error;
  344. }
  345. DWORD
  346. RemoveWorkItem(
  347. FARPROC * ppfnFunction,
  348. ULONG_PTR * pContext,
  349. HMODULE* hModuleToFree,
  350. DWORD * pdwFlags,
  351. DWORD dwTimeout
  352. )
  353. {
  354. BOOL bFirstTime = TRUE;
  355. DWORD dwWaitTime = dwTimeout;
  356. while (TRUE) {
  357. CWorkItem * pItem;
  358. //
  359. // first test the FIFO state without waiting for the event
  360. //
  361. if (!m_queue.IsEmpty())
  362. {
  363. m_qlock.Acquire();
  364. pItem = DequeueWorkItem();
  365. if (pItem != NULL)
  366. {
  367. if (pItem->m_flags & TPS_LONGEXECTIME)
  368. {
  369. MakeUnavailable();
  370. }
  371. m_qlock.Release();
  372. *ppfnFunction = pItem->m_function;
  373. *pContext = pItem->m_context;
  374. *pdwFlags = pItem->m_flags & ~TPS_RESERVED_FLAGS;
  375. *hModuleToFree = pItem->m_hInstModule;
  376. delete pItem;
  377. return ERROR_SUCCESS;
  378. }
  379. m_qlock.Release();
  380. }
  381. DWORD dwStartTime;
  382. if ((dwTimeout != INFINITE) && bFirstTime) {
  383. dwStartTime = GetTickCount();
  384. }
  385. //
  386. // if dwTimeout is 0 (poll) and we've already waited unsuccessfully
  387. // then we're done: we timed out
  388. //
  389. if ((dwTimeout == 0) && !bFirstTime) {
  390. break;
  391. }
  392. //
  393. // wait alertably: process I/O completions while we wait
  394. //
  395. // FEATURE - we want MsgWaitForMultipleObjectsEx() here, but Win95
  396. // doesn't support it
  397. //
  398. DWORD status = MsgWaitForMultipleObjects(1,
  399. &m_event,
  400. FALSE,
  401. dwWaitTime,
  402. //QS_ALLINPUT
  403. QS_SENDMESSAGE | QS_KEY
  404. );
  405. //
  406. // quit now if thread pool is terminating
  407. //
  408. if (g_bTpsTerminating) {
  409. break;
  410. }
  411. bFirstTime = FALSE;
  412. if ((status == WAIT_OBJECT_0) || (status == WAIT_IO_COMPLETION)) {
  413. //
  414. // we think there is something to remove from the FIFO or I/O
  415. // completed. If we're not waiting forever, update the time to
  416. // wait on the next iteration based on the time we started
  417. //
  418. if (dwTimeout != INFINITE) {
  419. DWORD dwElapsedTime = GetTickCount() - dwStartTime;
  420. if (dwElapsedTime > dwTimeout) {
  421. //
  422. // waited longer than requested. Don't wait again if
  423. // we find there's nothing in the FIFO
  424. //
  425. dwWaitTime = 0;
  426. } else {
  427. //
  428. // amount of time to wait next iteration is amount of
  429. // time until expiration of originally specified period
  430. //
  431. dwWaitTime = dwTimeout - dwElapsedTime;
  432. }
  433. }
  434. continue;
  435. } else if (status == WAIT_OBJECT_0 + 1) {
  436. MSG msg;
  437. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  438. if (msg.message == WM_QUIT) {
  439. return WAIT_ABANDONED;
  440. } else {
  441. DispatchMessage(&msg);
  442. }
  443. }
  444. continue;
  445. }
  446. //
  447. // WAIT_TIMEOUT (or WAIT_ABANDONED (?))
  448. //
  449. break;
  450. }
  451. return WAIT_TIMEOUT;
  452. }
  453. DWORD RemoveTagged(DWORD_PTR Tag, BOOL IsTag) {
  454. DWORD count = 0;
  455. m_qlock.Acquire();
  456. CPrioritizedListEntry * pEntry = (CPrioritizedListEntry *)m_queue.Next();
  457. CPrioritizedListEntry * pPrev = (CPrioritizedListEntry *)m_queue.Head();
  458. while (pEntry != m_queue.Head()) {
  459. CWorkItem * pItem = (CWorkItem *)pEntry;
  460. if (pItem->Match(Tag, IsTag)) {
  461. pItem->Remove();
  462. --m_queueSize;
  463. delete pItem;
  464. ++count;
  465. if (!IsTag) {
  466. break;
  467. }
  468. } else {
  469. pPrev = pEntry;
  470. }
  471. pEntry = (CPrioritizedListEntry *)pPrev->Next();
  472. }
  473. m_qlock.Release();
  474. return count;
  475. }
  476. DWORD GetQueueSize(VOID) const {
  477. return m_queueSize;
  478. }
  479. VOID PurgeWorkItems(VOID) {
  480. m_qlock.Acquire();
  481. CWorkItem * pItem;
  482. while ((pItem = DequeueWorkItem()) != NULL) {
  483. delete pItem;
  484. }
  485. m_qlock.Release();
  486. }
  487. VOID Signal(VOID) {
  488. if (m_event != NULL) {
  489. SetEvent(m_event);
  490. }
  491. }
  492. DWORD CreateWorkerThread(VOID) {
  493. HANDLE hThread;
  494. DWORD error = ERROR_SUCCESS;
  495. error = StartThread((LPTHREAD_START_ROUTINE)WorkerThread,
  496. &hThread,
  497. FALSE
  498. );
  499. if (error == ERROR_SUCCESS) {
  500. AddWorker();
  501. #if DBG
  502. if (m_totalWorkerThreads > m_maxWorkerThreadsCreated) {
  503. m_maxWorkerThreadsCreated = m_totalWorkerThreads;
  504. }
  505. //char buf[256];
  506. //wsprintf(buf, ">>>> started worker thread. Total = %d/%d. Avail = %d. Factor = %d/%d\n",
  507. // m_totalWorkerThreads,
  508. // m_maxWorkerThreadsCreated,
  509. // m_availableWorkerThreads,
  510. // m_qFactor,
  511. // m_qFactorMax
  512. // );
  513. //OutputDebugString(buf);
  514. #endif
  515. CloseHandle(hThread); // thread handle not required
  516. return ERROR_SUCCESS;
  517. }
  518. return error;
  519. }
  520. VOID TerminateThreads(DWORD Limit) {
  521. while (m_totalWorkerThreads > Limit) {
  522. Signal();
  523. SleepEx(0, FALSE);
  524. }
  525. }
  526. VOID AddWorker(VOID) {
  527. InterlockedIncrement((LPLONG)&m_totalWorkerThreads);
  528. MakeAvailable();
  529. }
  530. VOID RemoveWorker(VOID) {
  531. MakeUnavailable();
  532. InterlockedDecrement((LPLONG)&m_totalWorkerThreads);
  533. }
  534. };