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.

490 lines
12 KiB

  1. /*
  2. Copyright (c) 1992 Microsoft Corporation
  3. Module Name:
  4. scavengr.c
  5. Abstract:
  6. This file implements the scavenger queue management interface.
  7. Author:
  8. Jameel Hyder (microsoft!jameelh)
  9. Revision History:
  10. 25 Jun 1992 Initial Version
  11. Notes: Tab stop: 4
  12. --*/
  13. #define _SCAVENGER_LOCALS
  14. #define FILENUM FILE_SCAVENGR
  15. #include <afp.h>
  16. #include <scavengr.h>
  17. #include <client.h>
  18. #ifdef ALLOC_PRAGMA
  19. #pragma alloc_text( INIT, AfpScavengerInit)
  20. #pragma alloc_text( PAGE, AfpScavengerDeInit)
  21. #endif
  22. /*** AfpScavengerInit
  23. *
  24. * Initialize the scavenger system. This consists of a queue protected by a
  25. * spin lock and timer coupled to a DPC. The scavenger accepts requests to
  26. * schedule a worker after N units of time.
  27. */
  28. NTSTATUS
  29. AfpScavengerInit(
  30. VOID
  31. )
  32. {
  33. BOOLEAN TimerStarted;
  34. LARGE_INTEGER TimerValue;
  35. KeInitializeTimer(&afpScavengerTimer);
  36. INITIALIZE_SPIN_LOCK(&afpScavengerLock);
  37. KeInitializeDpc(&afpScavengerDpc, afpScavengerDpcRoutine, NULL);
  38. TimerValue.QuadPart = AFP_SCAVENGER_TIMER_TICK;
  39. TimerStarted = KeSetTimer(&afpScavengerTimer,
  40. TimerValue,
  41. &afpScavengerDpc);
  42. ASSERT(!TimerStarted);
  43. return STATUS_SUCCESS;
  44. }
  45. /*** AfpScavengerDeInit
  46. *
  47. * De-Initialize the scavenger system. Just cancel the timer.
  48. */
  49. VOID
  50. AfpScavengerDeInit(
  51. VOID
  52. )
  53. {
  54. KeCancelTimer(&afpScavengerTimer);
  55. }
  56. /*** AfpScavengerEnqueue
  57. *
  58. * Here is a thesis on the code that follows.
  59. *
  60. * The scavenger events are maintained as a list which the scavenger thread
  61. * looks at every timer tick. The list is maintained in such a way that only
  62. * the head of the list needs to be updated every tick i.e. the entire list
  63. * is never scanned. The way this is achieved is by keeping delta times
  64. * relative to the previous entry.
  65. *
  66. * Every timer tick, the relative time at the head of the list is decremented.
  67. * When that goes to ZERO, the head of the list is unlinked and dispatched.
  68. *
  69. * To give an example, we have the following events queued at time slots
  70. * X Schedule A after 10 ticks.
  71. * X+3 Schedule B after 5 ticks.
  72. * X+5 Schedule C after 4 ticks.
  73. * X+8 Schedule D after 6 ticks.
  74. *
  75. * So A will schedule at X+10, B at X+8 (X+3+5), C at X+9 (X+5+4) and
  76. * D at X+14 (X+8+6).
  77. *
  78. * The above example covers all the situations.
  79. *
  80. * - NULL List.
  81. * - Inserting at head of list.
  82. * - Inserting in the middle of the list.
  83. * - Appending to the list tail.
  84. *
  85. * The list will look as follows.
  86. *
  87. * BEFORE AFTER
  88. * ------ -----
  89. *
  90. * X Head -->| Head -> A(10) ->|
  91. * A(10)
  92. *
  93. * X+3 Head -> A(7) ->| Head -> B(5) -> A(2) ->|
  94. * B(5)
  95. *
  96. * X+5 Head -> B(3) -> A(2) ->| Head -> B(3) -> C(1) -> A(1) ->|
  97. * C(4)
  98. *
  99. * X+8 Head -> C(1) -> A(1) ->| Head -> C(1) -> A(1) -> D(4) ->|
  100. * D(6)
  101. *
  102. * The granularity is one tick.
  103. *
  104. * LOCKS_ASSUMED: AfpScavengerLock (SPIN)
  105. */
  106. VOID
  107. afpScavengerEnqueue(
  108. IN PSCAVENGERLIST pListNew
  109. )
  110. {
  111. PSCAVENGERLIST pList, *ppList;
  112. LONG DeltaTime = pListNew->scvgr_AbsTime;
  113. // The DeltaTime is adjusted in every pass of the loop to reflect the
  114. // time after the previous entry that the new entry will schedule.
  115. for (ppList = &afpScavengerList;
  116. (pList = *ppList) != NULL;
  117. ppList = &pList->scvgr_Next)
  118. {
  119. if (DeltaTime <= pList->scvgr_RelDelta)
  120. {
  121. pList->scvgr_RelDelta -= DeltaTime;
  122. break;
  123. }
  124. DeltaTime -= pList->scvgr_RelDelta;
  125. }
  126. pListNew->scvgr_RelDelta = DeltaTime;
  127. pListNew->scvgr_Next = pList;
  128. *ppList = pListNew;
  129. }
  130. /*** AfpScavengerScheduleEvent
  131. *
  132. * Insert an event in the scavenger event list. If the list is empty, then
  133. * fire off a timer. The time is specified in ticks. Each tick is currently
  134. * ONE SECOND. It may not be negative.
  135. *
  136. * The granularity is one tick.
  137. */
  138. NTSTATUS
  139. AfpScavengerScheduleEvent(
  140. IN SCAVENGER_ROUTINE Worker, // Routine to invoke when time expires
  141. IN PVOID pContext, // Context to pass to the routine
  142. IN LONG DeltaTime, // Schedule after this much time
  143. IN BOOLEAN fQueue // If TRUE, then worker must be queued
  144. )
  145. {
  146. PSCAVENGERLIST pList = NULL;
  147. KIRQL OldIrql;
  148. NTSTATUS Status = STATUS_SUCCESS;
  149. // Negative DeltaTime is invalid. ZERO is valid which implies immediate action
  150. ASSERT (DeltaTime >= 0);
  151. do
  152. {
  153. pList = (PSCAVENGERLIST)AfpAllocNonPagedMemory(sizeof(SCAVENGERLIST));
  154. if (pList == NULL)
  155. {
  156. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_ERR,
  157. ("AfpScavengerScheduleEvent: malloc Failed\n"));
  158. Status = STATUS_INSUFFICIENT_RESOURCES;
  159. break;
  160. }
  161. AfpInitializeWorkItem(&pList->scvgr_WorkItem,
  162. afpScavengerWorker,
  163. pList);
  164. pList->scvgr_Worker = Worker;
  165. pList->scvgr_Context = pContext;
  166. pList->scvgr_AbsTime = DeltaTime;
  167. pList->scvgr_fQueue = fQueue;
  168. if (DeltaTime == 0)
  169. {
  170. ASSERT (fQueue);
  171. AfpQueueWorkItem(&pList->scvgr_WorkItem);
  172. break;
  173. }
  174. if (!afpScavengerStopped)
  175. {
  176. ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
  177. //
  178. // due to an assumption made elsewhere, it's necessary to check
  179. // this again after holding the spinlock!
  180. //
  181. if (!afpScavengerStopped)
  182. {
  183. afpScavengerEnqueue(pList);
  184. RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
  185. }
  186. else
  187. {
  188. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_ERR,
  189. ("AfpScavengerScheduleEvent: Called after Flush !!\n"));
  190. RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
  191. AfpFreeMemory(pList);
  192. Status = STATUS_UNSUCCESSFUL;
  193. }
  194. }
  195. } while (False);
  196. return Status;
  197. }
  198. /*** AfpScavengerKillEvent
  199. *
  200. * Kill an event that was previously scheduled.
  201. */
  202. BOOLEAN
  203. AfpScavengerKillEvent(
  204. IN SCAVENGER_ROUTINE Worker, // Routine that was scheduled
  205. IN PVOID pContext // Context
  206. )
  207. {
  208. PSCAVENGERLIST pList, *ppList;
  209. KIRQL OldIrql;
  210. ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
  211. // The DeltaTime is adjusted in every pass of the loop to reflect the
  212. // time after the previous entry that the new entry will schedule.
  213. for (ppList = &afpScavengerList;
  214. (pList = *ppList) != NULL;
  215. ppList = &pList->scvgr_Next)
  216. {
  217. if ((pList->scvgr_Worker == Worker) &&
  218. (pList->scvgr_Context == pContext))
  219. {
  220. *ppList = pList->scvgr_Next;
  221. if (pList->scvgr_Next != NULL)
  222. {
  223. pList->scvgr_Next->scvgr_RelDelta += pList->scvgr_RelDelta;
  224. }
  225. break;
  226. }
  227. }
  228. RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
  229. if (pList != NULL)
  230. AfpFreeMemory(pList);
  231. return (pList != NULL);
  232. }
  233. /*** afpScavengerDpcRoutine
  234. *
  235. * This is called in at DISPATCH_LEVEL when the timer expires. The entry at
  236. * the head of the list is decremented and if ZERO unlinked and queued to the
  237. * worker. If the list is non-empty, the timer is fired again.
  238. */
  239. LOCAL VOID
  240. afpScavengerDpcRoutine(
  241. IN PKDPC pKDpc,
  242. IN PVOID pContext,
  243. IN PVOID SystemArgument1,
  244. IN PVOID SystemArgument2
  245. )
  246. {
  247. PSCAVENGERLIST pList;
  248. AFPSTATUS Status;
  249. BOOLEAN TimerStarted;
  250. LARGE_INTEGER TimerValue;
  251. #ifdef PROFILING
  252. TIME TimeS, TimeE;
  253. DWORD NumDispatched = 0;
  254. AfpGetPerfCounter(&TimeS);
  255. #endif
  256. AfpSecondsSinceEpoch++;
  257. if (afpScavengerStopped)
  258. {
  259. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_ERR,
  260. ("afpScavengerDpcRoutine: Entered after flush !!!\n"));
  261. return;
  262. }
  263. if (afpScavengerList != NULL)
  264. {
  265. ACQUIRE_SPIN_LOCK_AT_DPC(&afpScavengerLock);
  266. if (afpScavengerList->scvgr_RelDelta != 0)
  267. (afpScavengerList->scvgr_RelDelta)--;
  268. // We should never be here if we have no work to do
  269. while (afpScavengerList != NULL)
  270. {
  271. // Dispatch all entries that are ready to go
  272. if (afpScavengerList->scvgr_RelDelta == 0)
  273. {
  274. pList = afpScavengerList;
  275. afpScavengerList = pList->scvgr_Next;
  276. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
  277. ("afpScavengerDpcRoutine: Dispatching %lx\n",
  278. pList->scvgr_WorkItem.wi_Worker));
  279. // Release spin lock as the caller might call us back
  280. RELEASE_SPIN_LOCK_FROM_DPC(&afpScavengerLock);
  281. Status = AFP_ERR_QUEUE;
  282. if (!pList->scvgr_fQueue)
  283. {
  284. Status = (*pList->scvgr_Worker)(pList->scvgr_Context);
  285. #ifdef PROFILING
  286. NumDispatched++;
  287. #endif
  288. }
  289. ACQUIRE_SPIN_LOCK_AT_DPC(&afpScavengerLock);
  290. if (Status == AFP_ERR_QUEUE)
  291. {
  292. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
  293. ("afpScavengerDpcRoutine: Queueing %lx\n",
  294. pList->scvgr_WorkItem.wi_Worker));
  295. AfpQueueWorkItem(&pList->scvgr_WorkItem);
  296. }
  297. else if (Status == AFP_ERR_REQUEUE)
  298. {
  299. afpScavengerEnqueue(pList);
  300. }
  301. else AfpFreeMemory(pList);
  302. }
  303. else break;
  304. }
  305. RELEASE_SPIN_LOCK_FROM_DPC(&afpScavengerLock);
  306. }
  307. TimerValue.QuadPart = AFP_SCAVENGER_TIMER_TICK;
  308. TimerStarted = KeSetTimer(&afpScavengerTimer,
  309. TimerValue,
  310. &afpScavengerDpc);
  311. ASSERT(!TimerStarted);
  312. #ifdef PROFILING
  313. AfpGetPerfCounter(&TimeE);
  314. ACQUIRE_SPIN_LOCK_AT_DPC(&AfpStatisticsLock);
  315. AfpServerProfile->perf_ScavengerCount += NumDispatched;
  316. AfpServerProfile->perf_ScavengerTime.QuadPart +=
  317. (TimeE.QuadPart - TimeS.QuadPart);
  318. RELEASE_SPIN_LOCK_FROM_DPC(&AfpStatisticsLock);
  319. #endif
  320. }
  321. /*** AfpScavengerFlushAndStop
  322. *
  323. * Force all entries in the scavenger queue to be dispatched immediately. No
  324. * more queue'ing of scavenger routines is permitted after this. The scavenger
  325. * essentially shuts down. Callable only in the worker context.
  326. */
  327. VOID
  328. AfpScavengerFlushAndStop(
  329. VOID
  330. )
  331. {
  332. PSCAVENGERLIST pList;
  333. KIRQL OldIrql;
  334. ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
  335. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
  336. ("afpScavengerFlushAndStop: Entered\n"));
  337. ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
  338. ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
  339. afpScavengerStopped = True;
  340. KeCancelTimer(&afpScavengerTimer);
  341. if (afpScavengerList != NULL)
  342. {
  343. // Dispatch all entries right away
  344. while (afpScavengerList != NULL)
  345. {
  346. AFPSTATUS Status;
  347. pList = afpScavengerList;
  348. afpScavengerList = pList->scvgr_Next;
  349. // Call the worker with spin-lock held since they expect to be
  350. // called at DPC. We are safe since if the worker tries to
  351. // call AfpScavengerScheduleEvent(), we'll not try to re-acquire
  352. // the lock as afpScavengerStopped is True.
  353. DBGPRINT(DBG_COMP_SCVGR, DBG_LEVEL_INFO,
  354. ("afpScavengerFlushAndStop: Dispatching %lx\n",
  355. pList->scvgr_WorkItem.wi_Worker));
  356. if (!(pList->scvgr_fQueue))
  357. Status = (*pList->scvgr_Worker)(pList->scvgr_Context);
  358. if (pList->scvgr_fQueue ||
  359. (Status == AFP_ERR_QUEUE))
  360. {
  361. // Well do it the hard way, if the worker insists on working
  362. // at non DISPACTH level.
  363. RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
  364. (*pList->scvgr_Worker)(pList->scvgr_Context);
  365. ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
  366. }
  367. AfpFreeMemory(pList);
  368. }
  369. }
  370. RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
  371. }
  372. /*** AfpScavengerWorker
  373. *
  374. * This gets invoked when the scavenger Dpc queues up the routine.
  375. */
  376. LOCAL VOID FASTCALL
  377. afpScavengerWorker(
  378. IN PSCAVENGERLIST pList
  379. )
  380. {
  381. AFPSTATUS Status;
  382. KIRQL OldIrql;
  383. #ifdef PROFILING
  384. TIME TimeS, TimeE;
  385. AfpGetPerfCounter(&TimeS);
  386. #endif
  387. ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
  388. // Call the worker routine
  389. Status = (*pList->scvgr_Worker)(pList->scvgr_Context);
  390. ASSERT (Status != AFP_ERR_QUEUE);
  391. #ifdef PROFILING
  392. AfpGetPerfCounter(&TimeE);
  393. ACQUIRE_SPIN_LOCK(&AfpStatisticsLock, &OldIrql);
  394. AfpServerProfile->perf_ScavengerCount++;
  395. AfpServerProfile->perf_ScavengerTime.QuadPart +=
  396. (TimeE.QuadPart - TimeS.QuadPart);
  397. RELEASE_SPIN_LOCK(&AfpStatisticsLock, OldIrql);
  398. #endif
  399. if (Status == AFP_ERR_REQUEUE)
  400. {
  401. ACQUIRE_SPIN_LOCK(&afpScavengerLock, &OldIrql);
  402. afpScavengerEnqueue(pList);
  403. RELEASE_SPIN_LOCK(&afpScavengerLock, OldIrql);
  404. }
  405. else
  406. {
  407. ASSERT (NT_SUCCESS(Status));
  408. AfpFreeMemory(pList);
  409. }
  410. }
  411.