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