[NTOSKRNL] Implement CcPostDeferredWrites() that executes deferred writes.
[reactos.git] / ntoskrnl / cc / lazywrite.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/cc/lazywrite.c
5 * PURPOSE: Cache manager
6 *
7 * PROGRAMMERS: Pierre Schweitzer (pierre@reactos.org)
8 */
9
10 /* INCLUDES *****************************************************************/
11
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15
16 typedef enum _WORK_QUEUE_FUNCTIONS
17 {
18 ReadAhead = 1,
19 WriteBehind = 2,
20 LazyWrite = 3,
21 SetDone = 4,
22 } WORK_QUEUE_FUNCTIONS, *PWORK_QUEUE_FUNCTIONS;
23
24 /* Counters:
25 * - Amount of pages flushed by lazy writer
26 * - Number of times lazy writer ran
27 */
28 ULONG CcLazyWritePages = 0;
29 ULONG CcLazyWriteIos = 0;
30
31 /* Internal vars (MS):
32 * - Lazy writer status structure
33 * - Lookaside list where to allocate work items
34 * - Queue for regular work items
35 * - Available worker threads
36 * - Queue for stuff to be queued after lazy writer is done
37 * - Marker for throttling queues
38 * - Number of ongoing workers
39 * - Three seconds delay for lazy writer
40 * - One second delay for lazy writer
41 * - Zero delay for lazy writer
42 * - Number of worker threads
43 */
44 LAZY_WRITER LazyWriter;
45 NPAGED_LOOKASIDE_LIST CcTwilightLookasideList;
46 LIST_ENTRY CcRegularWorkQueue;
47 LIST_ENTRY CcIdleWorkerThreadList;
48 LIST_ENTRY CcPostTickWorkQueue;
49 BOOLEAN CcQueueThrottle = FALSE;
50 ULONG CcNumberActiveWorkerThreads = 0;
51 LARGE_INTEGER CcFirstDelay = RTL_CONSTANT_LARGE_INTEGER((LONGLONG)-1*3000*1000*10);
52 LARGE_INTEGER CcIdleDelay = RTL_CONSTANT_LARGE_INTEGER((LONGLONG)-1*1000*1000*10);
53 LARGE_INTEGER CcNoDelay = RTL_CONSTANT_LARGE_INTEGER((LONGLONG)0);
54 ULONG CcNumberWorkerThreads;
55
56 /* FUNCTIONS *****************************************************************/
57
58 VOID
59 CcPostWorkQueue(
60 IN PWORK_QUEUE_ENTRY WorkItem,
61 IN PLIST_ENTRY WorkQueue)
62 {
63 KIRQL OldIrql;
64 PWORK_QUEUE_ITEM ThreadToSpawn;
65
66 /* First of all, insert the item in the queue */
67 OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
68 InsertTailList(WorkQueue, &WorkItem->WorkQueueLinks);
69
70 /* Now, define whether we have to spawn a new work thread
71 * We will spawn a new one if:
72 * - There's no throttle in action
73 * - There's still at least one idle thread
74 */
75 ThreadToSpawn = NULL;
76 if (!CcQueueThrottle && !IsListEmpty(&CcIdleWorkerThreadList))
77 {
78 PLIST_ENTRY ListEntry;
79
80 /* Get the idle thread */
81 ListEntry = RemoveHeadList(&CcIdleWorkerThreadList);
82 ThreadToSpawn = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ITEM, List);
83
84 /* We're going to have one more! */
85 CcNumberActiveWorkerThreads += 1;
86 }
87
88 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
89
90 /* If we have a thread to spawn, do it! */
91 if (ThreadToSpawn != NULL)
92 {
93 /* We NULLify it to be consistent with initialization */
94 ThreadToSpawn->List.Flink = NULL;
95 ExQueueWorkItem(ThreadToSpawn, CriticalWorkQueue);
96 }
97 }
98
99 VOID
100 NTAPI
101 CcScanDpc(
102 IN PKDPC Dpc,
103 IN PVOID DeferredContext,
104 IN PVOID SystemArgument1,
105 IN PVOID SystemArgument2)
106 {
107 PWORK_QUEUE_ENTRY WorkItem;
108
109 /* Allocate a work item */
110 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
111 if (WorkItem == NULL)
112 {
113 LazyWriter.ScanActive = FALSE;
114 return;
115 }
116
117 /* And post it, it will be for lazy write */
118 WorkItem->Function = LazyWrite;
119 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
120 }
121
122 /* FIXME: handle master lock */
123 VOID
124 CcLazyWriteScan(VOID)
125 {
126 ULONG Target;
127 ULONG Count;
128 KIRQL OldIrql;
129 PLIST_ENTRY ListEntry;
130 LIST_ENTRY ToPost;
131 PWORK_QUEUE_ENTRY WorkItem;
132
133 /* Do we have entries to queue after we're done? */
134 InitializeListHead(&ToPost);
135 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
136 if (LazyWriter.OtherWork)
137 {
138 while (!IsListEmpty(&CcPostTickWorkQueue))
139 {
140 ListEntry = RemoveHeadList(&CcPostTickWorkQueue);
141 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
142 InsertTailList(&ToPost, &WorkItem->WorkQueueLinks);
143 }
144 LazyWriter.OtherWork = FALSE;
145 }
146 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
147
148 /* Our target is one-eighth of the dirty pages */
149 Target = CcTotalDirtyPages / 8;
150 if (Target != 0)
151 {
152 /* Flush! */
153 DPRINT1("Lazy writer starting (%d)\n", Target);
154 CcRosFlushDirtyPages(Target, &Count, FALSE, TRUE);
155
156 /* And update stats */
157 CcLazyWritePages += Count;
158 ++CcLazyWriteIos;
159 DPRINT1("Lazy writer done (%d)\n", Count);
160 }
161
162 /* If we have deferred writes, try them now! */
163 if (!IsListEmpty(&CcDeferredWrites))
164 {
165 CcPostDeferredWrites();
166 }
167
168 while (!IsListEmpty(&ToPost))
169 {
170 ListEntry = RemoveHeadList(&ToPost);
171 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
172 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
173 }
174
175 /* We're no longer active */
176 LazyWriter.ScanActive = FALSE;
177 }
178
179 VOID CcScheduleLazyWriteScan(
180 IN BOOLEAN NoDelay)
181 {
182 /* If no delay, immediately start lazy writer,
183 * no matter it was already started
184 */
185 if (NoDelay)
186 {
187 LazyWriter.ScanActive = TRUE;
188 KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc);
189 }
190 /* Otherwise, if it's not running, just wait three seconds to start it */
191 else if (!LazyWriter.ScanActive)
192 {
193 LazyWriter.ScanActive = TRUE;
194 KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc);
195 }
196 /* Finally, already running, so queue for the next second */
197 else
198 {
199 KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc);
200 }
201 }
202
203 VOID
204 NTAPI
205 CcWorkerThread(
206 IN PVOID Parameter)
207 {
208 KIRQL OldIrql;
209 BOOLEAN DropThrottle;
210 PWORK_QUEUE_ITEM Item;
211
212 /* Get back our thread item */
213 Item = Parameter;
214 /* And by default, don't touch throttle */
215 DropThrottle = FALSE;
216
217 /* Loop till we have jobs */
218 while (TRUE)
219 {
220 PWORK_QUEUE_ENTRY WorkItem;
221
222 /* Lock queues */
223 OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
224
225 /* If we have to touch throttle, reset it now! */
226 if (DropThrottle)
227 {
228 CcQueueThrottle = FALSE;
229 DropThrottle = FALSE;
230 }
231
232 /* If no work to do, we're done */
233 if (IsListEmpty(&CcRegularWorkQueue))
234 {
235 break;
236 }
237
238 /* Get our work item, if someone is waiting for us to finish
239 * and we're not the only thread in queue
240 * then, quit running to let the others do
241 * and throttle so that noone starts till current activity is over
242 */
243 WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
244 if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1)
245 {
246 CcQueueThrottle = TRUE;
247 break;
248 }
249
250 /* Otherwise, remove current entry */
251 RemoveEntryList(&WorkItem->WorkQueueLinks);
252 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
253
254 /* And handle it */
255 switch (WorkItem->Function)
256 {
257 /* We only support lazy write now */
258 case LazyWrite:
259 CcLazyWriteScan();
260 break;
261
262 case SetDone:
263 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE);
264 DropThrottle = TRUE;
265 break;
266 }
267
268 /* And release the item */
269 ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem);
270 }
271
272 /* Our thread is available again */
273 InsertTailList(&CcIdleWorkerThreadList, &Item->List);
274 /* One less worker */
275 --CcNumberActiveWorkerThreads;
276 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
277 }
278
279 /*
280 * @implemented
281 */
282 NTSTATUS
283 NTAPI
284 CcWaitForCurrentLazyWriterActivity (
285 VOID)
286 {
287 KIRQL OldIrql;
288 KEVENT WaitEvent;
289 PWORK_QUEUE_ENTRY WorkItem;
290
291 /* Allocate a work item */
292 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
293 if (WorkItem == NULL)
294 {
295 return STATUS_INSUFFICIENT_RESOURCES;
296 }
297
298 /* We want lazy writer to set our event */
299 WorkItem->Function = SetDone;
300 KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE);
301 WorkItem->Parameters.Event.Event = &WaitEvent;
302
303 /* Use the post tick queue */
304 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
305 InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks);
306
307 /* Inform the lazy writer it will have to handle the post tick queue */
308 LazyWriter.OtherWork = TRUE;
309 /* And if it's not running, queue a lazy writer run
310 * And start it NOW, we want the response now
311 */
312 if (!LazyWriter.ScanActive)
313 {
314 CcScheduleLazyWriteScan(TRUE);
315 }
316
317 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
318
319 /* And now, wait until lazy writer replies */
320 return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL);
321 }