* Sync up to trunk HEAD (r62286).
[reactos.git] / 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 _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 VOID
189 NTAPI
190 INIT_FUNCTION
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 VOID
266 NTAPI
267 INIT_FUNCTION
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 ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
360 (CmpTestRegistryLockExclusive() == TRUE));
361
362 /* Make sure it's valid */
363 if (Kcb->DelayedCloseIndex != CmpDelayedCloseSize) ASSERT(FALSE);
364
365 /* Sanity checks */
366 ASSERT(Kcb->RefCount == 0);
367 ASSERT(IsListEmpty(&Kcb->KeyBodyListHead) == TRUE);
368 for (i = 0; i < 4; i++) ASSERT(Kcb->KeyBodyArray[i] == NULL);
369
370 /* Allocate a delay item */
371 Entry = CmpAllocateDelayItem();
372 if (!Entry)
373 {
374 /* Cleanup immediately */
375 CmpCleanUpKcbCacheWithLock(Kcb, LockHeldExclusively);
376 return;
377 }
378
379 /* Sanity check */
380 if (Kcb->InDelayClose) ASSERT(FALSE);
381
382 /* Get the previous reference count */
383 OldRefCount = *(PLONG)&Kcb->InDelayClose;
384 ASSERT(OldRefCount == 0);
385
386 /* Write the new one */
387 NewRefCount = 1;
388 if (InterlockedCompareExchange((PLONG)&Kcb->InDelayClose,
389 NewRefCount,
390 OldRefCount) != OldRefCount)
391 {
392 /* Sanity check */
393 ASSERT(FALSE);
394 }
395
396 /* Reset the delayed close index */
397 Kcb->DelayedCloseIndex = 0;
398
399 /* Set up the close entry */
400 Kcb->DelayCloseEntry = Entry;
401 Entry->KeyControlBlock = Kcb;
402
403 /* Increase the number of elements */
404 InterlockedIncrement((PLONG)&CmpDelayedCloseElements);
405
406 /* Acquire the delayed close table lock */
407 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
408
409 /* Insert the entry into the list */
410 InsertHeadList(&CmpDelayedLRUListHead, &Entry->DelayedLRUList);
411
412 /* Check if we need to enable anything */
413 if ((CmpDelayedCloseElements > CmpDelayedCloseSize) &&
414 !(CmpDelayCloseWorkItemActive))
415 {
416 /* Yes, we have too many elements to close, and no work item */
417 CmpArmDelayedCloseTimer();
418 }
419
420 /* Release the table lock */
421 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
422 }
423
424 VOID
425 NTAPI
426 CmpRemoveFromDelayedClose(IN PCM_KEY_CONTROL_BLOCK Kcb)
427 {
428 PCM_DELAYED_CLOSE_ENTRY Entry;
429 ULONG NewRefCount, OldRefCount;
430 PAGED_CODE();
431
432 /* Sanity checks */
433 ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
434 (CmpTestRegistryLockExclusive() == TRUE));
435 if (Kcb->DelayedCloseIndex == CmpDelayedCloseSize) ASSERT(FALSE);
436
437 /* Get the entry and lock the table */
438 Entry = Kcb->DelayCloseEntry;
439 ASSERT(Entry);
440 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
441
442 /* Remove the entry */
443 RemoveEntryList(&Entry->DelayedLRUList);
444
445 /* Release the lock */
446 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
447
448 /* Free the entry */
449 CmpFreeDelayItem(Entry);
450
451 /* Reduce the number of elements */
452 InterlockedDecrement((PLONG)&CmpDelayedCloseElements);
453
454 /* Sanity check */
455 if (!Kcb->InDelayClose) ASSERT(FALSE);
456
457 /* Get the previous reference count */
458 OldRefCount = *(PLONG)&Kcb->InDelayClose;
459 ASSERT(OldRefCount == 1);
460
461 /* Write the new one */
462 NewRefCount = 0;
463 if (InterlockedCompareExchange((PLONG)&Kcb->InDelayClose,
464 NewRefCount,
465 OldRefCount) != OldRefCount)
466 {
467 /* Sanity check */
468 ASSERT(FALSE);
469 }
470
471 /* Remove the link to the entry */
472 Kcb->DelayCloseEntry = NULL;
473
474 /* Set new delay size and remove the delete flag */
475 Kcb->DelayedCloseIndex = CmpDelayedCloseSize;
476 }