VirtualBox

source: vbox/trunk/src/VBox/VMM/PDMAsyncCompletionFileCache.cpp@ 29676

Last change on this file since 29676 was 29587, checked in by vboxsync, 15 years ago

AsyncCompletion: Fix hang when closing the VM and flush the cache

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 83.1 KB
Line 
1/* $Id: PDMAsyncCompletionFileCache.cpp 29587 2010-05-17 21:42:26Z vboxsync $ */
2/** @file
3 * PDM Async I/O - Transport data asynchronous in R3 using EMT.
4 * File data cache.
5 */
6
7/*
8 * Copyright (C) 2006-2008 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19/** @page pg_pdm_async_completion_cache PDM Async Completion Cache - The file I/O cache
20 * This component implements an I/O cache for file endpoints based on the 2Q cache algorithm.
21 */
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#define LOG_GROUP LOG_GROUP_PDM_ASYNC_COMPLETION
27#include <iprt/asm.h>
28#include <iprt/mem.h>
29#include <iprt/path.h>
30#include <VBox/log.h>
31#include <VBox/stam.h>
32
33#include "PDMAsyncCompletionFileInternal.h"
34
35/**
36 * A I/O memory context.
37 */
38typedef struct PDMIOMEMCTX
39{
40 /** Pointer to the scatter/gather list. */
41 PCRTSGSEG paDataSeg;
42 /** Number of segments. */
43 size_t cSegments;
44 /** Current segment we are in. */
45 unsigned iSegIdx;
46 /** Pointer to the current buffer. */
47 uint8_t *pbBuf;
48 /** Number of bytes left in the current buffer. */
49 size_t cbBufLeft;
50} PDMIOMEMCTX, *PPDMIOMEMCTX;
51
52#ifdef VBOX_STRICT
53# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) \
54 do \
55 { \
56 AssertMsg(RTCritSectIsOwner(&Cache->CritSect), \
57 ("Thread does not own critical section\n"));\
58 } while(0)
59
60# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) \
61 do \
62 { \
63 AssertMsg(RTSemRWIsWriteOwner(pEpCache->SemRWEntries), \
64 ("Thread is not exclusive owner of the per endpoint RW semaphore\n")); \
65 } while(0)
66
67# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) \
68 do \
69 { \
70 AssertMsg(RTSemRWIsReadOwner(pEpCache->SemRWEntries), \
71 ("Thread is not read owner of the per endpoint RW semaphore\n")); \
72 } while(0)
73
74#else
75# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) do { } while(0)
76# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) do { } while(0)
77# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) do { } while(0)
78#endif
79
80/*******************************************************************************
81* Internal Functions *
82*******************************************************************************/
83static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc);
84
85/**
86 * Decrement the reference counter of the given cache entry.
87 *
88 * @returns nothing.
89 * @param pEntry The entry to release.
90 */
91DECLINLINE(void) pdmacFileEpCacheEntryRelease(PPDMACFILECACHEENTRY pEntry)
92{
93 AssertMsg(pEntry->cRefs > 0, ("Trying to release a not referenced entry\n"));
94 ASMAtomicDecU32(&pEntry->cRefs);
95}
96
97/**
98 * Increment the reference counter of the given cache entry.
99 *
100 * @returns nothing.
101 * @param pEntry The entry to reference.
102 */
103DECLINLINE(void) pdmacFileEpCacheEntryRef(PPDMACFILECACHEENTRY pEntry)
104{
105 ASMAtomicIncU32(&pEntry->cRefs);
106}
107
108/**
109 * Initialize a I/O memory context.
110 *
111 * @returns nothing
112 * @param pIoMemCtx Pointer to a unitialized I/O memory context.
113 * @param paDataSeg Pointer to the S/G list.
114 * @param cSegments Number of segments in the S/G list.
115 */
116DECLINLINE(void) pdmIoMemCtxInit(PPDMIOMEMCTX pIoMemCtx, PCRTSGSEG paDataSeg, size_t cSegments)
117{
118 AssertMsg((cSegments > 0) && paDataSeg, ("Trying to initialize a I/O memory context without a S/G list\n"));
119
120 pIoMemCtx->paDataSeg = paDataSeg;
121 pIoMemCtx->cSegments = cSegments;
122 pIoMemCtx->iSegIdx = 0;
123 pIoMemCtx->pbBuf = (uint8_t *)paDataSeg[0].pvSeg;
124 pIoMemCtx->cbBufLeft = paDataSeg[0].cbSeg;
125}
126
127/**
128 * Return a buffer from the I/O memory context.
129 *
130 * @returns Pointer to the buffer
131 * @param pIoMemCtx Pointer to the I/O memory context.
132 * @param pcbData Pointer to the amount of byte requested.
133 * If the current buffer doesn't have enough bytes left
134 * the amount is returned in the variable.
135 */
136DECLINLINE(uint8_t *) pdmIoMemCtxGetBuffer(PPDMIOMEMCTX pIoMemCtx, size_t *pcbData)
137{
138 size_t cbData = RT_MIN(*pcbData, pIoMemCtx->cbBufLeft);
139 uint8_t *pbBuf = pIoMemCtx->pbBuf;
140
141 pIoMemCtx->cbBufLeft -= cbData;
142
143 /* Advance to the next segment if required. */
144 if (!pIoMemCtx->cbBufLeft)
145 {
146 pIoMemCtx->iSegIdx++;
147
148 if (RT_UNLIKELY(pIoMemCtx->iSegIdx == pIoMemCtx->cSegments))
149 {
150 pIoMemCtx->cbBufLeft = 0;
151 pIoMemCtx->pbBuf = NULL;
152 }
153 else
154 {
155 pIoMemCtx->pbBuf = (uint8_t *)pIoMemCtx->paDataSeg[pIoMemCtx->iSegIdx].pvSeg;
156 pIoMemCtx->cbBufLeft = pIoMemCtx->paDataSeg[pIoMemCtx->iSegIdx].cbSeg;
157 }
158
159 *pcbData = cbData;
160 }
161 else
162 pIoMemCtx->pbBuf += cbData;
163
164 return pbBuf;
165}
166
167#ifdef DEBUG
168static void pdmacFileCacheValidate(PPDMACFILECACHEGLOBAL pCache)
169{
170 /* Amount of cached data should never exceed the maximum amount. */
171 AssertMsg(pCache->cbCached <= pCache->cbMax,
172 ("Current amount of cached data exceeds maximum\n"));
173
174 /* The amount of cached data in the LRU and FRU list should match cbCached */
175 AssertMsg(pCache->LruRecentlyUsedIn.cbCached + pCache->LruFrequentlyUsed.cbCached == pCache->cbCached,
176 ("Amount of cached data doesn't match\n"));
177
178 AssertMsg(pCache->LruRecentlyUsedOut.cbCached <= pCache->cbRecentlyUsedOutMax,
179 ("Paged out list exceeds maximum\n"));
180}
181#endif
182
183DECLINLINE(void) pdmacFileCacheLockEnter(PPDMACFILECACHEGLOBAL pCache)
184{
185 RTCritSectEnter(&pCache->CritSect);
186#ifdef DEBUG
187 pdmacFileCacheValidate(pCache);
188#endif
189}
190
191DECLINLINE(void) pdmacFileCacheLockLeave(PPDMACFILECACHEGLOBAL pCache)
192{
193#ifdef DEBUG
194 pdmacFileCacheValidate(pCache);
195#endif
196 RTCritSectLeave(&pCache->CritSect);
197}
198
199DECLINLINE(void) pdmacFileCacheSub(PPDMACFILECACHEGLOBAL pCache, uint32_t cbAmount)
200{
201 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
202 pCache->cbCached -= cbAmount;
203}
204
205DECLINLINE(void) pdmacFileCacheAdd(PPDMACFILECACHEGLOBAL pCache, uint32_t cbAmount)
206{
207 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
208 pCache->cbCached += cbAmount;
209}
210
211DECLINLINE(void) pdmacFileCacheListAdd(PPDMACFILELRULIST pList, uint32_t cbAmount)
212{
213 pList->cbCached += cbAmount;
214}
215
216DECLINLINE(void) pdmacFileCacheListSub(PPDMACFILELRULIST pList, uint32_t cbAmount)
217{
218 pList->cbCached -= cbAmount;
219}
220
221#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
222/**
223 * Checks consistency of a LRU list.
224 *
225 * @returns nothing
226 * @param pList The LRU list to check.
227 * @param pNotInList Element which is not allowed to occur in the list.
228 */
229static void pdmacFileCacheCheckList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pNotInList)
230{
231 PPDMACFILECACHEENTRY pCurr = pList->pHead;
232
233 /* Check that there are no double entries and no cycles in the list. */
234 while (pCurr)
235 {
236 PPDMACFILECACHEENTRY pNext = pCurr->pNext;
237
238 while (pNext)
239 {
240 AssertMsg(pCurr != pNext,
241 ("Entry %#p is at least two times in list %#p or there is a cycle in the list\n",
242 pCurr, pList));
243 pNext = pNext->pNext;
244 }
245
246 AssertMsg(pCurr != pNotInList, ("Not allowed entry %#p is in list\n", pCurr));
247
248 if (!pCurr->pNext)
249 AssertMsg(pCurr == pList->pTail, ("End of list reached but last element is not list tail\n"));
250
251 pCurr = pCurr->pNext;
252 }
253}
254#endif
255
256/**
257 * Unlinks a cache entry from the LRU list it is assigned to.
258 *
259 * @returns nothing.
260 * @param pEntry The entry to unlink.
261 */
262static void pdmacFileCacheEntryRemoveFromList(PPDMACFILECACHEENTRY pEntry)
263{
264 PPDMACFILELRULIST pList = pEntry->pList;
265 PPDMACFILECACHEENTRY pPrev, pNext;
266
267 LogFlowFunc((": Deleting entry %#p from list %#p\n", pEntry, pList));
268
269 AssertPtr(pList);
270
271#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
272 pdmacFileCacheCheckList(pList, NULL);
273#endif
274
275 pPrev = pEntry->pPrev;
276 pNext = pEntry->pNext;
277
278 AssertMsg(pEntry != pPrev, ("Entry links to itself as previous element\n"));
279 AssertMsg(pEntry != pNext, ("Entry links to itself as next element\n"));
280
281 if (pPrev)
282 pPrev->pNext = pNext;
283 else
284 {
285 pList->pHead = pNext;
286
287 if (pNext)
288 pNext->pPrev = NULL;
289 }
290
291 if (pNext)
292 pNext->pPrev = pPrev;
293 else
294 {
295 pList->pTail = pPrev;
296
297 if (pPrev)
298 pPrev->pNext = NULL;
299 }
300
301 pEntry->pList = NULL;
302 pEntry->pPrev = NULL;
303 pEntry->pNext = NULL;
304 pdmacFileCacheListSub(pList, pEntry->cbData);
305#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
306 pdmacFileCacheCheckList(pList, pEntry);
307#endif
308}
309
310/**
311 * Adds a cache entry to the given LRU list unlinking it from the currently
312 * assigned list if needed.
313 *
314 * @returns nothing.
315 * @param pList List to the add entry to.
316 * @param pEntry Entry to add.
317 */
318static void pdmacFileCacheEntryAddToList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pEntry)
319{
320 LogFlowFunc((": Adding entry %#p to list %#p\n", pEntry, pList));
321#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
322 pdmacFileCacheCheckList(pList, NULL);
323#endif
324
325 /* Remove from old list if needed */
326 if (pEntry->pList)
327 pdmacFileCacheEntryRemoveFromList(pEntry);
328
329 pEntry->pNext = pList->pHead;
330 if (pList->pHead)
331 pList->pHead->pPrev = pEntry;
332 else
333 {
334 Assert(!pList->pTail);
335 pList->pTail = pEntry;
336 }
337
338 pEntry->pPrev = NULL;
339 pList->pHead = pEntry;
340 pdmacFileCacheListAdd(pList, pEntry->cbData);
341 pEntry->pList = pList;
342#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
343 pdmacFileCacheCheckList(pList, NULL);
344#endif
345}
346
347/**
348 * Destroys a LRU list freeing all entries.
349 *
350 * @returns nothing
351 * @param pList Pointer to the LRU list to destroy.
352 *
353 * @note The caller must own the critical section of the cache.
354 */
355static void pdmacFileCacheDestroyList(PPDMACFILELRULIST pList)
356{
357 while (pList->pHead)
358 {
359 PPDMACFILECACHEENTRY pEntry = pList->pHead;
360
361 pList->pHead = pEntry->pNext;
362
363 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
364 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
365
366 RTMemPageFree(pEntry->pbData, pEntry->cbData);
367 RTMemFree(pEntry);
368 }
369}
370
371/**
372 * Tries to remove the given amount of bytes from a given list in the cache
373 * moving the entries to one of the given ghosts lists
374 *
375 * @returns Amount of data which could be freed.
376 * @param pCache Pointer to the global cache data.
377 * @param cbData The amount of the data to free.
378 * @param pListSrc The source list to evict data from.
379 * @param pGhostListSrc The ghost list removed entries should be moved to
380 * NULL if the entry should be freed.
381 * @param fReuseBuffer Flag whether a buffer should be reused if it has the same size
382 * @param ppbBuf Where to store the address of the buffer if an entry with the
383 * same size was found and fReuseBuffer is true.
384 *
385 * @note This function may return fewer bytes than requested because entries
386 * may be marked as non evictable if they are used for I/O at the
387 * moment.
388 */
389static size_t pdmacFileCacheEvictPagesFrom(PPDMACFILECACHEGLOBAL pCache, size_t cbData,
390 PPDMACFILELRULIST pListSrc, PPDMACFILELRULIST pGhostListDst,
391 bool fReuseBuffer, uint8_t **ppbBuffer)
392{
393 size_t cbEvicted = 0;
394
395 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
396
397 AssertMsg(cbData > 0, ("Evicting 0 bytes not possible\n"));
398 AssertMsg( !pGhostListDst
399 || (pGhostListDst == &pCache->LruRecentlyUsedOut),
400 ("Destination list must be NULL or the recently used but paged out list\n"));
401
402 if (fReuseBuffer)
403 {
404 AssertPtr(ppbBuffer);
405 *ppbBuffer = NULL;
406 }
407
408 /* Start deleting from the tail. */
409 PPDMACFILECACHEENTRY pEntry = pListSrc->pTail;
410
411 while ((cbEvicted < cbData) && pEntry)
412 {
413 PPDMACFILECACHEENTRY pCurr = pEntry;
414
415 pEntry = pEntry->pPrev;
416
417 /* We can't evict pages which are currently in progress or dirty but not in progress */
418 if ( !(pCurr->fFlags & PDMACFILECACHE_NOT_EVICTABLE)
419 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
420 {
421 /* Ok eviction candidate. Grab the endpoint semaphore and check again
422 * because somebody else might have raced us. */
423 PPDMACFILEENDPOINTCACHE pEndpointCache = &pCurr->pEndpoint->DataCache;
424 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
425
426 if (!(pCurr->fFlags & PDMACFILECACHE_NOT_EVICTABLE)
427 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
428 {
429 LogFlow(("Evicting entry %#p (%u bytes)\n", pCurr, pCurr->cbData));
430
431 if (fReuseBuffer && (pCurr->cbData == cbData))
432 {
433 STAM_COUNTER_INC(&pCache->StatBuffersReused);
434 *ppbBuffer = pCurr->pbData;
435 }
436 else if (pCurr->pbData)
437 RTMemPageFree(pCurr->pbData, pCurr->cbData);
438
439 pCurr->pbData = NULL;
440 cbEvicted += pCurr->cbData;
441
442 pdmacFileCacheEntryRemoveFromList(pCurr);
443 pdmacFileCacheSub(pCache, pCurr->cbData);
444
445 if (pGhostListDst)
446 {
447 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
448
449 PPDMACFILECACHEENTRY pGhostEntFree = pGhostListDst->pTail;
450
451 /* We have to remove the last entries from the paged out list. */
452 while ( ((pGhostListDst->cbCached + pCurr->cbData) > pCache->cbRecentlyUsedOutMax)
453 && pGhostEntFree)
454 {
455 PPDMACFILECACHEENTRY pFree = pGhostEntFree;
456 PPDMACFILEENDPOINTCACHE pEndpointCacheFree = &pFree->pEndpoint->DataCache;
457
458 pGhostEntFree = pGhostEntFree->pPrev;
459
460 RTSemRWRequestWrite(pEndpointCacheFree->SemRWEntries, RT_INDEFINITE_WAIT);
461
462 if (ASMAtomicReadU32(&pFree->cRefs) == 0)
463 {
464 pdmacFileCacheEntryRemoveFromList(pFree);
465
466 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
467 RTAvlrFileOffsetRemove(pEndpointCacheFree->pTree, pFree->Core.Key);
468 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
469
470 RTMemFree(pFree);
471 }
472
473 RTSemRWReleaseWrite(pEndpointCacheFree->SemRWEntries);
474 }
475
476 if (pGhostListDst->cbCached + pCurr->cbData > pCache->cbRecentlyUsedOutMax)
477 {
478 /* Couldn't remove enough entries. Delete */
479 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
480 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
481 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
482
483 RTMemFree(pCurr);
484 }
485 else
486 pdmacFileCacheEntryAddToList(pGhostListDst, pCurr);
487 }
488 else
489 {
490 /* Delete the entry from the AVL tree it is assigned to. */
491 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
492 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
493 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
494
495 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
496 RTMemFree(pCurr);
497 }
498 }
499
500 }
501 else
502 LogFlow(("Entry %#p (%u bytes) is still in progress and can't be evicted\n", pCurr, pCurr->cbData));
503 }
504
505 return cbEvicted;
506}
507
508static bool pdmacFileCacheReclaim(PPDMACFILECACHEGLOBAL pCache, size_t cbData, bool fReuseBuffer, uint8_t **ppbBuffer)
509{
510 size_t cbRemoved = 0;
511
512 if ((pCache->cbCached + cbData) < pCache->cbMax)
513 return true;
514 else if ((pCache->LruRecentlyUsedIn.cbCached + cbData) > pCache->cbRecentlyUsedInMax)
515 {
516 /* Try to evict as many bytes as possible from A1in */
517 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruRecentlyUsedIn,
518 &pCache->LruRecentlyUsedOut, fReuseBuffer, ppbBuffer);
519
520 /*
521 * If it was not possible to remove enough entries
522 * try the frequently accessed cache.
523 */
524 if (cbRemoved < cbData)
525 {
526 Assert(!fReuseBuffer || !*ppbBuffer); /* It is not possible that we got a buffer with the correct size but we didn't freed enough data. */
527
528 /*
529 * If we removed something we can't pass the reuse buffer flag anymore because
530 * we don't need to evict that much data
531 */
532 if (!cbRemoved)
533 cbRemoved += pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
534 NULL, fReuseBuffer, ppbBuffer);
535 else
536 cbRemoved += pdmacFileCacheEvictPagesFrom(pCache, cbData - cbRemoved, &pCache->LruFrequentlyUsed,
537 NULL, false, NULL);
538 }
539 }
540 else
541 {
542 /* We have to remove entries from frequently access list. */
543 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
544 NULL, fReuseBuffer, ppbBuffer);
545 }
546
547 LogFlowFunc((": removed %u bytes, requested %u\n", cbRemoved, cbData));
548 return (cbRemoved >= cbData);
549}
550
551/**
552 * Initiates a read I/O task for the given entry.
553 *
554 * @returns nothing.
555 * @param pEntry The entry to fetch the data to.
556 */
557static void pdmacFileCacheReadFromEndpoint(PPDMACFILECACHEENTRY pEntry)
558{
559 LogFlowFunc((": Reading data into cache entry %#p\n", pEntry));
560
561 /* Make sure no one evicts the entry while it is accessed. */
562 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
563
564 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
565 AssertPtr(pIoTask);
566
567 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
568
569 pIoTask->pEndpoint = pEntry->pEndpoint;
570 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_READ;
571 pIoTask->Off = pEntry->Core.Key;
572 pIoTask->DataSeg.cbSeg = pEntry->cbData;
573 pIoTask->DataSeg.pvSeg = pEntry->pbData;
574 pIoTask->pvUser = pEntry;
575 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
576
577 /* Send it off to the I/O manager. */
578 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
579}
580
581/**
582 * Initiates a write I/O task for the given entry.
583 *
584 * @returns nothing.
585 * @param pEntry The entry to read the data from.
586 */
587static void pdmacFileCacheWriteToEndpoint(PPDMACFILECACHEENTRY pEntry)
588{
589 LogFlowFunc((": Writing data from cache entry %#p\n", pEntry));
590
591 /* Make sure no one evicts the entry while it is accessed. */
592 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
593
594 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
595 AssertPtr(pIoTask);
596
597 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
598
599 pIoTask->pEndpoint = pEntry->pEndpoint;
600 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_WRITE;
601 pIoTask->Off = pEntry->Core.Key;
602 pIoTask->DataSeg.cbSeg = pEntry->cbData;
603 pIoTask->DataSeg.pvSeg = pEntry->pbData;
604 pIoTask->pvUser = pEntry;
605 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
606
607 /* Send it off to the I/O manager. */
608 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
609}
610
611/**
612 * Commit a single dirty entry to the endpoint
613 *
614 * @returns nothing
615 * @param pEntry The entry to commit.
616 */
617static void pdmacFileCacheEntryCommit(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
618{
619 NOREF(pEndpointCache);
620 AssertMsg( (pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY)
621 && !(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS),
622 ("Invalid flags set for entry %#p\n", pEntry));
623
624 pdmacFileCacheWriteToEndpoint(pEntry);
625}
626
627/**
628 * Commit all dirty entries for a single endpoint.
629 *
630 * @returns nothing.
631 * @param pEndpointCache The endpoint cache to commit.
632 */
633static void pdmacFileCacheEndpointCommit(PPDMACFILEENDPOINTCACHE pEndpointCache)
634{
635 uint32_t cbCommitted = 0;
636 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
637
638 /* The list is moved to a new header to reduce locking overhead. */
639 RTLISTNODE ListDirtyNotCommitted;
640 RTSPINLOCKTMP Tmp;
641
642 RTListInit(&ListDirtyNotCommitted);
643 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
644 RTListMove(&ListDirtyNotCommitted, &pEndpointCache->ListDirtyNotCommitted);
645 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
646
647 if (!RTListIsEmpty(&ListDirtyNotCommitted))
648 {
649 PPDMACFILECACHEENTRY pEntry = RTListNodeGetFirst(&ListDirtyNotCommitted,
650 PDMACFILECACHEENTRY,
651 NodeNotCommitted);
652
653 while (!RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted))
654 {
655 PPDMACFILECACHEENTRY pNext = RTListNodeGetNext(&pEntry->NodeNotCommitted, PDMACFILECACHEENTRY,
656 NodeNotCommitted);
657 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
658 cbCommitted += pEntry->cbData;
659 RTListNodeRemove(&pEntry->NodeNotCommitted);
660 pEntry = pNext;
661 }
662
663 /* Commit the last endpoint */
664 Assert(RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted));
665 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
666 RTListNodeRemove(&pEntry->NodeNotCommitted);
667 AssertMsg(RTListIsEmpty(&ListDirtyNotCommitted),
668 ("Committed all entries but list is not empty\n"));
669 }
670
671 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
672 AssertMsg(pEndpointCache->pCache->cbDirty >= cbCommitted,
673 ("Number of committed bytes exceeds number of dirty bytes\n"));
674 ASMAtomicSubU32(&pEndpointCache->pCache->cbDirty, cbCommitted);
675}
676
677/**
678 * Commit all dirty entries in the cache.
679 *
680 * @returns nothing.
681 * @param pCache The global cache instance.
682 */
683static void pdmacFileCacheCommitDirtyEntries(PPDMACFILECACHEGLOBAL pCache)
684{
685 bool fCommitInProgress = ASMAtomicXchgBool(&pCache->fCommitInProgress, true);
686
687 if (!fCommitInProgress)
688 {
689 pdmacFileCacheLockEnter(pCache);
690 Assert(!RTListIsEmpty(&pCache->ListEndpoints));
691
692 PPDMACFILEENDPOINTCACHE pEndpointCache = RTListNodeGetFirst(&pCache->ListEndpoints,
693 PDMACFILEENDPOINTCACHE,
694 NodeCacheEndpoint);
695 AssertPtr(pEndpointCache);
696
697 while (!RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint))
698 {
699 pdmacFileCacheEndpointCommit(pEndpointCache);
700
701 pEndpointCache = RTListNodeGetNext(&pEndpointCache->NodeCacheEndpoint, PDMACFILEENDPOINTCACHE,
702 NodeCacheEndpoint);
703 }
704
705 /* Commit the last endpoint */
706 Assert(RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint));
707 pdmacFileCacheEndpointCommit(pEndpointCache);
708
709 pdmacFileCacheLockLeave(pCache);
710 ASMAtomicWriteBool(&pCache->fCommitInProgress, false);
711 }
712}
713
714/**
715 * Adds the given entry as a dirty to the cache.
716 *
717 * @returns Flag whether the amount of dirty bytes in the cache exceeds the threshold
718 * @param pEndpointCache The endpoint cache the entry belongs to.
719 * @param pEntry The entry to add.
720 */
721static bool pdmacFileCacheAddDirtyEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
722{
723 bool fDirtyBytesExceeded = false;
724 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
725
726 /* If the commit timer is disabled we commit right away. */
727 if (pCache->u32CommitTimeoutMs == 0)
728 {
729 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
730 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
731 }
732 else if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY))
733 {
734 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
735
736 RTSPINLOCKTMP Tmp;
737 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
738 RTListAppend(&pEndpointCache->ListDirtyNotCommitted, &pEntry->NodeNotCommitted);
739 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
740
741 uint32_t cbDirty = ASMAtomicAddU32(&pCache->cbDirty, pEntry->cbData);
742
743 fDirtyBytesExceeded = (cbDirty >= pCache->cbCommitDirtyThreshold);
744 }
745
746 return fDirtyBytesExceeded;
747}
748
749
750/**
751 * Completes a task segment freeing all ressources and completes the task handle
752 * if everything was transfered.
753 *
754 * @returns Next task segment handle.
755 * @param pTaskSeg Task segment to complete.
756 * @param rc Status code to set.
757 */
758static PPDMACFILETASKSEG pdmacFileCacheTaskComplete(PPDMACFILETASKSEG pTaskSeg, int rc)
759{
760 PPDMACFILETASKSEG pNext = pTaskSeg->pNext;
761 PPDMASYNCCOMPLETIONTASKFILE pTaskFile = pTaskSeg->pTask;
762
763 if (RT_FAILURE(rc))
764 ASMAtomicCmpXchgS32(&pTaskFile->rc, rc, VINF_SUCCESS);
765
766 uint32_t uOld = ASMAtomicSubS32(&pTaskFile->cbTransferLeft, pTaskSeg->cbTransfer);
767 AssertMsg(uOld >= pTaskSeg->cbTransfer, ("New value would overflow\n"));
768 if (!(uOld - pTaskSeg->cbTransfer)
769 && !ASMAtomicXchgBool(&pTaskFile->fCompleted, true))
770 pdmR3AsyncCompletionCompleteTask(&pTaskFile->Core, pTaskFile->rc, true);
771
772 RTMemFree(pTaskSeg);
773
774 return pNext;
775}
776
777/**
778 * Completion callback for I/O tasks.
779 *
780 * @returns nothing.
781 * @param pTask The completed task.
782 * @param pvUser Opaque user data.
783 * @param rc Status code of the completed request.
784 */
785static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc)
786{
787 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pvUser;
788 PPDMACFILECACHEGLOBAL pCache = pEntry->pCache;
789 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint = pEntry->pEndpoint;
790 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
791
792 /* Reference the entry now as we are clearing the I/O in progres flag
793 * which protects the entry till now. */
794 pdmacFileEpCacheEntryRef(pEntry);
795
796 RTSemRWRequestWrite(pEndpoint->DataCache.SemRWEntries, RT_INDEFINITE_WAIT);
797 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
798
799 /* Process waiting segment list. The data in entry might have changed inbetween. */
800 bool fDirty = false;
801 PPDMACFILETASKSEG pComplete = pEntry->pWaitingHead;
802 PPDMACFILETASKSEG pCurr = pComplete;
803
804 AssertMsg((pCurr && pEntry->pWaitingTail) || (!pCurr && !pEntry->pWaitingTail),
805 ("The list tail was not updated correctly\n"));
806 pEntry->pWaitingTail = NULL;
807 pEntry->pWaitingHead = NULL;
808
809 if (pTask->enmTransferType == PDMACTASKFILETRANSFER_WRITE)
810 {
811 /*
812 * An error here is difficult to handle as the original request completed already.
813 * The error is logged for now and the VM is paused.
814 * If the user continues the entry is written again in the hope
815 * the user fixed the problem and the next write succeeds.
816 */
817 /** @todo r=aeichner: This solution doesn't work
818 * The user will get the message but the VM will hang afterwards
819 * VMR3Suspend() returns when the VM is suspended but suspending
820 * the VM will reopen the images readonly in DrvVD. They are closed first
821 * which will close the endpoints. This will block EMT while the
822 * I/O manager processes the close request but the IO manager is stuck
823 * in the VMR3Suspend call and can't process the request.
824 * Another problem is that closing the VM means flushing the cache
825 * but the entry failed and will probably fail again.
826 * No idea so far how to solve this problem... but the user gets informed
827 * at least.
828 */
829 if (RT_FAILURE(rc))
830 {
831 LogRel(("I/O cache: Error while writing entry at offset %RTfoff (%u bytes) to file \"%s\"\n",
832 pEntry->Core.Key, pEntry->cbData, pEndpoint->Core.pszUri));
833
834 rc = VMSetRuntimeError(pEndpoint->Core.pEpClass->pVM, 0, "CACHE_IOERR",
835 N_("The I/O cache encountered an error while updating data in file \"%s\" (rc=%Rrc). Make sure there is enough free space on the disk and that the disk is working properly. Operation can be resumed afterwards."), pEndpoint->Core.pszUri, rc);
836 AssertRC(rc);
837 rc = VMR3Suspend(pEndpoint->Core.pEpClass->pVM);
838 }
839 else
840 {
841 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IS_DIRTY;
842
843 while (pCurr)
844 {
845 AssertMsg(pCurr->fWrite, ("Completed write entries should never have read tasks attached\n"));
846
847 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
848 fDirty = true;
849
850 pCurr = pCurr->pNext;
851 }
852 }
853 }
854 else
855 {
856 AssertMsg(pTask->enmTransferType == PDMACTASKFILETRANSFER_READ, ("Invalid transfer type\n"));
857 AssertMsg(!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY),
858 ("Invalid flags set\n"));
859
860 while (pCurr)
861 {
862 if (pCurr->fWrite)
863 {
864 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
865 fDirty = true;
866 }
867 else
868 memcpy(pCurr->pvBuf, pEntry->pbData + pCurr->uBufOffset, pCurr->cbTransfer);
869
870 pCurr = pCurr->pNext;
871 }
872 }
873
874 bool fCommit = false;
875 if (fDirty)
876 fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
877
878 RTSemRWReleaseWrite(pEndpoint->DataCache.SemRWEntries);
879
880 /* Dereference so that it isn't protected anymore except we issued anyother write for it. */
881 pdmacFileEpCacheEntryRelease(pEntry);
882
883 if (fCommit)
884 pdmacFileCacheCommitDirtyEntries(pCache);
885
886 /* Complete waiters now. */
887 while (pComplete)
888 pComplete = pdmacFileCacheTaskComplete(pComplete, rc);
889}
890
891/**
892 * Commit timer callback.
893 */
894static void pdmacFileCacheCommitTimerCallback(PVM pVM, PTMTIMER pTimer, void *pvUser)
895{
896 PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile = (PPDMASYNCCOMPLETIONEPCLASSFILE)pvUser;
897 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
898
899 LogFlowFunc(("Commit interval expired, commiting dirty entries\n"));
900
901 if (ASMAtomicReadU32(&pCache->cbDirty) > 0)
902 pdmacFileCacheCommitDirtyEntries(pCache);
903
904 TMTimerSetMillies(pTimer, pCache->u32CommitTimeoutMs);
905 LogFlowFunc(("Entries committed, going to sleep\n"));
906}
907
908/**
909 * Initializies the I/O cache.
910 *
911 * returns VBox status code.
912 * @param pClassFile The global class data for file endpoints.
913 * @param pCfgNode CFGM node to query configuration data from.
914 */
915int pdmacFileCacheInit(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile, PCFGMNODE pCfgNode)
916{
917 int rc = VINF_SUCCESS;
918 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
919
920 rc = CFGMR3QueryU32Def(pCfgNode, "CacheSize", &pCache->cbMax, 5 * _1M);
921 AssertLogRelRCReturn(rc, rc);
922
923 RTListInit(&pCache->ListEndpoints);
924 pCache->cRefs = 0;
925 pCache->cbCached = 0;
926 pCache->fCommitInProgress = 0;
927 LogFlowFunc((": Maximum number of bytes cached %u\n", pCache->cbMax));
928
929 /* Initialize members */
930 pCache->LruRecentlyUsedIn.pHead = NULL;
931 pCache->LruRecentlyUsedIn.pTail = NULL;
932 pCache->LruRecentlyUsedIn.cbCached = 0;
933
934 pCache->LruRecentlyUsedOut.pHead = NULL;
935 pCache->LruRecentlyUsedOut.pTail = NULL;
936 pCache->LruRecentlyUsedOut.cbCached = 0;
937
938 pCache->LruFrequentlyUsed.pHead = NULL;
939 pCache->LruFrequentlyUsed.pTail = NULL;
940 pCache->LruFrequentlyUsed.cbCached = 0;
941
942 pCache->cbRecentlyUsedInMax = (pCache->cbMax / 100) * 25; /* 25% of the buffer size */
943 pCache->cbRecentlyUsedOutMax = (pCache->cbMax / 100) * 50; /* 50% of the buffer size */
944 LogFlowFunc((": cbRecentlyUsedInMax=%u cbRecentlyUsedOutMax=%u\n", pCache->cbRecentlyUsedInMax, pCache->cbRecentlyUsedOutMax));
945
946 /** @todo r=aeichner: Experiment to find optimal default values */
947 rc = CFGMR3QueryU32Def(pCfgNode, "CacheCommitIntervalMs", &pCache->u32CommitTimeoutMs, 10000 /* 10sec */);
948 AssertLogRelRCReturn(rc, rc);
949 rc = CFGMR3QueryU32(pCfgNode, "CacheCommitThreshold", &pCache->cbCommitDirtyThreshold);
950 if ( rc == VERR_CFGM_VALUE_NOT_FOUND
951 || rc == VERR_CFGM_NO_PARENT)
952 {
953 /* Start committing after 50% of the cache are dirty */
954 pCache->cbCommitDirtyThreshold = pCache->cbMax / 2;
955 }
956 else
957 return rc;
958
959 STAMR3Register(pClassFile->Core.pVM, &pCache->cbMax,
960 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
961 "/PDM/AsyncCompletion/File/cbMax",
962 STAMUNIT_BYTES,
963 "Maximum cache size");
964 STAMR3Register(pClassFile->Core.pVM, &pCache->cbCached,
965 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
966 "/PDM/AsyncCompletion/File/cbCached",
967 STAMUNIT_BYTES,
968 "Currently used cache");
969 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsedIn.cbCached,
970 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
971 "/PDM/AsyncCompletion/File/cbCachedMruIn",
972 STAMUNIT_BYTES,
973 "Number of bytes cached in MRU list");
974 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsedOut.cbCached,
975 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
976 "/PDM/AsyncCompletion/File/cbCachedMruOut",
977 STAMUNIT_BYTES,
978 "Number of bytes cached in FRU list");
979 STAMR3Register(pClassFile->Core.pVM, &pCache->LruFrequentlyUsed.cbCached,
980 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
981 "/PDM/AsyncCompletion/File/cbCachedFru",
982 STAMUNIT_BYTES,
983 "Number of bytes cached in FRU ghost list");
984
985#ifdef VBOX_WITH_STATISTICS
986 STAMR3Register(pClassFile->Core.pVM, &pCache->cHits,
987 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
988 "/PDM/AsyncCompletion/File/CacheHits",
989 STAMUNIT_COUNT, "Number of hits in the cache");
990 STAMR3Register(pClassFile->Core.pVM, &pCache->cPartialHits,
991 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
992 "/PDM/AsyncCompletion/File/CachePartialHits",
993 STAMUNIT_COUNT, "Number of partial hits in the cache");
994 STAMR3Register(pClassFile->Core.pVM, &pCache->cMisses,
995 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
996 "/PDM/AsyncCompletion/File/CacheMisses",
997 STAMUNIT_COUNT, "Number of misses when accessing the cache");
998 STAMR3Register(pClassFile->Core.pVM, &pCache->StatRead,
999 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1000 "/PDM/AsyncCompletion/File/CacheRead",
1001 STAMUNIT_BYTES, "Number of bytes read from the cache");
1002 STAMR3Register(pClassFile->Core.pVM, &pCache->StatWritten,
1003 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1004 "/PDM/AsyncCompletion/File/CacheWritten",
1005 STAMUNIT_BYTES, "Number of bytes written to the cache");
1006 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeGet,
1007 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1008 "/PDM/AsyncCompletion/File/CacheTreeGet",
1009 STAMUNIT_TICKS_PER_CALL, "Time taken to access an entry in the tree");
1010 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeInsert,
1011 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1012 "/PDM/AsyncCompletion/File/CacheTreeInsert",
1013 STAMUNIT_TICKS_PER_CALL, "Time taken to insert an entry in the tree");
1014 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeRemove,
1015 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1016 "/PDM/AsyncCompletion/File/CacheTreeRemove",
1017 STAMUNIT_TICKS_PER_CALL, "Time taken to remove an entry an the tree");
1018 STAMR3Register(pClassFile->Core.pVM, &pCache->StatBuffersReused,
1019 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1020 "/PDM/AsyncCompletion/File/CacheBuffersReused",
1021 STAMUNIT_COUNT, "Number of times a buffer could be reused");
1022#endif
1023
1024 /* Initialize the critical section */
1025 rc = RTCritSectInit(&pCache->CritSect);
1026
1027 if (RT_SUCCESS(rc))
1028 {
1029 /* Create the commit timer */
1030 if (pCache->u32CommitTimeoutMs > 0)
1031 rc = TMR3TimerCreateInternal(pClassFile->Core.pVM, TMCLOCK_REAL,
1032 pdmacFileCacheCommitTimerCallback,
1033 pClassFile,
1034 "Cache-Commit",
1035 &pClassFile->Cache.pTimerCommit);
1036
1037 if (RT_SUCCESS(rc))
1038 {
1039 LogRel(("AIOMgr: Cache successfully initialised. Cache size is %u bytes\n", pCache->cbMax));
1040 LogRel(("AIOMgr: Cache commit interval is %u ms\n", pCache->u32CommitTimeoutMs));
1041 LogRel(("AIOMgr: Cache commit threshold is %u bytes\n", pCache->cbCommitDirtyThreshold));
1042 return VINF_SUCCESS;
1043 }
1044
1045 RTCritSectDelete(&pCache->CritSect);
1046 }
1047
1048 return rc;
1049}
1050
1051/**
1052 * Destroysthe cache freeing all data.
1053 *
1054 * returns nothing.
1055 * @param pClassFile The global class data for file endpoints.
1056 */
1057void pdmacFileCacheDestroy(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
1058{
1059 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
1060
1061 /* Make sure no one else uses the cache now */
1062 pdmacFileCacheLockEnter(pCache);
1063
1064 /* Cleanup deleting all cache entries waiting for in progress entries to finish. */
1065 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsedIn);
1066 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsedOut);
1067 pdmacFileCacheDestroyList(&pCache->LruFrequentlyUsed);
1068
1069 pdmacFileCacheLockLeave(pCache);
1070
1071 RTCritSectDelete(&pCache->CritSect);
1072}
1073
1074/**
1075 * Initializes per endpoint cache data
1076 * like the AVL tree used to access cached entries.
1077 *
1078 * @returns VBox status code.
1079 * @param pEndpoint The endpoint to init the cache for,
1080 * @param pClassFile The global class data for file endpoints.
1081 */
1082int pdmacFileEpCacheInit(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
1083{
1084 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1085
1086 pEndpointCache->pCache = &pClassFile->Cache;
1087 RTListInit(&pEndpointCache->ListDirtyNotCommitted);
1088 int rc = RTSpinlockCreate(&pEndpointCache->LockList);
1089
1090 if (RT_SUCCESS(rc))
1091 {
1092 rc = RTSemRWCreate(&pEndpointCache->SemRWEntries);
1093 if (RT_SUCCESS(rc))
1094 {
1095 pEndpointCache->pTree = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
1096 if (pEndpointCache->pTree)
1097 {
1098 pClassFile->Cache.cRefs++;
1099 RTListAppend(&pClassFile->Cache.ListEndpoints, &pEndpointCache->NodeCacheEndpoint);
1100
1101 /* Arm the timer if this is the first endpoint. */
1102 if ( pClassFile->Cache.cRefs == 1
1103 && pClassFile->Cache.u32CommitTimeoutMs > 0)
1104 rc = TMTimerSetMillies(pClassFile->Cache.pTimerCommit, pClassFile->Cache.u32CommitTimeoutMs);
1105 }
1106 else
1107 rc = VERR_NO_MEMORY;
1108
1109 if (RT_FAILURE(rc))
1110 RTSemRWDestroy(pEndpointCache->SemRWEntries);
1111 }
1112
1113 if (RT_FAILURE(rc))
1114 RTSpinlockDestroy(pEndpointCache->LockList);
1115 }
1116
1117#ifdef VBOX_WITH_STATISTICS
1118 if (RT_SUCCESS(rc))
1119 {
1120 STAMR3RegisterF(pClassFile->Core.pVM, &pEndpointCache->StatWriteDeferred,
1121 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1122 STAMUNIT_COUNT, "Number of deferred writes",
1123 "/PDM/AsyncCompletion/File/%s/Cache/DeferredWrites", RTPathFilename(pEndpoint->Core.pszUri));
1124 }
1125#endif
1126
1127 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1128 return rc;
1129}
1130
1131/**
1132 * Callback for the AVL destroy routine. Frees a cache entry for this endpoint.
1133 *
1134 * @returns IPRT status code.
1135 * @param pNode The node to destroy.
1136 * @param pvUser Opaque user data.
1137 */
1138static int pdmacFileEpCacheEntryDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
1139{
1140 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pNode;
1141 PPDMACFILECACHEGLOBAL pCache = (PPDMACFILECACHEGLOBAL)pvUser;
1142 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEntry->pEndpoint->DataCache;
1143
1144 while (ASMAtomicReadU32(&pEntry->fFlags) & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY))
1145 {
1146 /* Leave the locks to let the I/O thread make progress but reference the entry to prevent eviction. */
1147 pdmacFileEpCacheEntryRef(pEntry);
1148 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1149 pdmacFileCacheLockLeave(pCache);
1150
1151 RTThreadSleep(250);
1152
1153 /* Re-enter all locks */
1154 pdmacFileCacheLockEnter(pCache);
1155 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1156 pdmacFileEpCacheEntryRelease(pEntry);
1157 }
1158
1159 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
1160 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
1161
1162 bool fUpdateCache = pEntry->pList == &pCache->LruFrequentlyUsed
1163 || pEntry->pList == &pCache->LruRecentlyUsedIn;
1164
1165 pdmacFileCacheEntryRemoveFromList(pEntry);
1166
1167 if (fUpdateCache)
1168 pdmacFileCacheSub(pCache, pEntry->cbData);
1169
1170 RTMemPageFree(pEntry->pbData, pEntry->cbData);
1171 RTMemFree(pEntry);
1172
1173 return VINF_SUCCESS;
1174}
1175
1176/**
1177 * Destroys all cache ressources used by the given endpoint.
1178 *
1179 * @returns nothing.
1180 * @param pEndpoint The endpoint to the destroy.
1181 */
1182void pdmacFileEpCacheDestroy(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint)
1183{
1184 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1185 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1186
1187 /* Make sure nobody is accessing the cache while we delete the tree. */
1188 pdmacFileCacheLockEnter(pCache);
1189 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1190 RTAvlrFileOffsetDestroy(pEndpointCache->pTree, pdmacFileEpCacheEntryDestroy, pCache);
1191 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1192
1193 RTSpinlockDestroy(pEndpointCache->LockList);
1194
1195 pCache->cRefs--;
1196 RTListNodeRemove(&pEndpointCache->NodeCacheEndpoint);
1197
1198 if ( !pCache->cRefs
1199 && pCache->u32CommitTimeoutMs > 0)
1200 TMTimerStop(pCache->pTimerCommit);
1201
1202 pdmacFileCacheLockLeave(pCache);
1203
1204 RTSemRWDestroy(pEndpointCache->SemRWEntries);
1205
1206#ifdef VBOX_WITH_STATISTICS
1207 PPDMASYNCCOMPLETIONEPCLASSFILE pEpClassFile = (PPDMASYNCCOMPLETIONEPCLASSFILE)pEndpoint->Core.pEpClass;
1208
1209 STAMR3Deregister(pEpClassFile->Core.pVM, &pEndpointCache->StatWriteDeferred);
1210#endif
1211}
1212
1213static PPDMACFILECACHEENTRY pdmacFileEpCacheGetCacheEntryByOffset(PPDMACFILEENDPOINTCACHE pEndpointCache, RTFOFF off)
1214{
1215 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1216 PPDMACFILECACHEENTRY pEntry = NULL;
1217
1218 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1219
1220 RTSemRWRequestRead(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1221 pEntry = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetRangeGet(pEndpointCache->pTree, off);
1222 if (pEntry)
1223 pdmacFileEpCacheEntryRef(pEntry);
1224 RTSemRWReleaseRead(pEndpointCache->SemRWEntries);
1225
1226 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1227
1228 return pEntry;
1229}
1230
1231/**
1232 * Return the best fit cache entries for the given offset.
1233 *
1234 * @returns nothing.
1235 * @param pEndpointCache The endpoint cache.
1236 * @param off The offset.
1237 * @param pEntryAbove Where to store the pointer to the best fit entry above the
1238 * the given offset. NULL if not required.
1239 * @param pEntryBelow Where to store the pointer to the best fit entry below the
1240 * the given offset. NULL if not required.
1241 */
1242static void pdmacFileEpCacheGetCacheBestFitEntryByOffset(PPDMACFILEENDPOINTCACHE pEndpointCache, RTFOFF off,
1243 PPDMACFILECACHEENTRY *ppEntryAbove,
1244 PPDMACFILECACHEENTRY *ppEntryBelow)
1245{
1246 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1247
1248 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1249
1250 RTSemRWRequestRead(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1251 if (ppEntryAbove)
1252 {
1253 *ppEntryAbove = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, true /*fAbove*/);
1254 if (*ppEntryAbove)
1255 pdmacFileEpCacheEntryRef(*ppEntryAbove);
1256 }
1257
1258 if (ppEntryBelow)
1259 {
1260 *ppEntryBelow = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, false /*fAbove*/);
1261 if (*ppEntryBelow)
1262 pdmacFileEpCacheEntryRef(*ppEntryBelow);
1263 }
1264 RTSemRWReleaseRead(pEndpointCache->SemRWEntries);
1265
1266 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1267}
1268
1269static void pdmacFileEpCacheInsertEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
1270{
1271 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1272
1273 STAM_PROFILE_ADV_START(&pCache->StatTreeInsert, Cache);
1274 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1275 bool fInserted = RTAvlrFileOffsetInsert(pEndpointCache->pTree, &pEntry->Core);
1276 AssertMsg(fInserted, ("Node was not inserted into tree\n"));
1277 STAM_PROFILE_ADV_STOP(&pCache->StatTreeInsert, Cache);
1278 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1279}
1280
1281/**
1282 * Allocates and initializes a new entry for the cache.
1283 * The entry has a reference count of 1.
1284 *
1285 * @returns Pointer to the new cache entry or NULL if out of memory.
1286 * @param pCache The cache the entry belongs to.
1287 * @param pEndoint The endpoint the entry holds data for.
1288 * @param off Start offset.
1289 * @param cbData Size of the cache entry.
1290 * @param pbBuffer Pointer to the buffer to use.
1291 * NULL if a new buffer should be allocated.
1292 * The buffer needs to have the same size of the entry.
1293 */
1294static PPDMACFILECACHEENTRY pdmacFileCacheEntryAlloc(PPDMACFILECACHEGLOBAL pCache,
1295 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1296 RTFOFF off, size_t cbData, uint8_t *pbBuffer)
1297{
1298 PPDMACFILECACHEENTRY pEntryNew = (PPDMACFILECACHEENTRY)RTMemAllocZ(sizeof(PDMACFILECACHEENTRY));
1299
1300 if (RT_UNLIKELY(!pEntryNew))
1301 return NULL;
1302
1303 pEntryNew->Core.Key = off;
1304 pEntryNew->Core.KeyLast = off + cbData - 1;
1305 pEntryNew->pEndpoint = pEndpoint;
1306 pEntryNew->pCache = pCache;
1307 pEntryNew->fFlags = 0;
1308 pEntryNew->cRefs = 1; /* We are using it now. */
1309 pEntryNew->pList = NULL;
1310 pEntryNew->cbData = cbData;
1311 pEntryNew->pWaitingHead = NULL;
1312 pEntryNew->pWaitingTail = NULL;
1313 if (pbBuffer)
1314 pEntryNew->pbData = pbBuffer;
1315 else
1316 pEntryNew->pbData = (uint8_t *)RTMemPageAlloc(cbData);
1317
1318 if (RT_UNLIKELY(!pEntryNew->pbData))
1319 {
1320 RTMemFree(pEntryNew);
1321 return NULL;
1322 }
1323
1324 return pEntryNew;
1325}
1326
1327/**
1328 * Adds a segment to the waiting list for a cache entry
1329 * which is currently in progress.
1330 *
1331 * @returns nothing.
1332 * @param pEntry The cache entry to add the segment to.
1333 * @param pSeg The segment to add.
1334 */
1335DECLINLINE(void) pdmacFileEpCacheEntryAddWaitingSegment(PPDMACFILECACHEENTRY pEntry, PPDMACFILETASKSEG pSeg)
1336{
1337 pSeg->pNext = NULL;
1338
1339 if (pEntry->pWaitingHead)
1340 {
1341 AssertPtr(pEntry->pWaitingTail);
1342
1343 pEntry->pWaitingTail->pNext = pSeg;
1344 pEntry->pWaitingTail = pSeg;
1345 }
1346 else
1347 {
1348 Assert(!pEntry->pWaitingTail);
1349
1350 pEntry->pWaitingHead = pSeg;
1351 pEntry->pWaitingTail = pSeg;
1352 }
1353}
1354
1355/**
1356 * Checks that a set of flags is set/clear acquiring the R/W semaphore
1357 * in exclusive mode.
1358 *
1359 * @returns true if the flag in fSet is set and the one in fClear is clear.
1360 * false othwerise.
1361 * The R/W semaphore is only held if true is returned.
1362 *
1363 * @param pEndpointCache The endpoint cache instance data.
1364 * @param pEntry The entry to check the flags for.
1365 * @param fSet The flag which is tested to be set.
1366 * @param fClear The flag which is tested to be clear.
1367 */
1368DECLINLINE(bool) pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(PPDMACFILEENDPOINTCACHE pEndpointCache,
1369 PPDMACFILECACHEENTRY pEntry,
1370 uint32_t fSet, uint32_t fClear)
1371{
1372 uint32_t fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1373 bool fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1374
1375 if (fPassed)
1376 {
1377 /* Acquire the lock and check again becuase the completion callback might have raced us. */
1378 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1379
1380 fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1381 fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1382
1383 /* Drop the lock if we didn't passed the test. */
1384 if (!fPassed)
1385 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1386 }
1387
1388 return fPassed;
1389}
1390
1391/**
1392 * Copies data to a buffer described by a I/O memory context.
1393 *
1394 * @returns nothing.
1395 * @param pIoMemCtx The I/O memory context to copy the data into.
1396 * @param pbData Pointer to the data data to copy.
1397 * @param cbData Amount of data to copy.
1398 */
1399static void pdmacFileEpCacheCopyToIoMemCtx(PPDMIOMEMCTX pIoMemCtx,
1400 uint8_t *pbData,
1401 size_t cbData)
1402{
1403 while (cbData)
1404 {
1405 size_t cbCopy = cbData;
1406 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbCopy);
1407
1408 AssertPtr(pbBuf);
1409
1410 memcpy(pbBuf, pbData, cbCopy);
1411
1412 cbData -= cbCopy;
1413 pbData += cbCopy;
1414 }
1415}
1416
1417/**
1418 * Copies data from a buffer described by a I/O memory context.
1419 *
1420 * @returns nothing.
1421 * @param pIoMemCtx The I/O memory context to copy the data from.
1422 * @param pbData Pointer to the destination buffer.
1423 * @param cbData Amount of data to copy.
1424 */
1425static void pdmacFileEpCacheCopyFromIoMemCtx(PPDMIOMEMCTX pIoMemCtx,
1426 uint8_t *pbData,
1427 size_t cbData)
1428{
1429 while (cbData)
1430 {
1431 size_t cbCopy = cbData;
1432 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbCopy);
1433
1434 AssertPtr(pbBuf);
1435
1436 memcpy(pbData, pbBuf, cbCopy);
1437
1438 cbData -= cbCopy;
1439 pbData += cbCopy;
1440 }
1441}
1442
1443/**
1444 * Add a buffer described by the I/O memory context
1445 * to the entry waiting for completion.
1446 *
1447 * @returns nothing.
1448 * @param pEntry The entry to add the buffer to.
1449 * @param pTask Task associated with the buffer.
1450 * @param pIoMemCtx The memory context to use.
1451 * @param OffDiff Offset from the start of the buffer
1452 * in the entry.
1453 * @param cbData Amount of data to wait for onthis entry.
1454 * @param fWrite Flag whether the task waits because it wants to write
1455 * to the cache entry.
1456 */
1457static void pdmacFileEpCacheEntryWaitersAdd(PPDMACFILECACHEENTRY pEntry,
1458 PPDMASYNCCOMPLETIONTASKFILE pTask,
1459 PPDMIOMEMCTX pIoMemCtx,
1460 RTFOFF OffDiff,
1461 size_t cbData,
1462 bool fWrite)
1463{
1464 while (cbData)
1465 {
1466 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
1467 size_t cbSeg = cbData;
1468 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbSeg);
1469
1470 pSeg->pTask = pTask;
1471 pSeg->uBufOffset = OffDiff;
1472 pSeg->cbTransfer = cbSeg;
1473 pSeg->pvBuf = pbBuf;
1474 pSeg->fWrite = fWrite;
1475
1476 pdmacFileEpCacheEntryAddWaitingSegment(pEntry, pSeg);
1477
1478 cbData -= cbSeg;
1479 OffDiff += cbSeg;
1480 }
1481}
1482
1483/**
1484 * Passthrough a part of a request directly to the I/O manager
1485 * handling the endpoint.
1486 *
1487 * @returns nothing.
1488 * @param pEndpoint The endpoint.
1489 * @param pTask The task.
1490 * @param pIoMemCtx The I/O memory context to use.
1491 * @param offStart Offset to start transfer from.
1492 * @param cbData Amount of data to transfer.
1493 * @param enmTransferType The transfer type (read/write)
1494 */
1495static void pdmacFileEpCacheRequestPassthrough(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1496 PPDMASYNCCOMPLETIONTASKFILE pTask,
1497 PPDMIOMEMCTX pIoMemCtx,
1498 RTFOFF offStart, size_t cbData,
1499 PDMACTASKFILETRANSFER enmTransferType)
1500{
1501 while (cbData)
1502 {
1503 size_t cbSeg = cbData;
1504 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbSeg);
1505 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEndpoint);
1506 AssertPtr(pIoTask);
1507
1508 pIoTask->pEndpoint = pEndpoint;
1509 pIoTask->enmTransferType = enmTransferType;
1510 pIoTask->Off = offStart;
1511 pIoTask->DataSeg.cbSeg = cbSeg;
1512 pIoTask->DataSeg.pvSeg = pbBuf;
1513 pIoTask->pvUser = pTask;
1514 pIoTask->pfnCompleted = pdmacFileEpTaskCompleted;
1515
1516 offStart += cbSeg;
1517 cbData -= cbSeg;
1518
1519 /* Send it off to the I/O manager. */
1520 pdmacFileEpAddTask(pEndpoint, pIoTask);
1521 }
1522}
1523
1524/**
1525 * Calculate aligned offset and size for a new cache entry
1526 * which do not intersect with an already existing entry and the
1527 * file end.
1528 *
1529 * @returns The number of bytes the entry can hold of the requested amount
1530 * of byte.
1531 * @param pEndpoint The endpoint.
1532 * @param pEndpointCache The endpoint cache.
1533 * @param off The start offset.
1534 * @param cb The number of bytes the entry needs to hold at least.
1535 * @param uAlignment Alignment of the boundary sizes.
1536 * @param poffAligned Where to store the aligned offset.
1537 * @param pcbAligned Where to store the aligned size of the entry.
1538 */
1539static size_t pdmacFileEpCacheEntryBoundariesCalc(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1540 PPDMACFILEENDPOINTCACHE pEndpointCache,
1541 RTFOFF off, size_t cb,
1542 unsigned uAlignment,
1543 RTFOFF *poffAligned, size_t *pcbAligned)
1544{
1545 size_t cbAligned;
1546 size_t cbInEntry = 0;
1547 RTFOFF offAligned;
1548 PPDMACFILECACHEENTRY pEntryAbove = NULL;
1549 PPDMACFILECACHEENTRY pEntryBelow = NULL;
1550
1551 /* Get the best fit entries around the offset */
1552 pdmacFileEpCacheGetCacheBestFitEntryByOffset(pEndpointCache, off,
1553 &pEntryAbove, &pEntryBelow);
1554
1555 /* Log the info */
1556 LogFlow(("%sest fit entry below off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1557 pEntryBelow ? "B" : "No b",
1558 off,
1559 pEntryBelow ? pEntryBelow->Core.Key : 0,
1560 pEntryBelow ? pEntryBelow->Core.KeyLast : 0,
1561 pEntryBelow ? pEntryBelow->cbData : 0));
1562
1563 LogFlow(("%sest fit entry above off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1564 pEntryAbove ? "B" : "No b",
1565 off,
1566 pEntryAbove ? pEntryAbove->Core.Key : 0,
1567 pEntryAbove ? pEntryAbove->Core.KeyLast : 0,
1568 pEntryAbove ? pEntryAbove->cbData : 0));
1569
1570 /* Align the offset first. */
1571 offAligned = off & ~(RTFOFF)(512-1);
1572 if ( pEntryBelow
1573 && offAligned <= pEntryBelow->Core.KeyLast)
1574 offAligned = pEntryBelow->Core.KeyLast;
1575
1576 if ( pEntryAbove
1577 && off + (RTFOFF)cb > pEntryAbove->Core.Key)
1578 {
1579 cbInEntry = pEntryAbove->Core.Key - off;
1580 cbAligned = pEntryAbove->Core.Key - offAligned;
1581 }
1582 else
1583 {
1584 /*
1585 * Align the size to a 4KB boundary.
1586 * Memory size is aligned to a page boundary
1587 * and memory is wasted if the size is rather small.
1588 * (For example reads with a size of 512 bytes).
1589 */
1590 cbInEntry = cb;
1591 cbAligned = RT_ALIGN_Z(cb + (off - offAligned), uAlignment);
1592
1593 /*
1594 * Clip to file size if the original request doesn't
1595 * exceed the file (not an appending write)
1596 */
1597 uint64_t cbReq = off + (RTFOFF)cb;
1598 if (cbReq >= pEndpoint->cbFile)
1599 cbAligned = cbReq - offAligned;
1600 else
1601 cbAligned = RT_MIN(pEndpoint->cbFile - offAligned, cbAligned);
1602 if (pEntryAbove)
1603 {
1604 Assert(pEntryAbove->Core.Key >= off);
1605 cbAligned = RT_MIN(cbAligned, (uint64_t)pEntryAbove->Core.Key - offAligned);
1606 }
1607 }
1608
1609 /* A few sanity checks */
1610 AssertMsg(!pEntryBelow || pEntryBelow->Core.KeyLast < offAligned,
1611 ("Aligned start offset intersects with another cache entry\n"));
1612 AssertMsg(!pEntryAbove || (offAligned + (RTFOFF)cbAligned) <= pEntryAbove->Core.Key,
1613 ("Aligned size intersects with another cache entry\n"));
1614 Assert(cbInEntry <= cbAligned);
1615 AssertMsg( ( offAligned + (RTFOFF)cbAligned <= (RTFOFF)pEndpoint->cbFile
1616 && off + (RTFOFF)cb <= (RTFOFF)pEndpoint->cbFile)
1617 || (offAligned + (RTFOFF)cbAligned <= off + (RTFOFF)cb),
1618 ("Unwanted file size increase\n"));
1619
1620 if (pEntryBelow)
1621 pdmacFileEpCacheEntryRelease(pEntryBelow);
1622 if (pEntryAbove)
1623 pdmacFileEpCacheEntryRelease(pEntryAbove);
1624
1625 LogFlow(("offAligned=%RTfoff cbAligned=%u\n", offAligned, cbAligned));
1626
1627 *poffAligned = offAligned;
1628 *pcbAligned = cbAligned;
1629
1630 return cbInEntry;
1631}
1632
1633/**
1634 * Create a new cache entry evicting data from the cache if required.
1635 *
1636 * @returns Pointer to the new cache entry or NULL
1637 * if not enough bytes could be evicted from the cache.
1638 * @param pEndpoint The endpoint.
1639 * @param pEndpointCache The endpoint cache.
1640 * @param off The offset.
1641 * @param cb Number of bytes the cache entry should have.
1642 * @param uAlignment Alignment the size of the entry should have.
1643 * @param pcbData Where to store the number of bytes the new
1644 * entry can hold. May be lower than actually requested
1645 * due to another entry intersecting the access range.
1646 */
1647static PPDMACFILECACHEENTRY pdmacFileEpCacheEntryCreate(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1648 PPDMACFILEENDPOINTCACHE pEndpointCache,
1649 RTFOFF off, size_t cb,
1650 unsigned uAlignment,
1651 size_t *pcbData)
1652{
1653 RTFOFF offStart = 0;
1654 size_t cbEntry = 0;
1655 PPDMACFILECACHEENTRY pEntryNew = NULL;
1656 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1657 uint8_t *pbBuffer = NULL;
1658
1659 *pcbData = pdmacFileEpCacheEntryBoundariesCalc(pEndpoint,
1660 pEndpointCache,
1661 off, cb,
1662 uAlignment,
1663 &offStart, &cbEntry);
1664
1665 pdmacFileCacheLockEnter(pCache);
1666 bool fEnough = pdmacFileCacheReclaim(pCache, cbEntry, true, &pbBuffer);
1667
1668 if (fEnough)
1669 {
1670 LogFlow(("Evicted enough bytes (%u requested). Creating new cache entry\n", cbEntry));
1671
1672 pEntryNew = pdmacFileCacheEntryAlloc(pCache, pEndpoint,
1673 offStart, cbEntry,
1674 pbBuffer);
1675 if (RT_LIKELY(pEntryNew))
1676 {
1677 pdmacFileCacheEntryAddToList(&pCache->LruRecentlyUsedIn, pEntryNew);
1678 pdmacFileCacheAdd(pCache, cbEntry);
1679 pdmacFileCacheLockLeave(pCache);
1680
1681 pdmacFileEpCacheInsertEntry(pEndpointCache, pEntryNew);
1682
1683 AssertMsg( (off >= pEntryNew->Core.Key)
1684 && (off + (RTFOFF)*pcbData <= pEntryNew->Core.KeyLast + 1),
1685 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1686 off, pEntryNew->Core.Key));
1687 }
1688 else
1689 pdmacFileCacheLockLeave(pCache);
1690 }
1691 else
1692 pdmacFileCacheLockLeave(pCache);
1693
1694 return pEntryNew;
1695}
1696
1697/**
1698 * Reads the specified data from the endpoint using the cache if possible.
1699 *
1700 * @returns VBox status code.
1701 * @param pEndpoint The endpoint to read from.
1702 * @param pTask The task structure used as identifier for this request.
1703 * @param off The offset to start reading from.
1704 * @param paSegments Pointer to the array holding the destination buffers.
1705 * @param cSegments Number of segments in the array.
1706 * @param cbRead Number of bytes to read.
1707 */
1708int pdmacFileEpCacheRead(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
1709 RTFOFF off, PCRTSGSEG paSegments, size_t cSegments,
1710 size_t cbRead)
1711{
1712 int rc = VINF_SUCCESS;
1713 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1714 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1715 PPDMACFILECACHEENTRY pEntry;
1716
1717 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbRead=%u\n",
1718 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbRead));
1719
1720 /* Set to completed to make sure that the task is valid while we access it. */
1721 ASMAtomicWriteBool(&pTask->fCompleted, true);
1722
1723 /* Init the I/O memory context */
1724 PDMIOMEMCTX IoMemCtx;
1725 pdmIoMemCtxInit(&IoMemCtx, paSegments, cSegments);
1726
1727 while (cbRead)
1728 {
1729 size_t cbToRead;
1730
1731 pEntry = pdmacFileEpCacheGetCacheEntryByOffset(pEndpointCache, off);
1732
1733 /*
1734 * If there is no entry we try to create a new one eviciting unused pages
1735 * if the cache is full. If this is not possible we will pass the request through
1736 * and skip the caching (all entries may be still in progress so they can't
1737 * be evicted)
1738 * If we have an entry it can be in one of the LRU lists where the entry
1739 * contains data (recently used or frequently used LRU) so we can just read
1740 * the data we need and put the entry at the head of the frequently used LRU list.
1741 * In case the entry is in one of the ghost lists it doesn't contain any data.
1742 * We have to fetch it again evicting pages from either T1 or T2 to make room.
1743 */
1744 if (pEntry)
1745 {
1746 RTFOFF OffDiff = off - pEntry->Core.Key;
1747
1748 AssertMsg(off >= pEntry->Core.Key,
1749 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1750 off, pEntry->Core.Key));
1751
1752 AssertPtr(pEntry->pList);
1753
1754 cbToRead = RT_MIN(pEntry->cbData - OffDiff, cbRead);
1755
1756 AssertMsg(off + (RTFOFF)cbToRead <= pEntry->Core.Key + pEntry->Core.KeyLast + 1,
1757 ("Buffer of cache entry exceeded off=%RTfoff cbToRead=%d\n",
1758 off, cbToRead));
1759
1760 cbRead -= cbToRead;
1761
1762 if (!cbRead)
1763 STAM_COUNTER_INC(&pCache->cHits);
1764 else
1765 STAM_COUNTER_INC(&pCache->cPartialHits);
1766
1767 STAM_COUNTER_ADD(&pCache->StatRead, cbToRead);
1768
1769 /* Ghost lists contain no data. */
1770 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1771 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1772 {
1773 if (pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
1774 PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
1775 PDMACFILECACHE_ENTRY_IS_DIRTY))
1776 {
1777 /* Entry didn't completed yet. Append to the list */
1778 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1779 &IoMemCtx,
1780 OffDiff, cbToRead,
1781 false /* fWrite */);
1782 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1783 }
1784 else
1785 {
1786 /* Read as much as we can from the entry. */
1787 pdmacFileEpCacheCopyToIoMemCtx(&IoMemCtx, pEntry->pbData + OffDiff, cbToRead);
1788 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToRead);
1789 }
1790
1791 /* Move this entry to the top position */
1792 if (pEntry->pList == &pCache->LruFrequentlyUsed)
1793 {
1794 pdmacFileCacheLockEnter(pCache);
1795 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
1796 pdmacFileCacheLockLeave(pCache);
1797 }
1798 /* Release the entry */
1799 pdmacFileEpCacheEntryRelease(pEntry);
1800 }
1801 else
1802 {
1803 uint8_t *pbBuffer = NULL;
1804
1805 LogFlow(("Fetching data for ghost entry %#p from file\n", pEntry));
1806
1807 pdmacFileCacheLockEnter(pCache);
1808 pdmacFileCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
1809 bool fEnough = pdmacFileCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
1810
1811 /* Move the entry to Am and fetch it to the cache. */
1812 if (fEnough)
1813 {
1814 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
1815 pdmacFileCacheAdd(pCache, pEntry->cbData);
1816 pdmacFileCacheLockLeave(pCache);
1817
1818 if (pbBuffer)
1819 pEntry->pbData = pbBuffer;
1820 else
1821 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
1822 AssertPtr(pEntry->pbData);
1823
1824 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1825 &IoMemCtx,
1826 OffDiff, cbToRead,
1827 false /* fWrite */);
1828 pdmacFileCacheReadFromEndpoint(pEntry);
1829 /* Release the entry */
1830 pdmacFileEpCacheEntryRelease(pEntry);
1831 }
1832 else
1833 {
1834 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1835 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
1836 RTAvlrFileOffsetRemove(pEndpointCache->pTree, pEntry->Core.Key);
1837 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
1838 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1839
1840 pdmacFileCacheLockLeave(pCache);
1841
1842 RTMemFree(pEntry);
1843
1844 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1845 &IoMemCtx, off, cbToRead,
1846 PDMACTASKFILETRANSFER_READ);
1847 }
1848 }
1849 }
1850 else
1851 {
1852#ifdef VBOX_WITH_IO_READ_CACHE
1853 /* No entry found for this offset. Create a new entry and fetch the data to the cache. */
1854 PPDMACFILECACHEENTRY pEntryNew = pdmacFileEpCacheEntryCreate(pEndpoint,
1855 pEndpointCache,
1856 off, cbRead,
1857 PAGE_SIZE,
1858 &cbToRead);
1859
1860 cbRead -= cbToRead;
1861
1862 if (pEntryNew)
1863 {
1864 if (!cbRead)
1865 STAM_COUNTER_INC(&pCache->cMisses);
1866 else
1867 STAM_COUNTER_INC(&pCache->cPartialHits);
1868
1869 pdmacFileEpCacheEntryWaitersAdd(pEntryNew, pTask,
1870 &IoMemCtx,
1871 off - pEntryNew->Core.Key,
1872 cbToRead,
1873 false /* fWrite */);
1874 pdmacFileCacheReadFromEndpoint(pEntryNew);
1875 pdmacFileEpCacheEntryRelease(pEntryNew); /* it is protected by the I/O in progress flag now. */
1876 }
1877 else
1878 {
1879 /*
1880 * There is not enough free space in the cache.
1881 * Pass the request directly to the I/O manager.
1882 */
1883 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToRead));
1884
1885 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1886 &IoMemCtx, off, cbToRead,
1887 PDMACTASKFILETRANSFER_READ);
1888 }
1889#else
1890 /* Clip read size if neccessary. */
1891 PPDMACFILECACHEENTRY pEntryAbove;
1892 pdmacFileEpCacheGetCacheBestFitEntryByOffset(pEndpointCache, off,
1893 &pEntryAbove, NULL);
1894
1895 if (pEntryAbove)
1896 {
1897 if (off + (RTFOFF)cbRead > pEntryAbove->Core.Key)
1898 cbToRead = pEntryAbove->Core.Key - off;
1899 else
1900 cbToRead = cbRead;
1901
1902 pdmacFileEpCacheEntryRelease(pEntryAbove);
1903 }
1904 else
1905 cbToRead = cbRead;
1906
1907 cbRead -= cbToRead;
1908 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1909 &IoMemCtx, off, cbToRead,
1910 PDMACTASKFILETRANSFER_READ);
1911#endif
1912 }
1913 off += cbToRead;
1914 }
1915
1916 ASMAtomicWriteBool(&pTask->fCompleted, false);
1917
1918 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
1919 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
1920 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
1921 else
1922 rc = VINF_AIO_TASK_PENDING;
1923
1924 LogFlowFunc((": Leave rc=%Rrc\n", rc));
1925
1926 return rc;
1927}
1928
1929/**
1930 * Writes the given data to the endpoint using the cache if possible.
1931 *
1932 * @returns VBox status code.
1933 * @param pEndpoint The endpoint to write to.
1934 * @param pTask The task structure used as identifier for this request.
1935 * @param off The offset to start writing to
1936 * @param paSegments Pointer to the array holding the source buffers.
1937 * @param cSegments Number of segments in the array.
1938 * @param cbWrite Number of bytes to write.
1939 */
1940int pdmacFileEpCacheWrite(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
1941 RTFOFF off, PCRTSGSEG paSegments, size_t cSegments,
1942 size_t cbWrite)
1943{
1944 int rc = VINF_SUCCESS;
1945 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1946 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1947 PPDMACFILECACHEENTRY pEntry;
1948
1949 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbWrite=%u\n",
1950 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbWrite));
1951
1952 /* Set to completed to make sure that the task is valid while we access it. */
1953 ASMAtomicWriteBool(&pTask->fCompleted, true);
1954
1955 /* Init the I/O memory context */
1956 PDMIOMEMCTX IoMemCtx;
1957 pdmIoMemCtxInit(&IoMemCtx, paSegments, cSegments);
1958
1959 while (cbWrite)
1960 {
1961 size_t cbToWrite;
1962
1963 pEntry = pdmacFileEpCacheGetCacheEntryByOffset(pEndpointCache, off);
1964
1965 if (pEntry)
1966 {
1967 /* Write the data into the entry and mark it as dirty */
1968 AssertPtr(pEntry->pList);
1969
1970 RTFOFF OffDiff = off - pEntry->Core.Key;
1971
1972 AssertMsg(off >= pEntry->Core.Key,
1973 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1974 off, pEntry->Core.Key));
1975
1976 cbToWrite = RT_MIN(pEntry->cbData - OffDiff, cbWrite);
1977 cbWrite -= cbToWrite;
1978
1979 if (!cbWrite)
1980 STAM_COUNTER_INC(&pCache->cHits);
1981 else
1982 STAM_COUNTER_INC(&pCache->cPartialHits);
1983
1984 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
1985
1986 /* Ghost lists contain no data. */
1987 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1988 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1989 {
1990 /* Check if the entry is dirty. */
1991 if(pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
1992 PDMACFILECACHE_ENTRY_IS_DIRTY,
1993 0))
1994 {
1995 /* If it is dirty but not in progrss just update the data. */
1996 if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS))
1997 {
1998 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
1999 pEntry->pbData + OffDiff,
2000 cbToWrite);
2001 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2002 }
2003 else
2004 {
2005 /* The data isn't written to the file yet */
2006 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2007 &IoMemCtx,
2008 OffDiff, cbToWrite,
2009 true /* fWrite */);
2010 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2011 }
2012
2013 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2014 }
2015 else /* Dirty bit not set */
2016 {
2017 /*
2018 * Check if a read is in progress for this entry.
2019 * We have to defer processing in that case.
2020 */
2021 if(pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
2022 PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
2023 0))
2024 {
2025 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2026 &IoMemCtx,
2027 OffDiff, cbToWrite,
2028 true /* fWrite */);
2029 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2030 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2031 }
2032 else /* I/O in progress flag not set */
2033 {
2034 /* Write as much as we can into the entry and update the file. */
2035 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2036 pEntry->pbData + OffDiff,
2037 cbToWrite);
2038 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2039
2040 bool fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
2041 if (fCommit)
2042 pdmacFileCacheCommitDirtyEntries(pCache);
2043 }
2044 } /* Dirty bit not set */
2045
2046 /* Move this entry to the top position */
2047 if (pEntry->pList == &pCache->LruFrequentlyUsed)
2048 {
2049 pdmacFileCacheLockEnter(pCache);
2050 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2051 pdmacFileCacheLockLeave(pCache);
2052 }
2053
2054 pdmacFileEpCacheEntryRelease(pEntry);
2055 }
2056 else /* Entry is on the ghost list */
2057 {
2058 uint8_t *pbBuffer = NULL;
2059
2060 pdmacFileCacheLockEnter(pCache);
2061 pdmacFileCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
2062 bool fEnough = pdmacFileCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
2063
2064 if (fEnough)
2065 {
2066 /* Move the entry to Am and fetch it to the cache. */
2067 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2068 pdmacFileCacheAdd(pCache, pEntry->cbData);
2069 pdmacFileCacheLockLeave(pCache);
2070
2071 if (pbBuffer)
2072 pEntry->pbData = pbBuffer;
2073 else
2074 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
2075 AssertPtr(pEntry->pbData);
2076
2077 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2078 &IoMemCtx,
2079 OffDiff, cbToWrite,
2080 true /* fWrite */);
2081 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2082 pdmacFileCacheReadFromEndpoint(pEntry);
2083
2084 /* Release the reference. If it is still needed the I/O in progress flag should protect it now. */
2085 pdmacFileEpCacheEntryRelease(pEntry);
2086 }
2087 else
2088 {
2089 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
2090 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2091 RTAvlrFileOffsetRemove(pEndpointCache->pTree, pEntry->Core.Key);
2092 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2093 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2094
2095 pdmacFileCacheLockLeave(pCache);
2096
2097 RTMemFree(pEntry);
2098 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
2099 &IoMemCtx, off, cbToWrite,
2100 PDMACTASKFILETRANSFER_WRITE);
2101 }
2102 }
2103 }
2104 else /* No entry found */
2105 {
2106 /*
2107 * No entry found. Try to create a new cache entry to store the data in and if that fails
2108 * write directly to the file.
2109 */
2110 PPDMACFILECACHEENTRY pEntryNew = pdmacFileEpCacheEntryCreate(pEndpoint,
2111 pEndpointCache,
2112 off, cbWrite,
2113 512,
2114 &cbToWrite);
2115
2116 cbWrite -= cbToWrite;
2117
2118 if (pEntryNew)
2119 {
2120 RTFOFF offDiff = off - pEntryNew->Core.Key;
2121
2122 STAM_COUNTER_INC(&pCache->cHits);
2123
2124 /*
2125 * Check if it is possible to just write the data without waiting
2126 * for it to get fetched first.
2127 */
2128 if (!offDiff && pEntryNew->cbData == cbToWrite)
2129 {
2130 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2131 pEntryNew->pbData,
2132 cbToWrite);
2133 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2134
2135 bool fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntryNew);
2136 if (fCommit)
2137 pdmacFileCacheCommitDirtyEntries(pCache);
2138 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
2139 }
2140 else
2141 {
2142 /* Defer the write and fetch the data from the endpoint. */
2143 pdmacFileEpCacheEntryWaitersAdd(pEntryNew, pTask,
2144 &IoMemCtx,
2145 offDiff, cbToWrite,
2146 true /* fWrite */);
2147 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2148 pdmacFileCacheReadFromEndpoint(pEntryNew);
2149 }
2150
2151 pdmacFileEpCacheEntryRelease(pEntryNew);
2152 }
2153 else
2154 {
2155 /*
2156 * There is not enough free space in the cache.
2157 * Pass the request directly to the I/O manager.
2158 */
2159 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToWrite));
2160
2161 STAM_COUNTER_INC(&pCache->cMisses);
2162
2163 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
2164 &IoMemCtx, off, cbToWrite,
2165 PDMACTASKFILETRANSFER_WRITE);
2166 }
2167 }
2168
2169 off += cbToWrite;
2170 }
2171
2172 ASMAtomicWriteBool(&pTask->fCompleted, false);
2173
2174 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
2175 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
2176 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
2177 else
2178 rc = VINF_AIO_TASK_PENDING;
2179
2180 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2181
2182 return rc;
2183}
2184
2185int pdmacFileEpCacheFlush(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint)
2186{
2187 int rc = VINF_SUCCESS;
2188
2189 LogFlowFunc((": pEndpoint=%#p{%s}\n", pEndpoint, pEndpoint->Core.pszUri));
2190
2191 /* Commit dirty entries in the cache. */
2192 pdmacFileCacheEndpointCommit(&pEndpoint->DataCache);
2193
2194 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2195 return rc;
2196}
2197
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette