Sync with trunk (aka 'I want my virtualbox mouse integration too')
[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 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 CmpInitializeDelayedCloseTable(VOID)
189 {
190
191 /* Setup the delayed close lock */
192 KeInitializeGuardedMutex(&CmpDelayedCloseTableLock);
193
194 /* Setup the work item */
195 ExInitializeWorkItem(&CmpDelayCloseWorkItem, CmpDelayCloseWorker, NULL);
196
197 /* Setup the list head */
198 InitializeListHead(&CmpDelayedLRUListHead);
199
200 /* Setup the DPC and its timer */
201 KeInitializeDpc(&CmpDelayCloseDpc, CmpDelayCloseDpcRoutine, NULL);
202 KeInitializeTimer(&CmpDelayCloseTimer);
203 }
204
205 VOID
206 NTAPI
207 CmpDelayDerefKCBDpcRoutine(IN PKDPC Dpc,
208 IN PVOID DeferredContext,
209 IN PVOID SystemArgument1,
210 IN PVOID SystemArgument2)
211 {
212 /* Sanity check */
213 ASSERT(CmpDelayDerefKCBWorkItemActive);
214
215 /* Queue the work item */
216 ExQueueWorkItem(&CmpDelayDerefKCBWorkItem, DelayedWorkQueue);
217 }
218
219 VOID
220 NTAPI
221 CmpDelayDerefKCBWorker(IN PVOID Context)
222 {
223 PCM_DELAY_DEREF_KCB_ITEM Entry;
224 PAGED_CODE();
225
226 /* Sanity check */
227 ASSERT(CmpDelayDerefKCBWorkItemActive);
228
229 /* Lock the registry and and list lock */
230 CmpLockRegistry();
231 KeAcquireGuardedMutex(&CmpDelayDerefKCBLock);
232
233 /* Check if the list is empty */
234 while (!IsListEmpty(&CmpDelayDerefKCBListHead))
235 {
236 /* Grab an entry */
237 Entry = (PVOID)RemoveHeadList(&CmpDelayDerefKCBListHead);
238
239 /* We can release the lock now */
240 KeReleaseGuardedMutex(&CmpDelayDerefKCBLock);
241
242 /* Now grab the actual entry */
243 Entry = CONTAINING_RECORD(Entry, CM_DELAY_DEREF_KCB_ITEM, ListEntry);
244 Entry->ListEntry.Flink = Entry->ListEntry.Blink = NULL;
245
246 /* Dereference and free */
247 CmpDereferenceKeyControlBlock(Entry->Kcb);
248 CmpFreeDelayItem(Entry);
249
250 /* Lock the list again */
251 KeAcquireGuardedMutex(&CmpDelayDerefKCBLock);
252 }
253
254 /* We're done */
255 CmpDelayDerefKCBWorkItemActive = FALSE;
256 KeReleaseGuardedMutex(&CmpDelayDerefKCBLock);
257 CmpUnlockRegistry();
258 }
259
260 VOID
261 NTAPI
262 CmpInitDelayDerefKCBEngine(VOID)
263 {
264 /* Initialize lock and list */
265 KeInitializeGuardedMutex(&CmpDelayDerefKCBLock);
266 InitializeListHead(&CmpDelayDerefKCBListHead);
267
268 /* Setup the work item */
269 ExInitializeWorkItem(&CmpDelayDerefKCBWorkItem,
270 CmpDelayDerefKCBWorker,
271 NULL);
272
273 /* Setup the DPC and timer for it */
274 KeInitializeDpc(&CmpDelayDerefKCBDpc, CmpDelayDerefKCBDpcRoutine, NULL);
275 KeInitializeTimer(&CmpDelayDerefKCBTimer);
276 }
277
278 VOID
279 NTAPI
280 CmpDelayDerefKeyControlBlock(IN PCM_KEY_CONTROL_BLOCK Kcb)
281 {
282 LONG OldRefCount, NewRefCount;
283 LARGE_INTEGER Timeout;
284 PCM_DELAY_DEREF_KCB_ITEM Entry;
285 PAGED_CODE();
286 CMTRACE(CM_REFERENCE_DEBUG,
287 "%s - Dereferencing KCB: %p\n", __FUNCTION__, Kcb);
288
289 /* Get the previous reference count */
290 OldRefCount = *(PLONG)&Kcb->RefCount;
291 NewRefCount = OldRefCount - 1;
292 if (((NewRefCount & 0xFFFF) > 0) &&
293 (InterlockedCompareExchange((PLONG)&Kcb->RefCount,
294 NewRefCount,
295 OldRefCount) == OldRefCount))
296 {
297 /* KCB still had references, so we're done */
298 return;
299 }
300
301 /* Allocate a delay item */
302 Entry = CmpAllocateDelayItem();
303 if (!Entry) return;
304
305 /* Set the KCB */
306 Entry->Kcb = Kcb;
307
308 /* Acquire the delayed deref table lock */
309 KeAcquireGuardedMutex(&CmpDelayDerefKCBLock);
310
311 /* Insert the entry into the list */
312 InsertTailList(&CmpDelayDerefKCBListHead, &Entry->ListEntry);
313
314 /* Check if we need to enable anything */
315 if (!CmpDelayDerefKCBWorkItemActive)
316 {
317 /* Yes, we have no work item, setup the interval */
318 CmpDelayDerefKCBWorkItemActive = TRUE;
319 Timeout.QuadPart = CmpDelayDerefKCBIntervalInSeconds * -10000000;
320 KeSetTimer(&CmpDelayDerefKCBTimer, Timeout, &CmpDelayDerefKCBDpc);
321 }
322
323 /* Release the table lock */
324 KeReleaseGuardedMutex(&CmpDelayDerefKCBLock);
325 }
326
327 VOID
328 NTAPI
329 CmpArmDelayedCloseTimer(VOID)
330 {
331 LARGE_INTEGER Timeout;
332 PAGED_CODE();
333
334 /* Set the worker active */
335 CmpDelayCloseWorkItemActive = TRUE;
336
337 /* Setup the interval */
338 Timeout.QuadPart = CmpDelayCloseIntervalInSeconds * -10000000;
339 KeSetTimer(&CmpDelayCloseTimer, Timeout, &CmpDelayCloseDpc);
340 }
341
342 VOID
343 NTAPI
344 CmpAddToDelayedClose(IN PCM_KEY_CONTROL_BLOCK Kcb,
345 IN BOOLEAN LockHeldExclusively)
346 {
347 ULONG i;
348 ULONG OldRefCount, NewRefCount;
349 PCM_DELAYED_CLOSE_ENTRY Entry;
350 PAGED_CODE();
351
352 /* Sanity check */
353 ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
354 (CmpTestRegistryLockExclusive() == TRUE));
355
356 /* Make sure it's valid */
357 if (Kcb->DelayedCloseIndex != CmpDelayedCloseSize) ASSERT(FALSE);
358
359 /* Sanity checks */
360 ASSERT(Kcb->RefCount == 0);
361 ASSERT(IsListEmpty(&Kcb->KeyBodyListHead) == TRUE);
362 for (i = 0; i < 4; i++) ASSERT(Kcb->KeyBodyArray[i] == NULL);
363
364 /* Allocate a delay item */
365 Entry = CmpAllocateDelayItem();
366 if (!Entry)
367 {
368 /* Cleanup immediately */
369 CmpCleanUpKcbCacheWithLock(Kcb, LockHeldExclusively);
370 return;
371 }
372
373 /* Sanity check */
374 if (Kcb->InDelayClose) ASSERT(FALSE);
375
376 /* Get the previous reference count */
377 OldRefCount = *(PLONG)&Kcb->InDelayClose;
378 ASSERT(OldRefCount == 0);
379
380 /* Write the new one */
381 NewRefCount = 1;
382 if (InterlockedCompareExchange((PLONG)&Kcb->InDelayClose,
383 NewRefCount,
384 OldRefCount) != OldRefCount)
385 {
386 /* Sanity check */
387 ASSERT(FALSE);
388 }
389
390 /* Reset the delayed close index */
391 Kcb->DelayedCloseIndex = 0;
392
393 /* Set up the close entry */
394 Kcb->DelayCloseEntry = Entry;
395 Entry->KeyControlBlock = Kcb;
396
397 /* Increase the number of elements */
398 InterlockedIncrement((PLONG)&CmpDelayedCloseElements);
399
400 /* Acquire the delayed close table lock */
401 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
402
403 /* Insert the entry into the list */
404 InsertHeadList(&CmpDelayedLRUListHead, &Entry->DelayedLRUList);
405
406 /* Check if we need to enable anything */
407 if ((CmpDelayedCloseElements > CmpDelayedCloseSize) &&
408 !(CmpDelayCloseWorkItemActive))
409 {
410 /* Yes, we have too many elements to close, and no work item */
411 CmpArmDelayedCloseTimer();
412 }
413
414 /* Release the table lock */
415 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
416 }
417
418 VOID
419 NTAPI
420 CmpRemoveFromDelayedClose(IN PCM_KEY_CONTROL_BLOCK Kcb)
421 {
422 PCM_DELAYED_CLOSE_ENTRY Entry;
423 ULONG NewRefCount, OldRefCount;
424 PAGED_CODE();
425
426 /* Sanity checks */
427 ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
428 (CmpTestRegistryLockExclusive() == TRUE));
429 if (Kcb->DelayedCloseIndex == CmpDelayedCloseSize) ASSERT(FALSE);
430
431 /* Get the entry and lock the table */
432 Entry = Kcb->DelayCloseEntry;
433 ASSERT(Entry);
434 KeAcquireGuardedMutex(&CmpDelayedCloseTableLock);
435
436 /* Remove the entry */
437 RemoveEntryList(&Entry->DelayedLRUList);
438
439 /* Release the lock */
440 KeReleaseGuardedMutex(&CmpDelayedCloseTableLock);
441
442 /* Free the entry */
443 CmpFreeDelayItem(Entry);
444
445 /* Reduce the number of elements */
446 InterlockedDecrement((PLONG)&CmpDelayedCloseElements);
447
448 /* Sanity check */
449 if (!Kcb->InDelayClose) ASSERT(FALSE);
450
451 /* Get the previous reference count */
452 OldRefCount = *(PLONG)&Kcb->InDelayClose;
453 ASSERT(OldRefCount == 1);
454
455 /* Write the new one */
456 NewRefCount = 0;
457 if (InterlockedCompareExchange((PLONG)&Kcb->InDelayClose,
458 NewRefCount,
459 OldRefCount) != OldRefCount)
460 {
461 /* Sanity check */
462 ASSERT(FALSE);
463 }
464
465 /* Remove the link to the entry */
466 Kcb->DelayCloseEntry = NULL;
467
468 /* Set new delay size and remove the delete flag */
469 Kcb->DelayedCloseIndex = CmpDelayedCloseSize;
470 }