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.

848 lines
21 KiB

  1. //+----------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (c) Microsoft Corporation 2001
  6. //
  7. // File: kerbscav.cxx
  8. //
  9. // Contents: Scavenger (task automation) code
  10. //
  11. //
  12. // History: 22-April-2001 Created MarkPu
  13. //
  14. //-----------------------------------------------------------------------------
  15. #ifndef WIN32_CHICAGO
  16. extern "C"
  17. {
  18. #include <nt.h>
  19. #include <ntrtl.h>
  20. #include <nturtl.h>
  21. #include <windows.h>
  22. #include <dsysdbg.h>
  23. }
  24. #else
  25. #include <kerb.hxx>
  26. #include <kerbp.h>
  27. #endif
  28. #include <kerbcomm.h>
  29. #include <kerbscav.h>
  30. //
  31. // FESTER: not a good idea to have these as globals, in case the application
  32. // would want multiple scavenger instances. This will do for now.
  33. //
  34. BOOLEAN ScavengerInitialized = FALSE;
  35. RTL_CRITICAL_SECTION ScavengerLock;
  36. HANDLE ScavengerTimerQueue = NULL;
  37. HANDLE ScavengerTimerShutdownEvent = NULL;
  38. LIST_ENTRY ScavengerTaskQueue = {0};
  39. LIST_ENTRY ScavengerDeadPool = {0};
  40. ULONG ScavengerTaskQueueSize = 0;
  41. ULONG ScavengerDeadPoolSize = 0;
  42. #define LockScavengerQueue() RtlEnterCriticalSection( &ScavengerLock )
  43. #define UnlockScavengerQueue() RtlLeaveCriticalSection( &ScavengerLock )
  44. struct SCAVENGER_TASK
  45. {
  46. LIST_ENTRY m_ListEntry;
  47. //
  48. // Periodicity control code
  49. //
  50. DWORD m_InsideTrigger; // Set to the ID of the callback thread
  51. BOOLEAN m_Canceled; // TRUE if task was canceled
  52. BOOLEAN m_Periodic; // TRUE if periodic
  53. LONG m_Interval; // recurrence interval, in milliseconds
  54. //
  55. // Task management
  56. //
  57. HANDLE m_Timer; // Timer handle
  58. ULONG m_Flags; // Timer flags (see CreateTimerQueueTimer)
  59. HANDLE m_ShutdownEvent; // Shutdown event
  60. LONG m_Processing; // Set to TRUE while inside the trigger
  61. KERB_TASK_TRIGGER m_pfnTrigger; // Invocation callback
  62. KERB_TASK_DESTROY m_pfnDestroy; // Destruction callback
  63. void * m_Context; // User-supplied task context
  64. };
  65. typedef SCAVENGER_TASK * PSCAVENGER_TASK;
  66. // ----------------------------------------------------------------------------
  67. //
  68. // Internal scavenger routines
  69. //
  70. // ----------------------------------------------------------------------------
  71. VOID
  72. ScavengerTimerCallback(
  73. IN PVOID Parameter,
  74. IN BOOLEAN Reason
  75. );
  76. //+----------------------------------------------------------------------------
  77. //
  78. // Function: ScavengerFreeTask
  79. //
  80. // Synopsis: Task 'destructor'
  81. //
  82. // Arguments: Task - task to be freed
  83. //
  84. // Returns: Nothing
  85. //
  86. //-----------------------------------------------------------------------------
  87. void
  88. ScavengerFreeTask(
  89. IN PSCAVENGER_TASK Task
  90. )
  91. {
  92. DsysAssert( Task );
  93. if ( Task->m_pfnDestroy ) {
  94. Task->m_pfnDestroy( Task->m_Context );
  95. }
  96. NtClose( Task->m_ShutdownEvent );
  97. MIDL_user_free( Task );
  98. return;
  99. }
  100. //+----------------------------------------------------------------------------
  101. //
  102. // Function: ScavengerPurgeDeadPool
  103. //
  104. // Synopsis: Disposes of items in the deadpool
  105. //
  106. // Arguments: TaskToAvoid - Task to leave hanging around (because
  107. // it corresponds to the current timer callback)
  108. // This parameter can be NULL
  109. //
  110. // Returns: Nothing
  111. //
  112. //-----------------------------------------------------------------------------
  113. void
  114. ScavengerPurgeDeadPool(
  115. IN OPTIONAL PSCAVENGER_TASK TaskToAvoid
  116. )
  117. {
  118. ULONG TasksLeftOver = 0;
  119. LockScavengerQueue();
  120. while ( !IsListEmpty( &ScavengerDeadPool ) &&
  121. TasksLeftOver < ScavengerDeadPoolSize ) {
  122. //
  123. // Get a task out of the list
  124. //
  125. BOOLEAN PutItBack = FALSE;
  126. PSCAVENGER_TASK Task = CONTAINING_RECORD(
  127. RemoveHeadList( &ScavengerDeadPool ),
  128. SCAVENGER_TASK,
  129. m_ListEntry
  130. );
  131. //
  132. // Only canceled tasks are allowed in the deadpool
  133. //
  134. DsysAssert( Task->m_Canceled );
  135. DsysAssert( ScavengerDeadPoolSize > 0 );
  136. ScavengerDeadPoolSize -= 1;
  137. UnlockScavengerQueue();
  138. if ( Task == TaskToAvoid ) {
  139. //
  140. // If this is the task associated with the current callback, skip it
  141. // This routine might be called from inside a timer callback routine,
  142. // and deleting a timer handle from inside its timer callback routine
  143. // leads to devastating consequences
  144. //
  145. PutItBack = TRUE;
  146. } else {
  147. //
  148. // Destroy the timer handle if it still exists
  149. //
  150. if ( Task->m_Timer != NULL ) {
  151. BOOL Success;
  152. Success = DeleteTimerQueueTimer(
  153. ScavengerTimerQueue,
  154. Task->m_Timer,
  155. Task->m_ShutdownEvent
  156. );
  157. DsysAssert( Success || ( GetLastError() == ERROR_IO_PENDING ));
  158. Task->m_Timer = NULL;
  159. }
  160. //
  161. // If the shutdown event is signaled,
  162. // it is safe to dispose of the task;
  163. // Otherwise, someone else will have to garbage collect this one
  164. //
  165. if ( WAIT_OBJECT_0 == WaitForSingleObject(
  166. Task->m_ShutdownEvent,
  167. 0 )) {
  168. ScavengerFreeTask( Task );
  169. } else {
  170. PutItBack = TRUE;
  171. }
  172. }
  173. LockScavengerQueue();
  174. //
  175. // If this is 'our' task, or there was trouble, insert it at the tail
  176. // so we can continue with tasks at the head of the deadpool list
  177. //
  178. if ( PutItBack ) {
  179. InsertTailList( &ScavengerDeadPool, &Task->m_ListEntry );
  180. ScavengerDeadPoolSize += 1;
  181. TasksLeftOver += 1;
  182. }
  183. }
  184. UnlockScavengerQueue();
  185. return;
  186. }
  187. //+----------------------------------------------------------------------------
  188. //
  189. // Function: ScavengerCancelTask
  190. //
  191. // Synopsis: Stops a task's timer for subsequent removal
  192. //
  193. // Arguments: Task - Task to cancel
  194. //
  195. // Returns: Nothing
  196. //
  197. //-----------------------------------------------------------------------------
  198. void
  199. ScavengerCancelTask(
  200. IN PSCAVENGER_TASK Task
  201. )
  202. {
  203. DsysAssert( Task );
  204. KERB_TASK_DESTROY pfnDestroy = NULL;
  205. void * Context = NULL;
  206. LockScavengerQueue();
  207. //
  208. // Only canceled tasks are allowed in the deadpool
  209. //
  210. DsysAssert( Task->m_Canceled );
  211. //
  212. // Move the task from the active task list to the deadpool
  213. //
  214. //
  215. // First, remove the task from the active task list
  216. //
  217. RemoveEntryList( &Task->m_ListEntry );
  218. ScavengerTaskQueueSize -= 1;
  219. //
  220. // Invoke the destruction callback right away, any further manipulation
  221. // of this task by the client is illegal. Do this outside the queue lock.
  222. //
  223. if ( Task->m_pfnDestroy ) {
  224. pfnDestroy = Task->m_pfnDestroy;
  225. Context = Task->m_Context;
  226. Task->m_pfnDestroy = NULL;
  227. }
  228. //
  229. // Finally, put the task on the deadpool
  230. //
  231. InsertTailList( &ScavengerDeadPool, &Task->m_ListEntry );
  232. ScavengerDeadPoolSize += 1;
  233. UnlockScavengerQueue();
  234. if ( pfnDestroy ) {
  235. pfnDestroy( Context );
  236. }
  237. return;
  238. }
  239. //+----------------------------------------------------------------------------
  240. //
  241. // Function: ScavengerAddTask
  242. //
  243. // Synopsis: Common logic involved in scheduling a new task
  244. //
  245. // Arguments: Parameter - Task being scheduled
  246. //
  247. // Returns: STATUS_SUCCESS if happy
  248. // STATUS_ error code otherwise
  249. //
  250. //-----------------------------------------------------------------------------
  251. NTSTATUS
  252. ScavengerAddTask(
  253. IN PSCAVENGER_TASK Task
  254. )
  255. {
  256. NTSTATUS Status = STATUS_SUCCESS;
  257. BOOL Success;
  258. DsysAssert( Task );
  259. LockScavengerQueue();
  260. //
  261. // Assumptions: properly configured task, ready to be scheduled
  262. //
  263. DsysAssert( Task->m_InsideTrigger == 0 );
  264. DsysAssert( !Task->m_Canceled );
  265. DsysAssert( Task->m_Timer == NULL );
  266. DsysAssert( Task->m_ShutdownEvent != NULL );
  267. DsysAssert( Task->m_Processing == FALSE );
  268. //
  269. // Schedule the task by creating its timer
  270. //
  271. Success = CreateTimerQueueTimer(
  272. &Task->m_Timer,
  273. ScavengerTimerQueue,
  274. ScavengerTimerCallback,
  275. Task,
  276. Task->m_Interval,
  277. Task->m_Periodic ? Task->m_Interval : 0,
  278. Task->m_Flags
  279. );
  280. if ( !Success ) {
  281. //
  282. // FESTER: map GetLastError() to an NT status code maybe?
  283. //
  284. Status = STATUS_UNSUCCESSFUL;
  285. // DsysAssert( FALSE );
  286. } else {
  287. InsertHeadList( &ScavengerTaskQueue, &Task->m_ListEntry );
  288. ScavengerTaskQueueSize += 1;
  289. }
  290. UnlockScavengerQueue();
  291. return Status;
  292. }
  293. //+----------------------------------------------------------------------------
  294. //
  295. // Function: ScavengerTimerCallback
  296. //
  297. // Synopsis: Scavenger worker routine
  298. //
  299. // Arguments: Parameter - Task handle
  300. // Reason - see definition of WAITORTIMERCALLBACK
  301. //
  302. // Returns: Nothing
  303. //
  304. //-----------------------------------------------------------------------------
  305. VOID
  306. ScavengerTimerCallback(
  307. IN PVOID Parameter,
  308. IN BOOLEAN Reason
  309. )
  310. {
  311. PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )Parameter;
  312. UNREFERENCED_PARAMETER( Reason );
  313. DsysAssert( Task );
  314. DsysAssert( Reason == TRUE );
  315. DsysAssert( Task->m_pfnTrigger );
  316. //
  317. // Callbacks that step on each others' heels are thrown out
  318. //
  319. if ( FALSE != InterlockedCompareExchange(
  320. &Task->m_Processing,
  321. TRUE,
  322. FALSE )) {
  323. return;
  324. }
  325. //
  326. // Invoke the trigger
  327. //
  328. DsysAssert( Task->m_InsideTrigger == 0 );
  329. DsysAssert( !Task->m_Canceled );
  330. Task->m_InsideTrigger = GetCurrentThreadId();
  331. Task->m_pfnTrigger( Task, Task->m_Context );
  332. Task->m_InsideTrigger = 0;
  333. if ( !Task->m_Periodic ) {
  334. //
  335. // Non-periodic tasks get removed right away
  336. //
  337. Task->m_Canceled = TRUE;
  338. }
  339. //
  340. // If the task has been canceled, move it to the deadpool
  341. //
  342. if ( Task->m_Canceled ) {
  343. ScavengerCancelTask( Task );
  344. } else {
  345. //
  346. // Task has not been canceled, so open it up to timer callbacks
  347. //
  348. InterlockedExchange( &Task->m_Processing, FALSE );
  349. }
  350. //
  351. // A timer callback is a good place to bury some bodies
  352. //
  353. ScavengerPurgeDeadPool( Task );
  354. return;
  355. }
  356. // ----------------------------------------------------------------------------
  357. //
  358. // External scavenger interfaces
  359. //
  360. // ----------------------------------------------------------------------------
  361. //+----------------------------------------------------------------------------
  362. //
  363. // Function: KerbInitializeScavenger
  364. //
  365. // Synopsis: Initializes the scavenger
  366. //
  367. // Arguments: None
  368. //
  369. // Returns: STATUS_SUCCESS if happy
  370. // STATUS_ error code otherwise
  371. //
  372. //-----------------------------------------------------------------------------
  373. NTSTATUS
  374. KerbInitializeScavenger()
  375. {
  376. NTSTATUS Status;
  377. DsysAssert( !ScavengerInitialized );
  378. //
  379. // Task queue and dead pool could be protected by different
  380. // locks, but the amount of time spent inside those locks is minimal,
  381. // so the same lock is used
  382. //
  383. Status = RtlInitializeCriticalSection( &ScavengerLock );
  384. if ( !NT_SUCCESS( Status )) {
  385. return Status;
  386. }
  387. InitializeListHead( &ScavengerTaskQueue );
  388. InitializeListHead( &ScavengerDeadPool );
  389. ScavengerTaskQueueSize = 0;
  390. ScavengerDeadPoolSize = 0;
  391. DsysAssert( ScavengerTimerShutdownEvent == NULL );
  392. Status = NtCreateEvent(
  393. &ScavengerTimerShutdownEvent,
  394. EVENT_QUERY_STATE |
  395. EVENT_MODIFY_STATE |
  396. SYNCHRONIZE,
  397. NULL,
  398. SynchronizationEvent,
  399. FALSE
  400. );
  401. if ( !NT_SUCCESS( Status )) {
  402. goto Error;
  403. }
  404. DsysAssert( ScavengerTimerQueue == NULL );
  405. Status = RtlCreateTimerQueue( &ScavengerTimerQueue );
  406. if ( !NT_SUCCESS( Status )) {
  407. goto Error;
  408. }
  409. //
  410. // We're ready to rock-n-roll
  411. //
  412. ScavengerInitialized = TRUE;
  413. Status = STATUS_SUCCESS;
  414. Cleanup:
  415. return Status;
  416. Error:
  417. DsysAssert( !NT_SUCCESS( Status ));
  418. if ( ScavengerTimerQueue != NULL ) {
  419. RtlDeleteTimerQueue( ScavengerTimerQueue );
  420. ScavengerTimerQueue = NULL;
  421. }
  422. if ( ScavengerTimerShutdownEvent != NULL ) {
  423. NtClose( ScavengerTimerShutdownEvent );
  424. ScavengerTimerShutdownEvent = NULL;
  425. }
  426. RtlDeleteCriticalSection( &ScavengerLock );
  427. ScavengerInitialized = FALSE;
  428. goto Cleanup;
  429. }
  430. //+----------------------------------------------------------------------------
  431. //
  432. // Function: KerbShutdownScavenger
  433. //
  434. // Synopsis: Shuts down the scavenger
  435. //
  436. // Arguments: None
  437. //
  438. // Returns: STATUS_SUCCESS if everything cleaned up properly
  439. // STATUS_ error code otherwise
  440. //
  441. // Note: If errors are encountered, the scavenger will not be destroyed,
  442. // but the task queue will be emptied.
  443. //
  444. //-----------------------------------------------------------------------------
  445. NTSTATUS
  446. KerbShutdownScavenger()
  447. {
  448. NTSTATUS Status;
  449. DsysAssert( ScavengerInitialized );
  450. Status = RtlDeleteTimerQueueEx(
  451. ScavengerTimerQueue,
  452. ScavengerTimerShutdownEvent
  453. );
  454. ScavengerPurgeDeadPool( NULL );
  455. WaitForSingleObject( ScavengerTimerShutdownEvent, INFINITE );
  456. //
  457. // Purge the contents of the scavenger queue
  458. // NOTE: no need to lock the queue anymore, as the timer has been shut down
  459. //
  460. while ( !IsListEmpty( &ScavengerTaskQueue )) {
  461. PSCAVENGER_TASK Task = CONTAINING_RECORD(
  462. RemoveHeadList( &ScavengerTaskQueue ),
  463. SCAVENGER_TASK,
  464. m_ListEntry
  465. );
  466. ScavengerFreeTask( Task );
  467. ScavengerTaskQueueSize -= 1;
  468. }
  469. DsysAssert( ScavengerTaskQueueSize == 0 );
  470. if ( NT_SUCCESS( Status )) {
  471. NtClose( ScavengerTimerShutdownEvent );
  472. ScavengerTimerShutdownEvent = NULL;
  473. ScavengerTimerQueue = NULL;
  474. RtlDeleteCriticalSection( &ScavengerLock );
  475. ScavengerInitialized = FALSE;
  476. }
  477. return Status;
  478. }
  479. //+----------------------------------------------------------------------------
  480. //
  481. // Function: KerbAddScavengerTask
  482. //
  483. // Synopsis: Adds a task to the list of those managed by the scavenger object
  484. //
  485. // Arguments: Periodic - If TRUE, this is to be a recurring task
  486. // Interval - Execution interval in milliseconds
  487. // Flags - WT_ flags (see CreateTimerQueueTimer)
  488. // pfnTrigger - Trigger callback
  489. // pfnDestroy - Destruction callback (OPTIONAL)
  490. // TaskItem - Task context (OPTIONAL)
  491. //
  492. // Returns: STATUS_SUCCESS if everything cleaned up properly
  493. // STATUS_ error code otherwise
  494. //
  495. //-----------------------------------------------------------------------------
  496. NTSTATUS
  497. KerbAddScavengerTask(
  498. IN BOOLEAN Periodic,
  499. IN LONG Interval,
  500. IN ULONG Flags,
  501. IN KERB_TASK_TRIGGER pfnTrigger,
  502. IN KERB_TASK_DESTROY pfnDestroy,
  503. IN void * TaskItem,
  504. OUT OPTIONAL void * * TaskHandle
  505. )
  506. {
  507. NTSTATUS Status;
  508. PSCAVENGER_TASK Task;
  509. DsysAssert( ScavengerInitialized );
  510. //
  511. // Validate the passed in parameters
  512. //
  513. if ( pfnTrigger == NULL ||
  514. ( Periodic && Interval == 0 )) {
  515. // DsysAssert( FALSE && "RTFM: Invalid parameter passed in to KerbAddScavengerTask." );
  516. return STATUS_INVALID_PARAMETER;
  517. }
  518. Task = ( PSCAVENGER_TASK )MIDL_user_allocate( sizeof( SCAVENGER_TASK ));
  519. if ( Task == NULL ) {
  520. return STATUS_INSUFFICIENT_RESOURCES;
  521. }
  522. Task->m_InsideTrigger = 0;
  523. Task->m_Canceled = FALSE;
  524. Task->m_Periodic = Periodic;
  525. Task->m_Interval = Interval;
  526. Task->m_Timer = NULL;
  527. Task->m_Flags = Flags;
  528. Task->m_ShutdownEvent = NULL;
  529. Task->m_Processing = FALSE;
  530. Task->m_pfnTrigger = pfnTrigger;
  531. Task->m_pfnDestroy = pfnDestroy;
  532. Task->m_Context = TaskItem;
  533. Status = NtCreateEvent(
  534. &Task->m_ShutdownEvent,
  535. EVENT_QUERY_STATE |
  536. EVENT_MODIFY_STATE |
  537. SYNCHRONIZE,
  538. NULL,
  539. SynchronizationEvent,
  540. FALSE
  541. );
  542. if ( !NT_SUCCESS( Status )) {
  543. MIDL_user_free( Task );
  544. return Status;
  545. }
  546. Status = ScavengerAddTask( Task );
  547. if ( !NT_SUCCESS( Status )) {
  548. Task->m_pfnDestroy = NULL; // Didn't take ownership yet, caller will destroy
  549. ScavengerFreeTask( Task );
  550. Task = NULL;
  551. }
  552. if ( TaskHandle ) {
  553. *TaskHandle = Task;
  554. }
  555. return Status;
  556. }
  557. //+----------------------------------------------------------------------------
  558. //
  559. // Function: KerbTaskDoItNow
  560. //
  561. // Synopsis: Makes the task fire right away
  562. //
  563. // Arguments: TaskHandle - Task handle
  564. //
  565. // Returns: TRUE if successful, FALSE otherwise
  566. //
  567. // NOTE: this function can only be called from outside a task trigger callback
  568. //
  569. //-----------------------------------------------------------------------------
  570. BOOL
  571. KerbTaskDoItNow(
  572. IN HANDLE TaskHandle
  573. )
  574. {
  575. PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
  576. DsysAssert( Task );
  577. return ChangeTimerQueueTimer( ScavengerTimerQueue, Task->m_Timer, 0, 0 );
  578. }
  579. //+----------------------------------------------------------------------------
  580. //
  581. // Function: KerbTaskIsPeriodic
  582. //
  583. // Synopsis: Tells whether a given task is a periodic task
  584. //
  585. // Arguments: TaskHandle - Task handle
  586. //
  587. // Returns: TRUE if the task is periodic, FALSE otherwise
  588. //
  589. // NOTE: this function can only be called from inside a task trigger callback
  590. //
  591. //-----------------------------------------------------------------------------
  592. BOOLEAN
  593. KerbTaskIsPeriodic(
  594. IN void * TaskHandle
  595. )
  596. {
  597. PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
  598. DsysAssert( Task );
  599. DsysAssert( Task->m_InsideTrigger == GetCurrentThreadId());
  600. return Task->m_Periodic;
  601. }
  602. //+----------------------------------------------------------------------------
  603. //
  604. // Function: KerbTaskGetInterval
  605. //
  606. // Synopsis: Retrieves the interval of a periodic task
  607. //
  608. // Arguments: TaskHandle - Task handle
  609. //
  610. // Returns: Interval associated with the task, in milliseconds
  611. //
  612. // NOTE: this function can only be called from inside a task trigger callback
  613. //
  614. //-----------------------------------------------------------------------------
  615. LONG
  616. KerbTaskGetInterval(
  617. IN void * TaskHandle
  618. )
  619. {
  620. PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
  621. DsysAssert( Task );
  622. DsysAssert( Task->m_InsideTrigger == GetCurrentThreadId());
  623. return Task->m_Interval;
  624. }
  625. //+----------------------------------------------------------------------------
  626. //
  627. // Function: KerbTaskCancel
  628. //
  629. // Synopsis: Cancels the task
  630. //
  631. // Arguments: TaskHandle - Task handle
  632. //
  633. // Returns: Nothing
  634. //
  635. // NOTE: this function can only be called from inside a task trigger callback
  636. //
  637. //-----------------------------------------------------------------------------
  638. void
  639. KerbTaskCancel(
  640. IN void * TaskHandle
  641. )
  642. {
  643. PSCAVENGER_TASK Task = ( PSCAVENGER_TASK )TaskHandle;
  644. DsysAssert( Task );
  645. DsysAssert( Task->m_InsideTrigger == GetCurrentThreadId());
  646. Task->m_Canceled = TRUE;
  647. return;
  648. }