58ac9a67ce88aa18283ab376a495e7a99d455db8
[reactos.git] / reactos / ntoskrnl / config / cmdelay.c
1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/config/cmdelay.c
5 * PURPOSE: Routines for handling delay close and allocate.
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
7 */
8
9 /* INCLUDES ******************************************************************/
10
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14
15 /* GLOBALS *******************************************************************/
16
17 WORK_QUEUE_ITEM CmpDelayDerefKCBWorkItem;
18
19 ULONG CmpDelayedCloseSize = 2048;
20 ULONG CmpDelayedCloseElements;
21 KGUARDED_MUTEX CmpDelayedCloseTableLock;
22 BOOLEAN CmpDelayCloseWorkItemActive;
23 WORK_QUEUE_ITEM CmpDelayCloseWorkItem;
24 LIST_ENTRY CmpDelayedLRUListHead;
25 ULONG CmpDelayCloseIntervalInSeconds = 5;
26 KDPC CmpDelayCloseDpc;
27 KTIMER CmpDelayCloseTimer;
28
29 KGUARDED_MUTEX CmpDelayDerefKCBLock;
30 BOOLEAN CmpDelayDerefKCBWorkItemActive;
31 LIST_ENTRY CmpDelayDerefKCBListHead;
32 ULONG CmpDelayDerefKCBIntervalInSeconds = 5;
33 KDPC CmpDelayDerefKCBDpc;
34 KTIMER CmpDelayDerefKCBTimer;
35
36 /* FUNCTIONS *****************************************************************/
37
38 VOID
39 NTAPI
40 CmpDelayCloseDpcRoutine(IN PKDPC Dpc,
41 IN PVOID DeferredContext,
42 IN PVOID SystemArgument1,
43 IN PVOID SystemArgument2)
44 {
45 /* Sanity check */
46 ASSERT(CmpDelayCloseWorkItemActive);
47
48 /* Queue the work item */
49 ExQueueWorkItem(&CmpDelayCloseWorkItem, DelayedWorkQueue);
50 }
51
52 VOID
53 NTAPI
54 CmpDelayCloseWorker(IN PVOID Context)
55 {
56 PCM_DELAYED_CLOSE_ENTRY ListEntry;
57 ULONG i, ConvKey;
58 PAGED_CODE();
59
60 /* Sanity check */
61 ASSERT(CmpDelayCloseWorkItemActive);
62
63 /* Lock the registry */
64 CmpLockRegistry();
65
66 /* Acquire the delayed close table lock */
67 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
68
69 /* Iterate */
70 for (i = 0; i < (CmpDelayedCloseSize >> 2); i++)
71 {
72 /* Break out of the loop if there is nothing to process */
73 if (CmpDelayedCloseElements <= CmpDelayedCloseSize) break;
74
75 /* Sanity check */
76 ASSERT(!IsListEmpty(&CmpDelayedLRUListHead));
77
78 /* Get the entry */
79 ListEntry = CONTAINING_RECORD(CmpDelayedLRUListHead.Blink,
80 CM_DELAYED_CLOSE_ENTRY,
81 DelayedLRUList);
82
83 /* Save the ConvKey value of the KCB */
84 ConvKey = ListEntry->KeyControlBlock->ConvKey;
85
86 /* Release the delayed close table lock */
87 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
88
89 /* Acquire the KCB lock */
90 CmpAcquireKcbLockExclusiveByKey(ConvKey);
91
92 /* Reacquire the delayed close table lock */
93 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
94
95 /* Get the entry */
96 ListEntry = CONTAINING_RECORD(CmpDelayedLRUListHead.Blink,
97 CM_DELAYED_CLOSE_ENTRY,
98 DelayedLRUList);
99
100 /* Is the entry we have still the first one? */
101 if (CmpDelayedCloseElements <= CmpDelayedCloseSize)
102 {
103 /* No, someone already inserted an entry there */
104 CmpReleaseKcbLockByKey(ConvKey);
105 break;
106 }
107
108 /* Is it a different entry? */
109 if (ConvKey != ListEntry->KeyControlBlock->ConvKey)
110 {
111 /* Release the delayed close table lock */
112 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
113
114 /* Release the KCB lock */
115 CmpReleaseKcbLockByKey(ConvKey);
116
117 /* Reacquire the delayed close table lock */
118 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
119
120 /* Iterate again */
121 continue;
122 }
123
124 /* Remove it from the end of the list */
125 ListEntry =
126 (PCM_DELAYED_CLOSE_ENTRY)RemoveTailList(&CmpDelayedLRUListHead);
127
128 /* Get the containing entry */
129 ListEntry = CONTAINING_RECORD(ListEntry,
130 CM_DELAYED_CLOSE_ENTRY,
131 DelayedLRUList);
132
133 /* Process the entry */
134 if ((ListEntry->KeyControlBlock->RefCount) ||
135 (ListEntry->KeyControlBlock->DelayedCloseIndex))
136 {
137 /* Add it to the beginning of the list */
138 InsertHeadList(&CmpDelayedLRUListHead, &ListEntry->DelayedLRUList);
139
140 /* Release the delayed close table lock */
141 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
142 }
143 else
144 {
145 /* Release the delayed close table lock */
146 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
147
148 /* Zero out the DelayCloseEntry pointer */
149 ListEntry->KeyControlBlock->DelayCloseEntry = NULL;
150
151 /* Cleanup the KCB cache */
152 CmpCleanUpKcbCacheWithLock(ListEntry->KeyControlBlock, FALSE);
153
154 /* Free the delay item */
155 CmpFreeDelayItem(ListEntry);
156
157 /* Decrement delayed close elements count */
158 InterlockedDecrement((PLONG)&CmpDelayedCloseElements);
159 }
160
161 /* Release the KCB lock */
162 CmpReleaseKcbLockByKey(ConvKey);
163
164 /* Reacquire the delayed close table lock */
165 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
166 }
167
168 if (CmpDelayedCloseElements <= CmpDelayedCloseSize)
169 {
170 /* We're not active anymore */
171 CmpDelayCloseWorkItemActive = FALSE;
172 }
173 else
174 {
175 /* We didn't process all things, so reschedule for the next time */
176 CmpArmDelayedCloseTimer();
177 }
178
179 /* Release the delayed close table lock */
180 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
181
182 /* Unlock the registry */
183 CmpUnlockRegistry();
184 }
185
186 VOID
187 NTAPI
188 INIT_FUNCTION
189 CmpInitializeDelayedCloseTable(VOID)
190 {
191
192 /* Setup the delayed close lock */
193 KeInitializeGuardedMutex(&CmpDelayedCloseTableLock);
194
195 /* Setup the work item */
196 ExInitializeWorkItem(&CmpDelayCloseWorkItem, CmpDelayCloseWorker, NULL);
197
198 /* Setup the list head */
199 InitializeListHead(&CmpDelayedLRUListHead);
200
201 /* Setup the DPC and its timer */
202 KeInitializeDpc(&CmpDelayCloseDpc, CmpDelayCloseDpcRoutine, NULL);
203 KeInitializeTimer(&CmpDelayCloseTimer);
204 }
205
206 VOID
207 NTAPI
208 CmpDelayDerefKCBDpcRoutine(IN PKDPC Dpc,
209 IN PVOID DeferredContext,
210 IN PVOID SystemArgument1,
211 IN PVOID SystemArgument2)
212 {
213 /* Sanity check */
214 ASSERT(CmpDelayDerefKCBWorkItemActive);
215
216 /* Queue the work item */
217 ExQueueWorkItem(&CmpDelayDerefKCBWorkItem, DelayedWorkQueue);
218 }
219
220 VOID
221 NTAPI
222 CmpDelayDerefKCBWorker(IN PVOID Context)
223 {
224 PCM_DELAY_DEREF_KCB_ITEM Entry;
225 PAGED_CODE();
226
227 /* Sanity check */
228 ASSERT(CmpDelayDerefKCBWorkItemActive);
229
230 /* Lock the registry and and list lock */
231 CmpLockRegistry();
232 KeAcquireGuardedMutex(&CmpDelayDerefKCBLock);
233
234 /* Check if the list is empty */
235 while (!IsListEmpty(&CmpDelayDerefKCBListHead))
236 {
237 /* Grab an entry */
238 Entry = (PVOID)RemoveHeadList(&CmpDelayDerefKCBListHead);
239
240 /* We can release the lock now */
241 KeReleaseGuardedMutex(&CmpDelayDerefKCBLock);
242
243 /* Now grab the actual entry */
244 Entry = CONTAINING_RECORD(Entry, CM_DELAY_DEREF_KCB_ITEM, ListEntry);
245 Entry->ListEntry.Flink = Entry->ListEntry.Blink = NULL;
246
247 /* Dereference and free */
248 CmpDereferenceKeyControlBlock(Entry->Kcb);
249 CmpFreeDelayItem(Entry);
250
251 /* Lock the list again */
252 KeAcquireGuardedMutex(&CmpDelayDerefKCBLock);
253 }
254
255 /* We're done */
256 CmpDelayDerefKCBWorkItemActive = FALSE;
257 KeReleaseGuardedMutex(&CmpDelayDerefKCBLock);
258 CmpUnlockRegistry();
259 }
260
261 VOID
262 NTAPI
263 INIT_FUNCTION
264 CmpInitDelayDerefKCBEngine(VOID)
265 {
266 /* Initialize lock and list */
267 KeInitializeGuardedMutex(&CmpDelayDerefKCBLock);
268 InitializeListHead(&CmpDelayDerefKCBListHead);
269
270 /* Setup the work item */
271 ExInitializeWorkItem(&CmpDelayDerefKCBWorkItem,
272 CmpDelayDerefKCBWorker,
273 NULL);
274
275 /* Setup the DPC and timer for it */
276 KeInitializeDpc(&CmpDelayDerefKCBDpc, CmpDelayDerefKCBDpcRoutine, NULL);
277 KeInitializeTimer(&CmpDelayDerefKCBTimer);
278 }
279
280 VOID
281 NTAPI
282 CmpDelayDerefKeyControlBlock(IN PCM_KEY_CONTROL_BLOCK Kcb)
283 {
284 LONG OldRefCount, NewRefCount;
285 LARGE_INTEGER Timeout;
286 PCM_DELAY_DEREF_KCB_ITEM Entry;
287 PAGED_CODE();
288 CMTRACE(CM_REFERENCE_DEBUG,
289 "%s - Dereferencing KCB: %p\n", __FUNCTION__, Kcb);
290
291 /* Get the previous reference count */
292 OldRefCount = *(PLONG)&Kcb->RefCount;
293 NewRefCount = OldRefCount - 1;
294 if (((NewRefCount & 0xFFFF) > 0) &&
295 (InterlockedCompareExchange((PLONG)&Kcb->RefCount,
296 NewRefCount,
297 OldRefCount) == OldRefCount))
298 {
299 /* KCB still had references, so we're done */
300 return;
301 }
302
303 /* Allocate a delay item */
304 Entry = CmpAllocateDelayItem();
305 if (!Entry) return;
306
307 /* Set the KCB */
308 Entry->Kcb = Kcb;
309
310 /* Acquire the delayed deref table lock */
311 KeAcquireGuardedMutex(&CmpDelayDerefKCBLock);
312
313 /* Insert the entry into the list */
314 InsertTailList(&CmpDelayDerefKCBListHead, &Entry->ListEntry);
315
316 /* Check if we need to enable anything */
317 if (!CmpDelayDerefKCBWorkItemActive)
318 {
319 /* Yes, we have no work item, setup the interval */
320 CmpDelayDerefKCBWorkItemActive = TRUE;
321 Timeout.QuadPart = CmpDelayDerefKCBIntervalInSeconds * -10000000;
322 KeSetTimer(&CmpDelayDerefKCBTimer, Timeout, &CmpDelayDerefKCBDpc);
323 }
324
325 /* Release the table lock */
326 KeReleaseGuardedMutex(&CmpDelayDerefKCBLock);
327 }
328
329 VOID
330 NTAPI
331 CmpArmDelayedCloseTimer(VOID)
332 {
333 LARGE_INTEGER Timeout;
334 PAGED_CODE();
335
336 /* Set the worker active */
337 CmpDelayCloseWorkItemActive = TRUE;
338
339 /* Setup the interval */
340 Timeout.QuadPart = CmpDelayCloseIntervalInSeconds * -10000000;
341 KeSetTimer(&CmpDelayCloseTimer, Timeout, &CmpDelayCloseDpc);
342 }
343
344 VOID
345 NTAPI
346 CmpAddToDelayedClose(IN PCM_KEY_CONTROL_BLOCK Kcb,
347 IN BOOLEAN LockHeldExclusively)
348 {
349 ULONG i;
350 ULONG OldRefCount, NewRefCount;
351 PCM_DELAYED_CLOSE_ENTRY Entry;
352 PAGED_CODE();
353
354 /* Sanity check */
355 ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
356 (CmpTestRegistryLockExclusive() == TRUE));
357
358 /* Make sure it's valid */
359 if (Kcb->DelayedCloseIndex != CmpDelayedCloseSize) ASSERT(FALSE);
360
361 /* Sanity checks */
362 ASSERT(Kcb->RefCount == 0);
363 ASSERT(IsListEmpty(&Kcb->KeyBodyListHead) == TRUE);
364 for (i = 0; i < 4; i++) ASSERT(Kcb->KeyBodyArray[i] == NULL);
365
366 /* Allocate a delay item */
367 Entry = CmpAllocateDelayItem();
368 if (!Entry)
369 {
370 /* Cleanup immediately */
371 CmpCleanUpKcbCacheWithLock(Kcb, LockHeldExclusively);
372 return;
373 }
374
375 /* Sanity check */
376 if (Kcb->InDelayClose) ASSERT(FALSE);
377
378 /* Get the previous reference count */
379 OldRefCount = *(PLONG)&Kcb->InDelayClose;
380 ASSERT(OldRefCount == 0);
381
382 /* Write the new one */
383 NewRefCount = 1;
384 if (InterlockedCompareExchange((PLONG)&Kcb->InDelayClose,
385 NewRefCount,
386 OldRefCount) != OldRefCount)
387 {
388 /* Sanity check */
389 ASSERT(FALSE);
390 }
391
392 /* Reset the delayed close index */
393 Kcb->DelayedCloseIndex = 0;
394
395 /* Set up the close entry */
396 Kcb->DelayCloseEntry = Entry;
397 Entry->KeyControlBlock = Kcb;
398
399 /* Increase the number of elements */
400 InterlockedIncrement((PLONG)&CmpDelayedCloseElements);
401
402 /* Acquire the delayed close table lock */
403 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
404
405 /* Insert the entry into the list */
406 InsertHeadList(&CmpDelayedLRUListHead, &Entry->DelayedLRUList);
407
408 /* Check if we need to enable anything */
409 if ((CmpDelayedCloseElements > CmpDelayedCloseSize) &&
410 !(CmpDelayCloseWorkItemActive))
411 {
412 /* Yes, we have too many elements to close, and no work item */
413 CmpArmDelayedCloseTimer();
414 }
415
416 /* Release the table lock */
417 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
418 }
419
420 VOID
421 NTAPI
422 CmpRemoveFromDelayedClose(IN PCM_KEY_CONTROL_BLOCK Kcb)
423 {
424 PCM_DELAYED_CLOSE_ENTRY Entry;
425 ULONG NewRefCount, OldRefCount;
426 PAGED_CODE();
427
428 /* Sanity checks */
429 ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
430 (CmpTestRegistryLockExclusive() == TRUE));
431 if (Kcb->DelayedCloseIndex == CmpDelayedCloseSize) ASSERT(FALSE);
432
433 /* Get the entry and lock the table */
434 Entry = Kcb->DelayCloseEntry;
435 ASSERT(Entry);
436 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
437
438 /* Remove the entry */
439 RemoveEntryList(&Entry->DelayedLRUList);
440
441 /* Release the lock */
442 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
443
444 /* Free the entry */
445 CmpFreeDelayItem(Entry);
446
447 /* Reduce the number of elements */
448 InterlockedDecrement((PLONG)&CmpDelayedCloseElements);
449
450 /* Sanity check */
451 if (!Kcb->InDelayClose) ASSERT(FALSE);
452
453 /* Get the previous reference count */
454 OldRefCount = *(PLONG)&Kcb->InDelayClose;
455 ASSERT(OldRefCount == 1);
456
457 /* Write the new one */
458 NewRefCount = 0;
459 if (InterlockedCompareExchange((PLONG)&Kcb->InDelayClose,
460 NewRefCount,
461 OldRefCount) != OldRefCount)
462 {
463 /* Sanity check */
464 ASSERT(FALSE);
465 }
466
467 /* Remove the link to the entry */
468 Kcb->DelayCloseEntry = NULL;
469
470 /* Set new delay size and remove the delete flag */
471 Kcb->DelayedCloseIndex = CmpDelayedCloseSize;
472 }