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.

1105 lines
29 KiB

  1. /*++
  2. Copyright (c) 1995-2000 Microsoft Corporation
  3. Module Name:
  4. sched.cxx
  5. Abstract:
  6. This module contains a simple timer interface for scheduling future
  7. work items
  8. Author:
  9. John Ludeman (johnl) 17-Jul-1995
  10. Project:
  11. Internet Servers Common Server DLL
  12. Revisions:
  13. Murali R. Krishnan (MuraliK) 16-Sept-1996
  14. Added scheduler items cache
  15. George V. Reilly (GeorgeRe) May-1999
  16. Removed the global variables; turned into refcounted objects, so
  17. that code will survive stops and restarts when work items take a
  18. long time to complete
  19. --*/
  20. //
  21. // Include Headers
  22. //
  23. #include "precomp.hxx"
  24. #include <objbase.h>
  25. #define DLL_IMPLEMENTATION
  26. #define IMPLEMENTATION_EXPORT
  27. #include <irtldbg.h>
  28. #include "sched.hxx"
  29. // Initialize class static members
  30. CSchedData* CSchedData::sm_psd = NULL;
  31. CLockedDoubleList CSchedData::sm_lstSchedulers;
  32. LONG CSchedData::sm_nID = 0;
  33. LONG CThreadData::sm_nID = 1000;
  34. LONG SCHED_ITEM::sm_lSerialNumber = SCHED_ITEM::SERIAL_NUM_INITIAL_VALUE;
  35. // SCHED_ITEM Finite State Machine
  36. SCHED_ITEM_STATE sg_rgSchedNextState[SI_OP_MAX][SI_MAX_ITEMS] = {
  37. // operation = SI_OP_ADD
  38. { // old state
  39. SI_ERROR, // SI_ERROR
  40. SI_ACTIVE, // SI_IDLE
  41. SI_ERROR, // SI_ACTIVE
  42. SI_ERROR, // SI_ACTIVE_PERIODIC
  43. SI_ERROR, // SI_CALLBACK_PERIODIC
  44. SI_ERROR, // SI_TO_BE_DELETED
  45. },
  46. // operation = SI_OP_ADD_PERIODIC
  47. { // old state
  48. SI_ERROR, // SI_ERROR
  49. SI_ACTIVE_PERIODIC, // SI_IDLE
  50. SI_ERROR, // SI_ACTIVE
  51. SI_ERROR, // SI_ACTIVE_PERIODIC
  52. SI_ACTIVE_PERIODIC, // SI_CALLBACK_PERIODIC: rescheduling
  53. // periodic item
  54. SI_ERROR, // SI_TO_BE_DELETED
  55. },
  56. // operation = SI_OP_CALLBACK
  57. { // old state
  58. SI_ERROR, // SI_ERROR
  59. SI_ERROR, // SI_IDLE
  60. SI_TO_BE_DELETED, // SI_ACTIVE: to be removed after completing
  61. // callbacks
  62. SI_CALLBACK_PERIODIC, // SI_ACTIVE_PERIODIC
  63. SI_ERROR, // SI_CALLBACK_PERIODIC
  64. SI_ERROR, // SI_TO_BE_DELETED
  65. },
  66. // operation = SI_OP_DELETE
  67. { // old state
  68. SI_ERROR, // SI_ERROR
  69. SI_ERROR, // SI_IDLE
  70. SI_IDLE, // SI_ACTIVE
  71. SI_IDLE, // SI_ACTIVE_PERIODIC
  72. SI_TO_BE_DELETED, // SI_CALLBACK_PERIODIC: mark this to be
  73. // deleted after return
  74. SI_TO_BE_DELETED, // SI_TO_BE_DELETED: idempotent delete ops
  75. }
  76. };
  77. //
  78. // Global data items
  79. //
  80. LONG cSchedInits = 0;
  81. LONG cSchedUninits = 0;
  82. /************************************************************
  83. * Public functions of Scheduler
  84. ************************************************************/
  85. BOOL
  86. SchedulerInitialize(
  87. VOID
  88. )
  89. /*++
  90. Routine Description:
  91. Initializes the scheduler/timer package
  92. Arguments:
  93. Return Value:
  94. TRUE if successful, FALSE on error (call GetLastError)
  95. --*/
  96. {
  97. // g_fErrorFlags |= DEBUG_SCHED;
  98. // g_pDebug->m_iControlFlag |= DEBUG_SCHED;
  99. ++cSchedInits;
  100. unsigned idThread;
  101. LONG i, numThreads;
  102. CSchedData* const psd = new CSchedData();
  103. if (psd == NULL || !psd->IsValid())
  104. return FALSE;
  105. IF_DEBUG(SCHED)
  106. {
  107. DBGPRINTF(( DBG_CONTEXT, "SchedulerInitialize: inits=%d, uninits=%d\n",
  108. cSchedInits, cSchedUninits));
  109. }
  110. if ( TsIsNtServer() ) {
  111. numThreads = max(NumProcessors(), NUM_SCHEDULE_THREADS_NTS);
  112. } else {
  113. numThreads = NUM_SCHEDULE_THREADS_PWS;
  114. }
  115. numThreads = min(numThreads, MAX_THREADS);
  116. // numThreads = MAX_THREADS;
  117. DBG_ASSERT(numThreads > 0);
  118. for (i = 0; i < numThreads; ++i)
  119. {
  120. CThreadData* ptd = new CThreadData(psd);
  121. if (ptd == NULL || !ptd->IsValid())
  122. {
  123. numThreads = i;
  124. if (ptd != NULL)
  125. ptd->Release();
  126. break;
  127. }
  128. }
  129. if (numThreads == 0)
  130. {
  131. delete psd;
  132. return FALSE;
  133. }
  134. // Kick scheduler threads into life now that everything has been
  135. // initialized.
  136. psd->m_lstThreads.Lock();
  137. for (CListEntry* ple = psd->m_lstThreads.First();
  138. ple != psd->m_lstThreads.HeadNode();
  139. ple = ple->Flink)
  140. {
  141. CThreadData* ptd = CONTAINING_RECORD(ple, CThreadData, m_leThreads);
  142. DBG_ASSERT(ptd->CheckSignature());
  143. ResumeThread(ptd->m_hThreadSelf);
  144. }
  145. psd->m_lstThreads.Unlock();
  146. // Update the global pointer to the scheduler
  147. CSchedData* const psd2 =
  148. (CSchedData*) InterlockedExchangePointer((VOID**)&CSchedData::sm_psd, psd);
  149. DBG_ASSERT(psd2 == NULL);
  150. return TRUE;
  151. } // SchedulerInitialize()
  152. VOID
  153. SchedulerTerminate(
  154. VOID
  155. )
  156. /*++
  157. Routine Description:
  158. Terminates and cleans up the scheduling package. Any items left on the
  159. list are *not* called during cleanup.
  160. --*/
  161. {
  162. // Grab the global pointer, then set it to NULL
  163. CSchedData* const psd =
  164. (CSchedData*) InterlockedExchangePointer((VOID**)&CSchedData::sm_psd, NULL);
  165. DBG_ASSERT(psd == NULL || !psd->m_fShutdown);
  166. ++cSchedUninits;
  167. IF_DEBUG(SCHED)
  168. {
  169. DBGPRINTF(( DBG_CONTEXT, "SchedulerTerminate: inits=%d, uninits=%d\n",
  170. cSchedInits, cSchedUninits));
  171. }
  172. if (psd == NULL || psd->m_fShutdown)
  173. {
  174. // already shutting down
  175. return;
  176. }
  177. psd->Terminate();
  178. }
  179. void
  180. CSchedData::Terminate()
  181. {
  182. HANDLE ahThreadIds[MAX_THREADS];
  183. int i;
  184. CListEntry* ple;
  185. m_fShutdown = TRUE;
  186. const int nMaxTries = 1;
  187. const DWORD dwTimeOut = INFINITE;
  188. for (int iTries = 0; iTries < nMaxTries; ++iTries)
  189. {
  190. int nThreads = 0;
  191. m_lstThreads.Lock();
  192. for (ple = m_lstThreads.First(), i = 0;
  193. ple != m_lstThreads.HeadNode();
  194. ple = ple->Flink, i++)
  195. {
  196. CThreadData* ptd = CONTAINING_RECORD(ple, CThreadData,
  197. m_leThreads);
  198. DBG_ASSERT(ptd->CheckSignature());
  199. // Set the shutdown event once for each thread
  200. DBGPRINTF(( DBG_CONTEXT, "CSchedData::Terminate: iteration %d, "
  201. "notifying %ld\n", iTries, ptd->m_nID));
  202. DBG_REQUIRE( SetEvent(ptd->m_hevtShutdown) );
  203. ahThreadIds[i] = ptd->m_hThreadSelf;
  204. ++nThreads;
  205. }
  206. m_lstThreads.Unlock();
  207. if (nThreads == 0)
  208. break;
  209. // Wait for all the threads to shut down
  210. DWORD dw = WaitForMultipleObjects(nThreads, ahThreadIds,
  211. TRUE, dwTimeOut);
  212. DBGPRINTF(( DBG_CONTEXT, "CSchedData::Terminate: WFMO = %x\n", dw));
  213. }
  214. LockItems();
  215. //
  216. // Delete all of the items that were scheduled, note we do *not*
  217. // call any scheduled items in the list (there shouldn't be any)
  218. //
  219. if ( !m_lstItems.IsEmpty() )
  220. {
  221. DBGPRINTF(( DBG_CONTEXT,
  222. "[CSchedData::Terminate] Warning - Items in schedule list "
  223. "at termination\n" ));
  224. int c = 0;
  225. CListEntry* pleSave = NULL;
  226. for (ple = m_lstItems.First();
  227. ple != m_lstItems.HeadNode();
  228. ple = pleSave)
  229. {
  230. SCHED_ITEM* psiList = CONTAINING_RECORD( ple, SCHED_ITEM,
  231. _ListEntry );
  232. DBG_ASSERT(psiList->CheckSignature());
  233. IF_DEBUG(SCHED)
  234. {
  235. DBGPRINTF(( DBG_CONTEXT,
  236. "[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
  237. psiList,
  238. psiList->_dwSerialNumber,
  239. psiList->_pContext,
  240. psiList->_pfnCallback,
  241. psiList->_siState,
  242. psiList->_dwCallbackThreadId
  243. ));
  244. }
  245. pleSave = ple->Flink;
  246. if (!psiList->FInsideCallbackOnOtherThread())
  247. {
  248. CDoubleList::RemoveEntry( &psiList->_ListEntry );
  249. ple->Flink = NULL;
  250. DeleteSchedItem(psiList);
  251. ++c;
  252. }
  253. }
  254. DBGPRINTF(( DBG_CONTEXT,
  255. "[CSchedData::Terminate] %d items deleted\n", c ));
  256. }
  257. UnlockItems();
  258. Release(); // release the last reference to `this'
  259. } // CSchedData::Terminate()
  260. CSchedData::~CSchedData()
  261. {
  262. DBG_ASSERT(m_lstThreads.IsEmpty());
  263. DBG_ASSERT(m_lstItems.IsEmpty());
  264. DBG_ASSERT(m_cThreads == 0);
  265. DBG_ASSERT(m_cRefs == 0);
  266. sm_lstSchedulers.RemoveEntry(&m_leGlobalList);
  267. CloseHandle(m_hevtNotify);
  268. delete m_pachSchedItems;
  269. CListEntry* pleSave = NULL;
  270. for (CListEntry* ple = m_lstDeadThreads.First();
  271. ple != m_lstDeadThreads.HeadNode();
  272. ple = pleSave)
  273. {
  274. pleSave = ple->Flink;
  275. CThreadData* ptd = CONTAINING_RECORD(ple, CThreadData, m_leThreads);
  276. DBG_ASSERT(ptd->CheckSignature());
  277. delete ptd;
  278. }
  279. m_dwSignature = SIGNATURE_SCHEDDATA_FREE;
  280. }
  281. DWORD
  282. WINAPI
  283. ScheduleWorkItem(
  284. PFN_SCHED_CALLBACK pfnCallback,
  285. PVOID pContext,
  286. DWORD msecTime,
  287. BOOL fPeriodic
  288. )
  289. /*++
  290. Routine Description:
  291. Adds a timed work item to the work list
  292. Arguments:
  293. pfnCallback - Function to call
  294. pContext - Context to pass to the callback
  295. msecTime - number of milliseconds to wait before calling timeout
  296. nPriority - Thread priority to set for work item
  297. Return Value:
  298. zero on failure, non-zero on success. The return value can be used to
  299. remove the scheduled work item.
  300. --*/
  301. {
  302. CSchedData* const psd = CSchedData::Scheduler();
  303. if (psd == NULL)
  304. return 0;
  305. //
  306. // 1. alloc a new scheduler item
  307. //
  308. SCHED_ITEM* psi = psd->NewSchedItem(pfnCallback, pContext, msecTime);
  309. if ( !psi )
  310. {
  311. // unable to create the item - return error cookie '0'
  312. return 0;
  313. }
  314. DWORD dwRet = psi->_dwSerialNumber;
  315. SCHED_OPS siop = ((fPeriodic)? SI_OP_ADD_PERIODIC : SI_OP_ADD);
  316. psi->_siState = sg_rgSchedNextState[siop][SI_IDLE];
  317. //
  318. // 2. Insert the scheduler item into the active scheduler work-items list.
  319. //
  320. psd->LockItems();
  321. psd->InsertIntoWorkItemList( psi);
  322. psd->UnlockItems();
  323. //
  324. // 3. Indicate to scheduler threads that there is one new item on the list
  325. //
  326. DBG_REQUIRE( SetEvent( psd->m_hevtNotify ));
  327. IF_DEBUG(SCHED)
  328. {
  329. DBGPRINTF(( DBG_CONTEXT,
  330. "ScheduleWorkItem: (%d) "
  331. "[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
  332. psd->m_nID,
  333. psi,
  334. psi->_dwSerialNumber,
  335. psi->_pContext,
  336. psi->_pfnCallback,
  337. psi->_siState,
  338. psi->_dwCallbackThreadId
  339. ));
  340. }
  341. return dwRet;
  342. } // ScheduleWorkItem()
  343. BOOL
  344. WINAPI
  345. RemoveWorkItem(
  346. DWORD dwCookie
  347. )
  348. /*++
  349. Routine Description:
  350. Removes a scheduled work item
  351. Arguments:
  352. dwCookie - The return value from a previous call to ScheduleWorkItem
  353. Return Value:
  354. TRUE if the item was found, FALSE if the item was not found.
  355. --*/
  356. {
  357. CSchedData* const psd = CSchedData::Scheduler();
  358. if (psd == NULL)
  359. return FALSE;
  360. SCHED_ITEM* psi = NULL;
  361. BOOL fWait = FALSE;
  362. IF_DEBUG(SCHED)
  363. {
  364. DBGPRINTF(( DBG_CONTEXT, "RemoveWorkItem: cookie=%d sched=%d\n",
  365. dwCookie, psd->m_nID));
  366. }
  367. //
  368. // 1. lock the list
  369. //
  370. psd->LockItems();
  371. //
  372. // 2. Find scheduler item on the list.
  373. //
  374. psi = psd->FindSchedulerItem( dwCookie);
  375. if ( NULL != psi)
  376. {
  377. //
  378. // 3. based on the state of the item take necessary actions.
  379. //
  380. SCHED_ITEM_STATE st =
  381. sg_rgSchedNextState[SI_OP_DELETE][psi->_siState];
  382. psi->_siState = st;
  383. switch ( st)
  384. {
  385. case SI_TO_BE_DELETED:
  386. {
  387. DBGPRINTF(( DBG_CONTEXT,
  388. "SchedItem(%08p) marked to be deleted\n",
  389. psi));
  390. // item will be deleted later.
  391. if (psi->FInsideCallbackOnOtherThread()) {
  392. // need to wait till callback complete
  393. psi->AddEvent();
  394. fWait = TRUE;
  395. break;
  396. }
  397. }
  398. // fallthru
  399. case SI_IDLE:
  400. {
  401. // delete immediately
  402. CDoubleList::RemoveEntry( &psi->_ListEntry );
  403. psi->_ListEntry.Flink = NULL;
  404. IF_DEBUG(SCHED)
  405. {
  406. DBGPRINTF(( DBG_CONTEXT,
  407. "RemoveWorkItem: "
  408. "[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
  409. psi,
  410. psi->_dwSerialNumber,
  411. psi->_pContext,
  412. psi->_pfnCallback,
  413. psi->_siState,
  414. psi->_dwCallbackThreadId
  415. ));
  416. }
  417. psd->DeleteSchedItem(psi);
  418. break;
  419. }
  420. default:
  421. DBG_ASSERT( FALSE);
  422. break;
  423. } // switch()
  424. }
  425. // 4. Unlock the list
  426. psd->UnlockItems();
  427. // 5. Wait for callback event and release the item
  428. if (fWait)
  429. {
  430. LONG l = psi->WaitForEventAndRelease();
  431. IF_DEBUG(SCHED)
  432. {
  433. DBGPRINTF(( DBG_CONTEXT, "RemoveWorkItem: %d "
  434. "WaitForEventAndRelease returned %d.\n",
  435. dwCookie, l));
  436. }
  437. if (l == 0)
  438. psd->DeleteSchedItem(psi);
  439. }
  440. IF_DEBUG(SCHED)
  441. {
  442. if ( NULL == psi)
  443. DBGPRINTF(( DBG_CONTEXT, "RemoveWorkItem: %d not found\n",
  444. dwCookie));
  445. }
  446. // return TRUE if we found the item
  447. return ( NULL != psi);
  448. } // RemoveWorkItem()
  449. DWORD
  450. WINAPI
  451. ScheduleAdjustTime(
  452. DWORD dwCookie,
  453. DWORD msecNewTime
  454. )
  455. /*++
  456. This function finds the scheduler object for given cookie and
  457. changes the interval for next timeout of the item. Returns a
  458. Win32 error code: NO_ERROR => success.
  459. --*/
  460. {
  461. CSchedData* const psd = CSchedData::Scheduler();
  462. if (psd == NULL)
  463. return ERROR_NO_DATA;
  464. DBG_ASSERT( 0 != dwCookie);
  465. psd->LockItems();
  466. // 1. Find the work item for given cookie
  467. SCHED_ITEM* psi = psd->FindSchedulerItem( dwCookie);
  468. if ( NULL != psi)
  469. {
  470. // 2. Remove the item from the list
  471. CDoubleList::RemoveEntry( &psi->_ListEntry );
  472. psi->_ListEntry.Flink = NULL;
  473. // 3. Change the timeout value
  474. psi->ChangeTimeInterval( msecNewTime);
  475. // 4. Recalc expiry time and reinsert into the list of work items.
  476. psi->CalcExpiresTime();
  477. psd->InsertIntoWorkItemList( psi);
  478. IF_DEBUG(SCHED)
  479. {
  480. DBGPRINTF(( DBG_CONTEXT,
  481. "ScheduleAdjustTime: "
  482. "[%8p] ser=%d ctxt=%p fncbk=%p state=%d thrd=%d\n",
  483. psi,
  484. psi->_dwSerialNumber,
  485. psi->_pContext,
  486. psi->_pfnCallback,
  487. psi->_siState,
  488. psi->_dwCallbackThreadId
  489. ));
  490. }
  491. }
  492. psd->UnlockItems();
  493. // 5. Indicate to scheduler threads that there is one new item on the list
  494. if (NULL != psi)
  495. DBG_REQUIRE( SetEvent( psd->m_hevtNotify ));
  496. return ( (NULL != psi) ? NO_ERROR : ERROR_INVALID_PARAMETER);
  497. } // ScheduleAdjustTime()
  498. /************************************************************
  499. * Internal functions of Scheduler
  500. ************************************************************/
  501. VOID
  502. CSchedData::InsertIntoWorkItemList(SCHED_ITEM* psi)
  503. {
  504. SCHED_ITEM* psiList;
  505. CListEntry* ple;
  506. DBG_ASSERT( NULL != psi);
  507. DBG_ASSERT(psi->CheckSignature());
  508. DBG_ASSERT( (psi->_siState == SI_ACTIVE) ||
  509. (psi->_siState == SI_ACTIVE_PERIODIC) ||
  510. (psi->_siState == SI_CALLBACK_PERIODIC ) );
  511. // Assumed that the scheduler list is locked.
  512. DBG_ASSERT(m_lstItems.IsLocked());
  513. //
  514. // Insert the list in order based on expires time
  515. //
  516. for ( ple = m_lstItems.First();
  517. ple != m_lstItems.HeadNode();
  518. ple = ple->Flink )
  519. {
  520. psiList = CONTAINING_RECORD( ple, SCHED_ITEM, _ListEntry );
  521. DBG_ASSERT(psiList->CheckSignature());
  522. if ( psiList->_msecExpires > psi->_msecExpires )
  523. {
  524. break;
  525. }
  526. }
  527. //
  528. // Insert the item psi in front of the item ple
  529. // This should work in whether the list is empty or this is the last item
  530. // on the circular list
  531. //
  532. psi->_ListEntry.Flink = ple;
  533. psi->_ListEntry.Blink = ple->Blink;
  534. ple->Blink->Flink = &psi->_ListEntry;
  535. ple->Blink = &psi->_ListEntry;
  536. return;
  537. } // InsertIntoWorkItemList()
  538. SCHED_ITEM*
  539. CSchedData::FindSchedulerItem(DWORD dwCookie)
  540. {
  541. CListEntry* ple;
  542. SCHED_ITEM* psi = NULL;
  543. // Should be called with the scheduler list locked.
  544. DBG_ASSERT(m_lstItems.IsLocked());
  545. for ( ple = m_lstItems.First();
  546. ple != m_lstItems.HeadNode();
  547. ple = ple->Flink )
  548. {
  549. psi = CONTAINING_RECORD( ple, SCHED_ITEM, _ListEntry );
  550. DBG_ASSERT( psi->CheckSignature() );
  551. if ( dwCookie == psi->_dwSerialNumber )
  552. {
  553. // found the match - return
  554. return ( psi);
  555. }
  556. } // for
  557. return ( NULL);
  558. } // FindSchedulerItem()
  559. unsigned
  560. __stdcall
  561. SchedulerWorkerThread(
  562. void* pvParam
  563. )
  564. /*++
  565. Routine Description:
  566. ThreadProc for scheduler.
  567. Arguments:
  568. Unused.
  569. Return Value:
  570. TRUE if successful, FALSE on error (call GetLastError)
  571. --*/
  572. {
  573. CThreadData* const ptd = (CThreadData*) pvParam;
  574. DBG_ASSERT( ptd != NULL );
  575. CSchedData* const psd = ptd->m_psdOwner;
  576. DBG_ASSERT( psd != NULL );
  577. IF_DEBUG(SCHED)
  578. {
  579. DBGPRINTF(( DBG_CONTEXT, "SchedulerWorkerThread (%d) starting: "
  580. "CThreadData=%p CSchedData=%p, %d\n",
  581. ptd->m_nID,
  582. ptd, psd, psd->m_nID));
  583. }
  584. int cExecuted = 0;
  585. DWORD cmsecWait = INFINITE;
  586. __int64 TickCount;
  587. SCHED_ITEM * psi, * psiExpired;
  588. CListEntry * ple;
  589. BOOL fListLocked = FALSE;
  590. DWORD dwWait;
  591. HRESULT hr;
  592. HANDLE ahEvt[2] = {psd->m_hevtNotify, ptd->m_hevtShutdown};
  593. hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  594. if (hr != S_OK && hr != S_FALSE)
  595. {
  596. DBG_ASSERT(FALSE);
  597. return FALSE;
  598. }
  599. while (!psd->m_fShutdown)
  600. {
  601. DBG_ASSERT(!fListLocked); // the list must be unlocked here
  602. while ( TRUE )
  603. {
  604. MSG msg;
  605. //
  606. // Need to do MsgWait instead of WaitForSingleObject
  607. // to process windows msgs. We now have a window
  608. // because of COM.
  609. //
  610. dwWait = MsgWaitForMultipleObjects( 2,
  611. ahEvt,
  612. FALSE, // wait for anything
  613. cmsecWait,
  614. QS_ALLINPUT );
  615. if (psd->m_fShutdown)
  616. goto exit;
  617. if ( (dwWait == WAIT_OBJECT_0) || // psd->m_hevtNotify
  618. (dwWait == WAIT_OBJECT_0 + 1) || // ptd->m_hevtShutdown
  619. (dwWait == WAIT_TIMEOUT) )
  620. {
  621. break;
  622. }
  623. while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
  624. {
  625. TranslateMessage( &msg );
  626. DispatchMessage( &msg );
  627. }
  628. }
  629. switch (dwWait)
  630. {
  631. default:
  632. DBGPRINTF(( DBG_CONTEXT,
  633. "[Scheduler] Error %d waiting on SchedulerEvent\n",
  634. GetLastError() ));
  635. // Fall through
  636. case WAIT_OBJECT_0:
  637. // Means a new item has been scheduled, reset the timeout, or
  638. // we are shutting down
  639. psd->LockItems();
  640. fListLocked = TRUE;
  641. // Get the timeout value for the first item in the list
  642. if (!psd->m_lstItems.IsEmpty())
  643. {
  644. psi = CONTAINING_RECORD( psd->m_lstItems.First(),
  645. SCHED_ITEM,
  646. _ListEntry );
  647. DBG_ASSERT(psi->CheckSignature());
  648. // Make sure the front item hasn't already expired
  649. TickCount = GetCurrentTimeInMilliseconds();
  650. if (TickCount > psi->_msecExpires)
  651. {
  652. // Run scheduled items
  653. break;
  654. }
  655. // the delay is guaranteed NOT to be > 1<<32
  656. // as per parameter to SCHED_ITEM constructor
  657. cmsecWait = (DWORD)(psi->_msecExpires - TickCount);
  658. }
  659. else
  660. {
  661. cmsecWait = INFINITE;
  662. }
  663. psd->UnlockItems();
  664. fListLocked = FALSE;
  665. // Wait for something else (back to sleep)
  666. continue;
  667. case WAIT_TIMEOUT:
  668. // Run scheduled items
  669. break;
  670. }
  671. // Run scheduled items
  672. while (!psd->m_fShutdown)
  673. {
  674. // Lock the list if needed
  675. if (!fListLocked)
  676. {
  677. psd->LockItems();
  678. fListLocked = TRUE;
  679. }
  680. // No timeout by default (if no items found)
  681. cmsecWait = INFINITE;
  682. if (psd->m_lstItems.IsEmpty())
  683. break;
  684. // Find the first expired work item
  685. TickCount = GetCurrentTimeInMilliseconds();
  686. psiExpired = NULL;
  687. for ( ple = psd->m_lstItems.First();
  688. ple != psd->m_lstItems.HeadNode();
  689. ple = ple->Flink
  690. )
  691. {
  692. psi = CONTAINING_RECORD(ple, SCHED_ITEM, _ListEntry);
  693. DBG_ASSERT(psi->CheckSignature());
  694. if ( ((psi->_siState == SI_ACTIVE) ||
  695. (psi->_siState == SI_ACTIVE_PERIODIC)) )
  696. {
  697. if (TickCount > psi->_msecExpires)
  698. {
  699. // Found Expired Item
  700. psiExpired = psi;
  701. }
  702. else
  703. {
  704. // Since they are in sorted order, once we hit one
  705. // that's not expired, we don't need to look further
  706. cmsecWait = (DWORD)(psi->_msecExpires - TickCount);
  707. }
  708. break;
  709. }
  710. }
  711. // If no expired work items found, go back to sleep
  712. if (psiExpired == NULL)
  713. {
  714. break;
  715. }
  716. // Take care of the found expired work item
  717. SCHED_ITEM_STATE st =
  718. sg_rgSchedNextState[SI_OP_CALLBACK][psiExpired->_siState];
  719. psiExpired->_siState = st;
  720. psiExpired->_dwCallbackThreadId = GetCurrentThreadId();
  721. DBG_ASSERT(st == SI_TO_BE_DELETED || st == SI_CALLBACK_PERIODIC);
  722. // Unlock the list while in the callback
  723. DBG_ASSERT(fListLocked);
  724. psd->UnlockItems();
  725. fListLocked = FALSE;
  726. // While in PERIODIC callback the list is kept unlocked
  727. // leaving the object exposed
  728. IF_DEBUG(SCHED)
  729. {
  730. DBGPRINTF((DBG_CONTEXT,
  731. "SchedulerWorkerThread (%d): starting %scall: "
  732. "ser=%d ctxt=%p fncbk=%p state=%d\n",
  733. ptd->m_nID,
  734. (st == SI_CALLBACK_PERIODIC) ? "periodic " : "",
  735. psiExpired->_dwSerialNumber,
  736. psiExpired->_pContext,
  737. psiExpired->_pfnCallback,
  738. psiExpired->_siState));
  739. }
  740. // Is this still a valid function? There have been problems
  741. // with scheduler clients (such as isatq.dll) getting unloaded,
  742. // without first calling RemoveWorkItem to clean up.
  743. if (IsBadCodePtr(reinterpret_cast<FARPROC>(psiExpired->_pfnCallback)))
  744. {
  745. DWORD dwErr = GetLastError();
  746. IF_DEBUG(SCHED)
  747. {
  748. DBGPRINTF((DBG_CONTEXT,
  749. "SchedulerWorkerThread (%d): "
  750. "invalid callback function %p, error %d\n",
  751. ptd->m_nID,
  752. psiExpired->_pfnCallback, dwErr));
  753. }
  754. psiExpired->_siState = SI_TO_BE_DELETED;
  755. goto relock;
  756. }
  757. __try
  758. {
  759. psiExpired->_pfnCallback(psiExpired->_pContext);
  760. }
  761. __except (EXCEPTION_EXECUTE_HANDLER)
  762. {
  763. DWORD dwErr = GetExceptionCode();
  764. IF_DEBUG(SCHED)
  765. {
  766. DBGPRINTF((DBG_CONTEXT,
  767. "SchedulerWorkerThread (%d): "
  768. "exception %d in callback function %p\n",
  769. ptd->m_nID,
  770. dwErr, psiExpired->_pfnCallback));
  771. }
  772. psiExpired->_siState = SI_TO_BE_DELETED;
  773. }
  774. ++cExecuted;
  775. IF_DEBUG(SCHED)
  776. {
  777. DBGPRINTF((DBG_CONTEXT,
  778. "SchedulerWorkerThread (%d): finished %scall: "
  779. "ser=%d ctxt=%p fncbk=%p state=%d\n",
  780. ptd->m_nID,
  781. (st == SI_CALLBACK_PERIODIC) ? "periodic " : "",
  782. psiExpired->_dwSerialNumber,
  783. psiExpired->_pContext,
  784. psiExpired->_pfnCallback,
  785. psiExpired->_siState));
  786. }
  787. relock:
  788. // Relock the list
  789. DBG_ASSERT(!fListLocked);
  790. psd->LockItems();
  791. fListLocked = TRUE;
  792. psiExpired->_dwCallbackThreadId = 0;
  793. // While in the callback the state can change
  794. if (psiExpired->_siState == SI_TO_BE_DELETED)
  795. {
  796. // User requested delete
  797. // Remove this item from the list
  798. CDoubleList::RemoveEntry( &psiExpired->_ListEntry );
  799. psiExpired->_ListEntry.Flink = NULL;
  800. // While in callback RemoveWorkItem() could have attached
  801. // an event to notify itself when callback is done
  802. if (psiExpired->_hCallbackEvent)
  803. {
  804. // Signal the event after item is gone from the list
  805. SetEvent(psiExpired->_hCallbackEvent);
  806. // RemoveWorkItem() will remove the item
  807. }
  808. else
  809. {
  810. // Get rid of the item
  811. psd->DeleteSchedItem(psiExpired);
  812. }
  813. }
  814. else
  815. {
  816. // no events attached
  817. DBG_ASSERT(psiExpired->_hCallbackEvent == NULL);
  818. // must still remain SI_CALLBACK_PERIODIC unless deleted
  819. DBG_ASSERT(psiExpired->_siState == SI_CALLBACK_PERIODIC);
  820. // NYI: For now remove from the list and reinsert it
  821. CDoubleList::RemoveEntry( &psiExpired->_ListEntry );
  822. psiExpired->_ListEntry.Flink = NULL;
  823. // recalc the expiry time and reinsert into the list
  824. psiExpired->_siState =
  825. sg_rgSchedNextState[SI_OP_ADD_PERIODIC]
  826. [psiExpired->_siState];
  827. psiExpired->CalcExpiresTime();
  828. psd->InsertIntoWorkItemList(psiExpired);
  829. }
  830. // Start looking in the list from the beginning in case
  831. // new items have been added or other threads removed them
  832. } // while
  833. if (fListLocked)
  834. {
  835. psd->UnlockItems();
  836. fListLocked = FALSE;
  837. }
  838. } // while
  839. exit:
  840. CoUninitialize();
  841. // Destroy the thread object
  842. ptd->Release();
  843. return cExecuted;
  844. } // SchedulerWorkerThread()