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.

1139 lines
33 KiB

  1. #include "priv.h"
  2. #include "schedule.h"
  3. // debug stuff for tracking critical section owners.....
  4. #ifdef DEBUG
  5. #define DECLARE_CRITICAL_SECTION(x) CRITICAL_SECTION x; \
  6. DWORD dwThread##x;
  7. #define STATIC_DECLARE_CRITICAL_SECTION(x) static CRITICAL_SECTION x; \
  8. static DWORD dwThread##x;
  9. #define STATIC_INIT_CRITICAL_SECTION(c,x) CRITICAL_SECTION c::x = {0}; \
  10. DWORD c::dwThread##x;
  11. #define ASSERT_CRITICAL_SECTION(x) ASSERT( dwThread##x == GetCurrentThreadId() );
  12. #define OBJECT_ASSERT_CRITICAL_SECTION(o,x) ASSERT( o->dwThread##x == GetCurrentThreadId() );
  13. #define ENTER_CRITICAL_SECTION(x) EnterCriticalSection(&x); \
  14. dwThread##x = GetCurrentThreadId();
  15. #define OBJECT_ENTER_CRITICAL_SECTION(o,x) EnterCriticalSection(&o->x); \
  16. o->dwThread##x = GetCurrentThreadId();
  17. #define LEAVE_CRITICAL_SECTION(x) ASSERT_CRITICAL_SECTION(x); \
  18. LeaveCriticalSection(&x);
  19. #define OBJECT_LEAVE_CRITICAL_SECTION(o,x) OBJECT_ASSERT_CRITICAL_SECTION(o,x); \
  20. LeaveCriticalSection(&o->x);
  21. #else
  22. #define DECLARE_CRITICAL_SECTION(x) CRITICAL_SECTION x;
  23. #define STATIC_DECLARE_CRITICAL_SECTION(x) static CRITICAL_SECTION x;
  24. #define STATIC_INIT_CRITICAL_SECTION(c,x) CRITICAL_SECTION c::x = {0};
  25. #define ASSERT_CRITICAL_SECTION(x)
  26. #define OBJECT_ASSERT_CRITICAL_SECTION(o,x)
  27. #define ENTER_CRITICAL_SECTION(x) EnterCriticalSection(&x);
  28. #define OBJECT_ENTER_CRITICAL_SECTION(o,x) EnterCriticalSection(&o->x);
  29. #define LEAVE_CRITICAL_SECTION(x) LeaveCriticalSection(&x);
  30. #define OBJECT_LEAVE_CRITICAL_SECTION(o,x) LeaveCriticalSection(&o->x);
  31. #endif
  32. #define TF_SCHEDULER 0x20
  33. // struct to hold the details for each task that is to be executed....
  34. struct TaskNode
  35. {
  36. LPRUNNABLETASK pTask;
  37. TASKOWNERID toid;
  38. DWORD dwPriority;
  39. DWORD_PTR dwLParam;
  40. BOOL fSuspended;
  41. };
  42. class CShellTaskScheduler : public IShellTaskScheduler2
  43. {
  44. public:
  45. CShellTaskScheduler( HRESULT * pHr );
  46. ~CShellTaskScheduler();
  47. STDMETHOD (QueryInterface) (REFIID riid, LPVOID * ppvObj );
  48. STDMETHOD_(ULONG, AddRef)( void );
  49. STDMETHOD_(ULONG,Release)( void );
  50. STDMETHOD (AddTask)(IRunnableTask * pTask,
  51. REFTASKOWNERID rtoid,
  52. DWORD_PTR lParam,
  53. DWORD dwPriority );
  54. STDMETHOD (RemoveTasks)( REFTASKOWNERID rtoid,
  55. DWORD_PTR dwLParam,
  56. BOOL fWaitIfRunning );
  57. STDMETHOD (Status)( DWORD dwStatus, DWORD dwThreadTimeout );
  58. STDMETHOD_(UINT, CountTasks)(REFTASKOWNERID rtoid);
  59. STDMETHOD (AddTask2)(IRunnableTask * pTask,
  60. REFTASKOWNERID rtoid,
  61. DWORD_PTR lParam,
  62. DWORD dwPriority,
  63. DWORD grfFlags);
  64. STDMETHOD (MoveTask)(REFTASKOWNERID rtoid,
  65. DWORD_PTR dwLParam,
  66. DWORD dwPriority,
  67. DWORD grfFlags );
  68. protected:
  69. // data held by a task scheduler to refer to the current worker that it has....
  70. struct WorkerData
  71. {
  72. BOOL Init(CShellTaskScheduler *pts);
  73. // this (pThis) is used to pass the controlling
  74. // object back and forth to the thread, so that threads can be moved
  75. // back and forth from objects as they need them.
  76. CShellTaskScheduler * pThis;
  77. #ifdef DEBUG
  78. DWORD dwThreadID;
  79. #endif
  80. };
  81. friend UINT CShellTaskScheduler_ThreadProc( LPVOID pParam );
  82. friend int CALLBACK ListDestroyCallback( LPVOID p, LPVOID pData );
  83. VOID _KillScheduler( BOOL bKillCurTask );
  84. BOOL _WakeScheduler( void );
  85. BOOL _RemoveTasksFromList( REFTASKOWNERID rtoid, DWORD_PTR dwLParam );
  86. // create a worker thread data block that can be associated with a task scheduler....
  87. WorkerData * FetchWorker( void );
  88. // from a worker thread, let go of the scheduler it is associated...
  89. static BOOL ReleaseWorker( WorkerData * pThread );
  90. /***********PERINSTANCE DATA ************/
  91. DECLARE_CRITICAL_SECTION( m_csListLock )
  92. HDPA m_hTaskList;
  93. WorkerData * m_pWorkerThread;
  94. // the currently running task...
  95. TaskNode * m_pRunning;
  96. // a semaphore that counts, so that all waiters canbe released...
  97. HANDLE m_hCurTaskEnded;
  98. DWORD m_dwStatus;
  99. int m_iSignalCurTask; // - tell the thread to signal when the
  100. // current task is finished if non-zero
  101. // the other thread will signal the
  102. // handle as many times as this variable
  103. // holds.
  104. BOOL m_fEmptyQueueAndSleep; // - tell the thread to empty itself and
  105. // go to sleep (usually it is dying....
  106. int m_iGoToSleep; // - tell the tread to go to sleep without emptying the queue
  107. long m_cRef;
  108. #ifdef DEBUG
  109. void AssertForNoOneWaiting( void )
  110. {
  111. // no one should be queued for waiting
  112. ASSERT( m_iSignalCurTask == 0 );
  113. // release the semaphore by zero to get the current count....
  114. LONG lPrevCount = 0;
  115. ReleaseSemaphore( m_hCurTaskEnded, 0, &lPrevCount );
  116. ASSERT( lPrevCount == 0 );
  117. };
  118. #endif
  119. void IWantToKnowWhenCurTaskDone( void )
  120. {
  121. m_iSignalCurTask ++;
  122. };
  123. };
  124. // private messages sent to the scheduler thread...
  125. #define WM_SCH_WAKEUP WM_USER + 0x600
  126. #define WM_SCH_TERMINATE WM_USER + 0x601
  127. STDAPI CShellTaskScheduler_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  128. {
  129. if ( pUnkOuter )
  130. {
  131. return CLASS_E_NOAGGREGATION;
  132. }
  133. HRESULT hr = NOERROR;
  134. CShellTaskScheduler * pScheduler = new CShellTaskScheduler( & hr );
  135. if ( !pScheduler )
  136. {
  137. return E_OUTOFMEMORY;
  138. }
  139. if ( FAILED( hr ))
  140. {
  141. delete pScheduler;
  142. return hr;
  143. }
  144. *ppunk = SAFECAST(pScheduler, IShellTaskScheduler *);
  145. return NOERROR;
  146. }
  147. // Global ExplorerTaskScheduler object that is used by multiple components.
  148. IShellTaskScheduler * g_pTaskScheduler = NULL;
  149. // This is the class factory routine for creating the one and only ExplorerTaskScheduler object.
  150. // We have a static object (g_pTaskScheduler) that everyone who wants to use it shares.
  151. STDAPI CSharedTaskScheduler_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  152. {
  153. HRESULT hr = NOERROR;
  154. if (pUnkOuter)
  155. return CLASS_E_NOAGGREGATION;
  156. ENTERCRITICAL;
  157. if (g_pTaskScheduler)
  158. {
  159. g_pTaskScheduler->AddRef();
  160. }
  161. else
  162. {
  163. hr = CShellTaskScheduler_CreateInstance(NULL, (LPUNKNOWN*)&g_pTaskScheduler, NULL);
  164. if (SUCCEEDED(hr))
  165. {
  166. // set timeout to be 1 minute.....
  167. g_pTaskScheduler->Status( ITSSFLAG_KILL_ON_DESTROY, 1 * 60 * 1000 );
  168. // keep an additional ref for us..
  169. g_pTaskScheduler->AddRef();
  170. }
  171. }
  172. *ppunk = SAFECAST(g_pTaskScheduler, IShellTaskScheduler*);
  173. LEAVECRITICAL;
  174. return hr;
  175. }
  176. STDAPI SHIsThereASystemScheduler( void )
  177. {
  178. return ( g_pTaskScheduler ? S_OK : S_FALSE );
  179. }
  180. // use CoCreateInstance - thread pool removes need for global scheduler
  181. STDAPI SHGetSystemScheduler( LPSHELLTASKSCHEDULER * ppScheduler )
  182. {
  183. if ( !ppScheduler )
  184. {
  185. return E_INVALIDARG;
  186. }
  187. return CSharedTaskScheduler_CreateInstance(NULL, (IUnknown **)ppScheduler, NULL );
  188. }
  189. // use CoCreateInstance - thread pool removes need for global scheduler
  190. STDAPI SHFreeSystemScheduler( void )
  191. {
  192. TraceMsg(TF_SCHEDULER, "SHfss: g_pTaskSched=%x", g_pTaskScheduler);
  193. IShellTaskScheduler * pSched;
  194. ENTERCRITICAL;
  195. pSched = g_pTaskScheduler;
  196. g_pTaskScheduler = NULL;
  197. LEAVECRITICAL;
  198. if ( pSched )
  199. {
  200. // assume the scheduler is empty....
  201. pSched->RemoveTasks( TOID_NULL, ITSAT_DEFAULT_LPARAM, FALSE );
  202. pSched->Release();
  203. }
  204. return NOERROR;
  205. }
  206. #ifdef DEBUG
  207. STDAPI_(void) SHValidateEmptySystemScheduler()
  208. {
  209. if ( g_pTaskScheduler )
  210. {
  211. ASSERT( g_pTaskScheduler->CountTasks( TOID_NULL ) == 0 );
  212. }
  213. }
  214. #endif
  215. int InsertInPriorityOrder( HDPA hTaskList, TaskNode * pNewNode, BOOL fBefore );
  216. int CALLBACK ListDestroyCallback( LPVOID p, LPVOID pData )
  217. {
  218. ASSERT( p != NULL );
  219. if ( ! p )
  220. {
  221. TraceMsg( TF_ERROR, "ListDestroyCallback() - p is NULL!" );
  222. return TRUE;
  223. }
  224. CShellTaskScheduler * pThis = (CShellTaskScheduler *) pData;
  225. ASSERT( pThis );
  226. if ( ! pThis )
  227. {
  228. TraceMsg( TF_ERROR, "ListDestroyCallback() - pThis is NULL!" );
  229. return TRUE;
  230. }
  231. TaskNode * pNode = (TaskNode *) p;
  232. ASSERT( pNode != NULL );
  233. ASSERT( pNode->pTask != NULL );
  234. #ifdef DEBUG
  235. if ( pThis->m_pWorkerThread )
  236. {
  237. // notify the thread that we are emptying the list from here, so remove these
  238. // items from its mem track list
  239. }
  240. #endif
  241. // if it is suspended, kill it. If it is not suspended, then it has
  242. // probably never been started..
  243. if ( pNode->fSuspended )
  244. {
  245. pNode->pTask->Kill( pThis->m_dwStatus == ITSSFLAG_COMPLETE_ON_DESTROY );
  246. }
  247. pNode->pTask->Release();
  248. delete pNode;
  249. return TRUE;
  250. }
  251. STDMETHODIMP CShellTaskScheduler::QueryInterface( REFIID riid, LPVOID * ppvObj )
  252. {
  253. static const QITAB qit[] = {
  254. QITABENT(CShellTaskScheduler, IShellTaskScheduler),
  255. QITABENT(CShellTaskScheduler, IShellTaskScheduler2),
  256. { 0 },
  257. };
  258. return QISearch(this, qit, riid, ppvObj);
  259. }
  260. STDMETHODIMP_ (ULONG) CShellTaskScheduler::AddRef()
  261. {
  262. InterlockedIncrement( &m_cRef );
  263. return m_cRef;
  264. }
  265. STDMETHODIMP_ (ULONG) CShellTaskScheduler::Release()
  266. {
  267. if (0 == m_cRef)
  268. {
  269. AssertMsg(0, TEXT("CShellTaskScheduler::Release called too many times!"));
  270. return 0;
  271. }
  272. if (InterlockedDecrement( &m_cRef ) == 0)
  273. {
  274. delete this;
  275. return 0;
  276. }
  277. return m_cRef;
  278. }
  279. CShellTaskScheduler::CShellTaskScheduler( HRESULT * pHr) : m_cRef(1)
  280. {
  281. InitializeCriticalSection( &m_csListLock );
  282. ASSERT(m_pWorkerThread == NULL);
  283. ASSERT(m_pRunning == NULL);
  284. m_dwStatus = ITSSFLAG_COMPLETE_ON_DESTROY;
  285. // grow queue by five each time...
  286. m_hTaskList = DPA_Create( 5 );
  287. if ( !m_hTaskList )
  288. {
  289. *pHr = E_OUTOFMEMORY;
  290. }
  291. m_hCurTaskEnded = CreateSemaphoreWrap( NULL, 0, 0xffff, NULL );
  292. if ( !m_hCurTaskEnded )
  293. {
  294. *pHr = E_FAIL;
  295. }
  296. DllAddRef();
  297. }
  298. CShellTaskScheduler::~CShellTaskScheduler()
  299. {
  300. // if we don't have a tasklist and semaphore (constructor failure), we can't have a workerthread
  301. ASSERT((m_hTaskList && m_hCurTaskEnded) || !m_pWorkerThread);
  302. // but if we have a task list...
  303. if ( m_hTaskList )
  304. {
  305. EnterCriticalSection( &m_csListLock );
  306. // if we have a background worker thread, then it MUST be doing something as we
  307. // are now in the crit section so it can't go away
  308. if ( m_pWorkerThread )
  309. {
  310. // we tell the object we need to know when it has done with its stuff....
  311. // we reuse the event we already have...
  312. m_fEmptyQueueAndSleep = TRUE;
  313. #ifdef DEBUG
  314. AssertForNoOneWaiting();
  315. #endif
  316. IWantToKnowWhenCurTaskDone();
  317. // tell the cur task to go away.....
  318. TraceMsg(TF_SCHEDULER, "(%x)csts.dtor: call _KillScheduler", GetCurrentThreadId());
  319. _KillScheduler( m_dwStatus == ITSSFLAG_KILL_ON_DESTROY );
  320. // free the thread. At this point there is always
  321. LeaveCriticalSection( &m_csListLock );
  322. TraceMsg(TF_SCHEDULER, "csts.dtor: call u.WFSMT(m_hCurTaskEnded=%x)", m_hCurTaskEnded);
  323. DWORD dwRes = SHWaitForSendMessageThread(m_hCurTaskEnded, INFINITE);
  324. ASSERT(dwRes == WAIT_OBJECT_0);
  325. TraceMsg(TF_SCHEDULER, "csts.dtor: u.WFSMT() done");
  326. ASSERT( !m_pWorkerThread );
  327. }
  328. else
  329. {
  330. LeaveCriticalSection( &m_csListLock );
  331. }
  332. // empty the list incase it is not empty (it should be)
  333. DPA_EnumCallback( m_hTaskList, ListDestroyCallback, this );
  334. DPA_DeleteAllPtrs( m_hTaskList );
  335. DPA_Destroy( m_hTaskList );
  336. m_hTaskList = NULL;
  337. }
  338. if ( m_hCurTaskEnded )
  339. CloseHandle( m_hCurTaskEnded );
  340. DeleteCriticalSection( &m_csListLock );
  341. DllRelease();
  342. }
  343. STDMETHODIMP CShellTaskScheduler::AddTask( IRunnableTask * pTask,
  344. REFTASKOWNERID rtoid,
  345. DWORD_PTR dwLParam,
  346. DWORD dwPriority )
  347. {
  348. return AddTask2(pTask, rtoid, dwLParam, dwPriority, ITSSFLAG_TASK_PLACEINBACK);
  349. }
  350. STDMETHODIMP CShellTaskScheduler::AddTask2( IRunnableTask * pTask,
  351. REFTASKOWNERID rtoid,
  352. DWORD_PTR dwLParam,
  353. DWORD dwPriority,
  354. DWORD grfFlags )
  355. {
  356. if ( !pTask )
  357. return E_INVALIDARG;
  358. HRESULT hr = E_OUTOFMEMORY; // assume failure
  359. TaskNode * pNewNode = new TaskNode;
  360. if ( pNewNode )
  361. {
  362. pNewNode->pTask = pTask;
  363. pTask->AddRef();
  364. pNewNode->toid = rtoid;
  365. pNewNode->dwPriority = dwPriority;
  366. pNewNode->dwLParam = dwLParam;
  367. pNewNode->fSuspended = FALSE;
  368. EnterCriticalSection( &m_csListLock );
  369. int iPos = -1;
  370. if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT)
  371. {
  372. iPos = InsertInPriorityOrder( m_hTaskList, pNewNode, TRUE );
  373. }
  374. else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK)
  375. {
  376. iPos = InsertInPriorityOrder( m_hTaskList, pNewNode, FALSE );
  377. }
  378. if ( iPos != -1 && m_pRunning )
  379. {
  380. if ( m_pRunning->dwPriority < dwPriority )
  381. {
  382. // try to suspend the current task. If this works, the task will
  383. // return to the scheduler with E_PENDING. It will then be added
  384. // suspended in the queue to be Resumed later....
  385. m_pRunning->pTask->Suspend();
  386. }
  387. }
  388. BOOL bRes = FALSE;
  389. if ( iPos != -1 )
  390. {
  391. // get a worker thread and awaken it...
  392. // we do this in the crit section because we need to test m_pWorkerThread and
  393. // to save us from releasing and grabbing it again...
  394. bRes = _WakeScheduler();
  395. #ifdef DEBUG
  396. if ( bRes && m_pWorkerThread )
  397. {
  398. //
  399. // We are putting this memory block in a linked list and it will most likely be freed
  400. // from the background thread. Remove it from the per-thread memory list to avoid
  401. // detecting it as a memory leak.
  402. //
  403. // WARNING - WARNING - WARNING:
  404. // We cannot...
  405. // assume that when pTask is Released it will be deleted, so move it
  406. // to the other thread's memory list.
  407. //
  408. // This will be incorrect some of the time and we don't want to investigate
  409. // fake leaks. -BryanSt
  410. //transfer_to_thread_memlist( m_pWorkerThread->dwThreadID, pNewNode->pTask );
  411. }
  412. #endif
  413. }
  414. LeaveCriticalSection( &m_csListLock );
  415. // we failed to add it to the list
  416. if ( iPos == -1 )
  417. {
  418. // we failed to add it to the list, must have been a memory failure...
  419. pTask->Release(); // for the AddRef above
  420. delete pNewNode;
  421. goto Leave;
  422. }
  423. hr = bRes ? NOERROR : E_FAIL;
  424. }
  425. Leave:
  426. return hr;
  427. }
  428. STDMETHODIMP CShellTaskScheduler::RemoveTasks( REFTASKOWNERID rtoid,
  429. DWORD_PTR dwLParam,
  430. BOOL fWaitIfRunning )
  431. {
  432. BOOL fRemoveAll = IsEqualGUID( TOID_NULL, rtoid );
  433. BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM );
  434. BOOL fWaitOnHandle = FALSE;
  435. // note, this ignores the current
  436. EnterCriticalSection( &m_csListLock );
  437. _RemoveTasksFromList( rtoid, dwLParam );
  438. if ( m_pRunning && ( fWaitIfRunning || m_dwStatus == ITSSFLAG_KILL_ON_DESTROY ))
  439. {
  440. // kill the current task ...
  441. if (( fRemoveAll || IsEqualGUID( rtoid, m_pRunning->toid )) &&
  442. ( fAllItems || dwLParam == m_pRunning->dwLParam ))
  443. {
  444. ASSERT( m_pRunning->pTask );
  445. if ( m_dwStatus == ITSSFLAG_KILL_ON_DESTROY )
  446. {
  447. m_pRunning->pTask->Kill( fWaitIfRunning );
  448. }
  449. // definitive support for waiting until they are done...
  450. // (note, only do it is there is a task running, otherwise we'll sit
  451. // on a handle that will never fire)
  452. if ( fWaitIfRunning )
  453. {
  454. IWantToKnowWhenCurTaskDone();
  455. // don't use this directly outside of the cirtical section because it can change...
  456. ASSERT ( m_iSignalCurTask );
  457. fWaitOnHandle = TRUE;
  458. m_iGoToSleep++;
  459. }
  460. }
  461. }
  462. LeaveCriticalSection( &m_csListLock );
  463. // now wait if we need to......
  464. if ( fWaitOnHandle )
  465. {
  466. DWORD dwRes = SHWaitForSendMessageThread(m_hCurTaskEnded, INFINITE);
  467. ASSERT(dwRes == WAIT_OBJECT_0);
  468. EnterCriticalSection( &m_csListLock );
  469. // Remove tasks that might have been added while the last task was finishing
  470. _RemoveTasksFromList( rtoid, dwLParam );
  471. m_iGoToSleep--;
  472. // See if we need to wake the thread now.
  473. if ( m_iGoToSleep == 0 && DPA_GetPtrCount( m_hTaskList ) > 0 )
  474. _WakeScheduler();
  475. LeaveCriticalSection( &m_csListLock );
  476. }
  477. return NOERROR;
  478. }
  479. BOOL CShellTaskScheduler::_RemoveTasksFromList( REFTASKOWNERID rtoid, DWORD_PTR dwLParam )
  480. {
  481. // assumes that we are already holding the critical section
  482. BOOL fRemoveAll = IsEqualGUID( TOID_NULL, rtoid );
  483. BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM );
  484. int iIndex = 0;
  485. do
  486. {
  487. TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex );
  488. if ( !pNode )
  489. {
  490. break;
  491. }
  492. ASSERT( pNode );
  493. ASSERT( pNode->pTask );
  494. if (( fRemoveAll || IsEqualGUID( pNode->toid, rtoid )) && ( fAllItems || dwLParam == pNode->dwLParam ))
  495. {
  496. // remove it
  497. DPA_DeletePtr( m_hTaskList, iIndex );
  498. if ( pNode->fSuspended )
  499. {
  500. // kill it just incase....
  501. pNode->pTask->Kill( FALSE );
  502. }
  503. pNode->pTask->Release();
  504. delete pNode;
  505. }
  506. else
  507. {
  508. iIndex ++;
  509. }
  510. }
  511. while ( TRUE );
  512. return TRUE;
  513. }
  514. //
  515. // CShellTaskScheduler::MoveTask
  516. //
  517. STDMETHODIMP CShellTaskScheduler::MoveTask( REFTASKOWNERID rtoid,
  518. DWORD_PTR dwLParam,
  519. DWORD dwPriority,
  520. DWORD grfFlags )
  521. {
  522. int iInsert;
  523. int iIndex;
  524. BOOL fMoveAll = IsEqualGUID( TOID_NULL, rtoid );
  525. BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM );
  526. BOOL bMatch = FALSE ;
  527. int iIndexStart;
  528. int iIndexInc;
  529. EnterCriticalSection( &m_csListLock );
  530. // Init direction of search
  531. if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT)
  532. {
  533. iIndexStart = 0;
  534. iInsert = DPA_GetPtrCount( m_hTaskList );
  535. iIndexInc = 1;
  536. }
  537. else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK)
  538. {
  539. iIndexStart = iInsert = DPA_GetPtrCount( m_hTaskList );
  540. iIndexInc = -1;
  541. }
  542. // Find insert point (based on priority)
  543. iIndex = 0;
  544. do
  545. {
  546. TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex );
  547. if ( !pNode )
  548. {
  549. break;
  550. }
  551. if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT)
  552. {
  553. if (pNode->dwPriority <= dwPriority)
  554. {
  555. iInsert = iIndex;
  556. break;
  557. }
  558. }
  559. else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK)
  560. {
  561. if (pNode->dwPriority > dwPriority)
  562. {
  563. iInsert = iIndex;
  564. }
  565. else
  566. {
  567. break;
  568. }
  569. }
  570. iIndex++;
  571. }
  572. while (TRUE);
  573. // Now try and locate any items.
  574. iIndex = iIndexStart;
  575. do
  576. {
  577. TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex );
  578. if ( !pNode )
  579. {
  580. break;
  581. }
  582. if (( fMoveAll || IsEqualGUID( pNode->toid, rtoid )) &&
  583. ( fAllItems || dwLParam == pNode->dwLParam ))
  584. {
  585. bMatch = TRUE;
  586. // Can we move this node?
  587. if ( iIndex != iInsert )
  588. {
  589. int iPos = DPA_InsertPtr( m_hTaskList, iInsert, pNode );
  590. if (iPos != -1)
  591. {
  592. if ( iIndex > iInsert )
  593. {
  594. DPA_DeletePtr( m_hTaskList, iIndex + 1); // Will have shifted one
  595. }
  596. else
  597. {
  598. DPA_DeletePtr( m_hTaskList, iIndex);
  599. }
  600. }
  601. }
  602. }
  603. iIndex += iIndexInc;
  604. }
  605. while ( !bMatch );
  606. LeaveCriticalSection( &m_csListLock );
  607. return (bMatch ? S_OK : S_FALSE);
  608. }
  609. BOOL CShellTaskScheduler::_WakeScheduler( )
  610. {
  611. // assume we are in the object's critsection.....
  612. if ( NULL == m_pWorkerThread )
  613. {
  614. // we need a worker quick ....
  615. m_pWorkerThread = FetchWorker();
  616. }
  617. return ( NULL != m_pWorkerThread );
  618. }
  619. VOID CShellTaskScheduler::_KillScheduler( BOOL bKillCurTask )
  620. {
  621. // assumes that we are already holding the critical section
  622. if ( m_pRunning != NULL && bKillCurTask )
  623. {
  624. ASSERT( m_pRunning->pTask );
  625. // tell the currently running task that it should die
  626. // quickly, because we are a separate thread than the
  627. // one that is running the task, it can be notified
  628. m_pRunning->pTask->Kill( FALSE );
  629. }
  630. }
  631. UINT CShellTaskScheduler_ThreadProc( LPVOID pParam )
  632. {
  633. // make sure we have a message QUEUE // BOGUS - why do we need this?
  634. MSG msg;
  635. PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );
  636. ASSERT( pParam );
  637. HRESULT hrInit = SHCoInitialize();
  638. CShellTaskScheduler::WorkerData * pWorker = (CShellTaskScheduler::WorkerData *) pParam;
  639. DWORD dwRes = 0;
  640. TraceMsg(TF_SCHEDULER, "(?%x)ShellTaskScheduler::Thread Started", GetCurrentThreadId());
  641. #ifdef DEBUG
  642. pWorker->dwThreadID = GetCurrentThreadId();
  643. #endif
  644. // figure out who we are attatched to (where the queue is we get tasks from)
  645. CShellTaskScheduler * pThis = pWorker->pThis;
  646. // we must always have a valid parent object at this point....
  647. ASSERT( pThis && IS_VALID_WRITE_PTR( pThis, CShellTaskScheduler ));
  648. do
  649. {
  650. MSG msg;
  651. HRESULT hr = NOERROR;
  652. TaskNode * pTask = NULL;
  653. OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );
  654. // this means we are being told to quit...
  655. if ( pThis->m_fEmptyQueueAndSleep )
  656. {
  657. // we are being told to empty the queue .....
  658. DPA_EnumCallback( pThis->m_hTaskList, ListDestroyCallback, pThis );
  659. DPA_DeleteAllPtrs( pThis->m_hTaskList );
  660. }
  661. else if ( !pThis->m_iGoToSleep )
  662. {
  663. // get the first item...
  664. pTask = (TaskNode *) DPA_GetPtr( pThis->m_hTaskList, 0 );
  665. }
  666. if ( pTask )
  667. {
  668. // remove from the list...
  669. DPA_DeletePtr( pThis->m_hTaskList, 0 );
  670. }
  671. pThis->m_pRunning = pTask;
  672. OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );
  673. if ( pTask == NULL )
  674. {
  675. // cache the scheduler pointer, as we need it to leave the crit section
  676. CShellTaskScheduler * pScheduler = pThis;
  677. // queue is empty, go back on the thread pool.....
  678. // we are about to enter a deep deep sleep/coma, so remove us from the object....
  679. OBJECT_ENTER_CRITICAL_SECTION( pScheduler, m_csListLock );
  680. HANDLE hSleep = pThis->m_fEmptyQueueAndSleep ? pThis->m_hCurTaskEnded : NULL;
  681. BOOL fEmptyAndLeave = pThis->m_fEmptyQueueAndSleep;
  682. // make sure they didn't just add something to the queue, or have we been asked to go to sleep
  683. if ( pThis->m_iGoToSleep || DPA_GetPtrCount( pThis->m_hTaskList ) == 0)
  684. {
  685. if ( CShellTaskScheduler::ReleaseWorker( pWorker ))
  686. {
  687. pThis = NULL;
  688. }
  689. }
  690. OBJECT_LEAVE_CRITICAL_SECTION( pScheduler, m_csListLock );
  691. if ( pThis && !fEmptyAndLeave )
  692. {
  693. // they must have added something at the last moment...
  694. continue;
  695. }
  696. // we are being emptied, tell them we are no longer attatched....
  697. if ( hSleep )
  698. {
  699. ReleaseSemaphore( hSleep, 1, NULL);
  700. }
  701. break;
  702. }
  703. else
  704. {
  705. #ifndef DEBUG
  706. //__try
  707. {
  708. #endif
  709. if ( pTask->fSuspended )
  710. {
  711. pTask->fSuspended = FALSE;
  712. hr = pTask->pTask->Resume();
  713. }
  714. else
  715. {
  716. // run the task...
  717. hr = pTask->pTask->Run( );
  718. }
  719. #ifndef DEBUG
  720. }
  721. //__except( EXCEPTION_EXECUTE_HANDLER )
  722. // {
  723. // ignore it.... and pray we are fine...
  724. //}
  725. // __endexcept
  726. #endif
  727. BOOL fEmptyQueue;
  728. OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );
  729. {
  730. pThis->m_pRunning = NULL;
  731. // check to see if we have been asked to notify them
  732. // on completion....
  733. // NOTE: the NOT clause is needed so that we release ourselves
  734. // NOTE: and signal them at the right point, if we do it here,
  735. // NOTE: they leave us stranded, delete the crit section and
  736. // NOTE: we fault.
  737. if ( pThis->m_iSignalCurTask && !pThis->m_fEmptyQueueAndSleep )
  738. {
  739. LONG lPrevCount = 0;
  740. // release all those that are waiting. (we are using a semaphore
  741. // because we are a free threaded object and God knows how many
  742. // threads are waiting, and he passed on the information in the
  743. // iSignalCurTask variable
  744. ReleaseSemaphore( pThis->m_hCurTaskEnded, pThis->m_iSignalCurTask, &lPrevCount );
  745. // reset the count.
  746. pThis->m_iSignalCurTask = 0;
  747. }
  748. fEmptyQueue = pThis->m_fEmptyQueueAndSleep;
  749. }
  750. OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );
  751. if ( hr != E_PENDING || fEmptyQueue )
  752. {
  753. ULONG cRef = pTask->pTask->Release();
  754. delete pTask;
  755. pTask = NULL;
  756. }
  757. // empty the message queue...
  758. while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
  759. {
  760. {
  761. #ifdef DEBUG
  762. if (msg.message == WM_ENDSESSION)
  763. TraceMsg(TF_SCHEDULER, "(?%x)csts.tp: peek #2 got WM_ENDESSION", GetCurrentThreadId());
  764. #endif
  765. TranslateMessage( &msg );
  766. DispatchMessage( &msg );
  767. }
  768. }
  769. ASSERT( pThis && IS_VALID_WRITE_PTR( pThis, CShellTaskScheduler ));
  770. // the task must have been suspended because a higher priority
  771. // task has been added to the queue..... (this only works if the
  772. // task supports the Suspend() method).
  773. if ( hr == E_PENDING && pTask && !fEmptyQueue )
  774. {
  775. // put the task on the Suspended Queue ....
  776. pTask->fSuspended = TRUE;
  777. OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );
  778. int iIndex = InsertInPriorityOrder( pThis->m_hTaskList, pTask, TRUE );
  779. OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );
  780. if ( iIndex == -1 )
  781. {
  782. // we are so low on memory, kill it...
  783. pTask->pTask->Kill( FALSE );
  784. pTask->pTask->Release();
  785. delete pTask;
  786. }
  787. pTask = NULL;
  788. }
  789. }
  790. }
  791. while ( TRUE );
  792. TraceMsg(TF_SCHEDULER, "(?%x)ShellTaskScheduler::Thread Ended", GetCurrentThreadId());
  793. SHCoUninitialize(hrInit);
  794. return 0;
  795. }
  796. STDMETHODIMP CShellTaskScheduler::Status( DWORD dwStatus, DWORD dwThreadTimeout )
  797. {
  798. m_dwStatus = dwStatus & ITSSFLAG_FLAGS_MASK;
  799. if ( dwThreadTimeout != ITSS_THREAD_TIMEOUT_NO_CHANGE )
  800. {
  801. /*
  802. * We don't support thread termination or pool timeout any more
  803. if ( dwStatus & ITSSFLAG_THREAD_TERMINATE_TIMEOUT )
  804. {
  805. m_dwThreadRlsKillTimeout = dwThreadTimeout;
  806. }
  807. else if ( dwStatus & ITSSFLAG_THREAD_POOL_TIMEOUT )
  808. {
  809. CShellTaskScheduler::s_dwComaTimeout = dwThreadTimeout;
  810. }
  811. */
  812. }
  813. return NOERROR;
  814. }
  815. STDMETHODIMP_(UINT) CShellTaskScheduler::CountTasks(REFTASKOWNERID rtoid)
  816. {
  817. UINT iMatch = 0;
  818. BOOL fMatchAll = IsEqualGUID( TOID_NULL, rtoid );
  819. ENTER_CRITICAL_SECTION( m_csListLock );
  820. if ( fMatchAll )
  821. {
  822. iMatch = DPA_GetPtrCount( m_hTaskList );
  823. }
  824. else
  825. {
  826. int iIndex = 0;
  827. do
  828. {
  829. TaskNode * pNode = (TaskNode * )DPA_GetPtr( m_hTaskList, iIndex ++ );
  830. if ( !pNode )
  831. {
  832. break;
  833. }
  834. if ( IsEqualGUID( pNode->toid, rtoid ))
  835. {
  836. iMatch ++;
  837. }
  838. }
  839. while ( TRUE );
  840. }
  841. if ( m_pRunning )
  842. {
  843. if ( fMatchAll || IsEqualGUID( rtoid, m_pRunning->toid ))
  844. {
  845. iMatch ++;
  846. }
  847. }
  848. LEAVE_CRITICAL_SECTION( m_csListLock );
  849. return iMatch;
  850. }
  851. ////////////////////////////////////////////////////////////////////////////////////
  852. int InsertInPriorityOrder( HDPA hTaskList, TaskNode * pNewNode, BOOL fStart )
  853. {
  854. // routine assumes that we are thread safe, therfore grab the crit-section
  855. // prior to calling this function
  856. int iPos = -1;
  857. int iIndex = 0;
  858. do
  859. {
  860. TaskNode * pNode = (TaskNode *) DPA_GetPtr( hTaskList, iIndex );
  861. if ( !pNode )
  862. {
  863. break;
  864. }
  865. // the fStart allows us to either add it before other tasks of the same
  866. // priority or after.
  867. if ((( pNode->dwPriority < pNewNode->dwPriority ) && !fStart ) || (( pNode->dwPriority <= pNewNode->dwPriority ) && fStart ))
  868. {
  869. iPos = DPA_InsertPtr( hTaskList, iIndex, pNewNode );
  870. break;
  871. }
  872. iIndex ++;
  873. }
  874. while ( TRUE );
  875. if ( iPos == -1 )
  876. {
  877. // add item to end of list...
  878. iPos = DPA_AppendPtr( hTaskList, pNewNode );
  879. }
  880. return iPos;
  881. }
  882. CShellTaskScheduler::WorkerData * CShellTaskScheduler::FetchWorker()
  883. {
  884. WorkerData * pWorker = new WorkerData;
  885. if ( pWorker )
  886. {
  887. // call to Shlwapi thread pool
  888. if ( pWorker->Init(this) && SHQueueUserWorkItem( (LPTHREAD_START_ROUTINE)CShellTaskScheduler_ThreadProc,
  889. pWorker,
  890. 0,
  891. (DWORD_PTR)NULL,
  892. (DWORD_PTR *)NULL,
  893. "browseui.dll",
  894. TPS_LONGEXECTIME | TPS_DEMANDTHREAD
  895. ) )
  896. {
  897. return pWorker;
  898. }
  899. else
  900. delete pWorker;
  901. }
  902. return NULL;
  903. }
  904. // used by main thread proc to release its link the the task scheduler because it
  905. // has run out of things to do....
  906. BOOL CShellTaskScheduler::ReleaseWorker( WorkerData * pWorker )
  907. {
  908. ASSERT( pWorker && IS_VALID_WRITE_PTR( pWorker, WorkerData ));
  909. CShellTaskScheduler * pThis = pWorker->pThis;
  910. OBJECT_ASSERT_CRITICAL_SECTION( pThis, m_csListLock );
  911. ASSERT( pWorker && IS_VALID_WRITE_PTR( pWorker, CShellTaskScheduler::WorkerData ));
  912. if ( DPA_GetPtrCount( pThis->m_hTaskList ) > 0 )
  913. {
  914. // something was added to the queue at the last minute....
  915. return FALSE;
  916. }
  917. // we assume we have entered the critsection of pThis
  918. pThis->m_pWorkerThread = NULL;
  919. pWorker->pThis = NULL;
  920. delete pWorker;
  921. return TRUE;
  922. }
  923. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  924. BOOL CShellTaskScheduler::WorkerData::Init(CShellTaskScheduler *pts)
  925. {
  926. pThis = pts;
  927. return TRUE;
  928. }