[FASTFAT] Implement delayed close
authorPierre Schweitzer <pierre@reactos.org>
Sat, 18 Aug 2018 15:04:02 +0000 (17:04 +0200)
committerPierre Schweitzer <pierre@reactos.org>
Sat, 18 Aug 2018 17:03:30 +0000 (19:03 +0200)
When we're about to close a file (ie, forget everything about it
and release any associated structure), actually delay it.
This allows keep data fresh in memory for faster reuse in case
it would be required. The effective closing will only happen after some time.

For specific operations, this will produce a real speed up in ReactOS.
For instance, with that patch, Winamp starts within seconds, instead of dozen
of minutes.
In most cases, it will bring ReactOS to performances it had before fixing
the huge leak in FastFAT (commit 94ead99) without leaking the whole FS.

For now, due to regressions, this is only activated for files and not
for directories. Once it gets fixed, it will be enabled for both.

CORE-14826
CORE-14917

drivers/filesystems/fastfat/cleanup.c
drivers/filesystems/fastfat/close.c
drivers/filesystems/fastfat/create.c
drivers/filesystems/fastfat/iface.c
drivers/filesystems/fastfat/kdbg.c
drivers/filesystems/fastfat/vfat.h

index d19fa94..ae35408 100644 (file)
@@ -4,6 +4,7 @@
  * FILE:             drivers/fs/vfat/cleanup.c
  * PURPOSE:          VFAT Filesystem
  * PROGRAMMER:       Jason Filby (jasonfilby@yahoo.com)
+ *                   Pierre Schweitzer (pierre@reactos.org)
  */
 
 /* INCLUDES *****************************************************************/
@@ -127,6 +128,29 @@ VfatCleanupFile(
         {
             IoRemoveShareAccess(FileObject, &pFcb->FCBShareAccess);
         }
+        /* If that's the last open handle we just closed, try to see whether
+         * we can delay close operation
+         */
+        else if (!BooleanFlagOn(pFcb->Flags, FCB_DELETE_PENDING) && !BooleanFlagOn(pFcb->Flags, FCB_IS_PAGE_FILE) &&
+                 !BooleanFlagOn(pFcb->Flags, FCB_IS_FAT) && !BooleanFlagOn(pFcb->Flags, FCB_IS_VOLUME))
+        {
+            /* This is only allowed if that's a directory with no open files
+             * OR if it's a file with no section opened
+             * FIXME: only allow files for now
+             */
+#if 0
+            if ((vfatFCBIsDirectory(pFcb) && IsListEmpty(&pFcb->ParentListHead)) ||
+                (!vfatFCBIsDirectory(pFcb) && FileObject->SectionObjectPointer->DataSectionObject == NULL &&
+                 FileObject->SectionObjectPointer->ImageSectionObject == NULL))
+#else
+            if (!vfatFCBIsDirectory(pFcb) && FileObject->SectionObjectPointer->DataSectionObject == NULL &&
+                FileObject->SectionObjectPointer->ImageSectionObject == NULL)
+#endif
+            {
+                DPRINT("Delaying close of: %wZ\n", &pFcb->PathNameU);
+                SetFlag(pFcb->Flags, FCB_DELAYED_CLOSE);
+            }
+        }
 
         FileObject->Flags |= FO_CLEANUP_COMPLETE;
 #ifdef KDBG
index bb5cfaa..0c313e6 100644 (file)
@@ -4,6 +4,7 @@
  * FILE:             drivers/filesystems/fastfat/close.c
  * PURPOSE:          VFAT Filesystem
  * PROGRAMMER:       Jason Filby (jasonfilby@yahoo.com)
+ *                   Pierre Schweitzer (pierre@reactos.org)
  */
 
 /* INCLUDES *****************************************************************/
 
 /* FUNCTIONS ****************************************************************/
 
+VOID
+VfatCommonCloseFile(
+    PDEVICE_EXTENSION DeviceExt,
+    PVFATFCB pFcb)
+{
+    /* Nothing to do for volumes */
+    if (BooleanFlagOn(pFcb->Flags, FCB_IS_VOLUME))
+    {
+        return;
+    }
+
+    /* If cache is still initialized, release it
+     * This only affects directories
+     */
+    if (pFcb->OpenHandleCount == 0 && BooleanFlagOn(pFcb->Flags, FCB_CACHE_INITIALIZED))
+    {
+        PFILE_OBJECT tmpFileObject;
+        tmpFileObject = pFcb->FileObject;
+        if (tmpFileObject != NULL)
+        {
+            pFcb->FileObject = NULL;
+            CcUninitializeCacheMap(tmpFileObject, NULL, NULL);
+            ClearFlag(pFcb->Flags, FCB_CACHE_INITIALIZED);
+            ObDereferenceObject(tmpFileObject);
+        }
+    }
+
+#ifdef KDBG
+    pFcb->Flags |= FCB_CLOSED;
+#endif
+
+    /* Release the FCB, we likely cause its deletion */
+    vfatReleaseFCB(DeviceExt, pFcb);
+}
+
+VOID
+NTAPI
+VfatCloseWorker(
+    IN PDEVICE_OBJECT DeviceObject,
+    IN PVOID Context)
+{
+    PLIST_ENTRY Entry;
+    PVFATFCB pFcb;
+    PDEVICE_EXTENSION Vcb;
+    PVFAT_CLOSE_CONTEXT CloseContext;
+    BOOLEAN ConcurrentDeletion;
+
+    /* Start removing work items */
+    ExAcquireFastMutex(&VfatGlobalData->CloseMutex);
+    while (!IsListEmpty(&VfatGlobalData->CloseListHead))
+    {
+        Entry = RemoveHeadList(&VfatGlobalData->CloseListHead);
+        CloseContext = CONTAINING_RECORD(Entry, VFAT_CLOSE_CONTEXT, CloseListEntry);
+
+        /* One less */
+        --VfatGlobalData->CloseCount;
+        /* Reset its entry to detect concurrent deletions */
+        InitializeListHead(&CloseContext->CloseListEntry);
+        ExReleaseFastMutex(&VfatGlobalData->CloseMutex);
+
+        /* Get the elements */
+        Vcb = CloseContext->Vcb;
+        pFcb = CloseContext->Fcb;
+        ExAcquireResourceExclusiveLite(&Vcb->DirResource, TRUE);
+        /* If it didn't got deleted in between */
+        if (BooleanFlagOn(pFcb->Flags, FCB_DELAYED_CLOSE))
+        {
+            /* Close it! */
+            DPRINT("Late closing: %wZ\n", &pFcb->PathNameU);
+            ClearFlag(pFcb->Flags, FCB_DELAYED_CLOSE);
+            pFcb->CloseContext = NULL;
+            VfatCommonCloseFile(Vcb, pFcb);
+            ConcurrentDeletion = FALSE;
+        }
+        else
+        {
+            /* Otherwise, mark not to delete it */
+            ConcurrentDeletion = TRUE;
+        }
+        ExReleaseResourceLite(&Vcb->DirResource);
+
+        /* If we were the fastest, delete the context */
+        if (!ConcurrentDeletion)
+        {
+            ExFreeToPagedLookasideList(&VfatGlobalData->CloseContextLookasideList, CloseContext);
+        }
+
+        /* Lock again the list */
+        ExAcquireFastMutex(&VfatGlobalData->CloseMutex);
+    }
+
+    /* We're done, bye! */
+    VfatGlobalData->CloseWorkerRunning = FALSE;
+    ExReleaseFastMutex(&VfatGlobalData->CloseMutex);
+}
+
+NTSTATUS
+VfatPostCloseFile(
+    PDEVICE_EXTENSION DeviceExt,
+    PFILE_OBJECT FileObject)
+{
+    PVFAT_CLOSE_CONTEXT CloseContext;
+
+    /* Allocate a work item */
+    CloseContext = ExAllocateFromPagedLookasideList(&VfatGlobalData->CloseContextLookasideList);
+    if (CloseContext == NULL)
+    {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    /* Set relevant fields */
+    CloseContext->Vcb = DeviceExt;
+    CloseContext->Fcb = FileObject->FsContext;
+    CloseContext->Fcb->CloseContext = CloseContext;
+
+    /* Acquire the lock to insert in list */
+    ExAcquireFastMutex(&VfatGlobalData->CloseMutex);
+
+    /* One more element */
+    InsertTailList(&VfatGlobalData->CloseListHead, &CloseContext->CloseListEntry);
+    ++VfatGlobalData->CloseCount;
+
+    /* If we have more than 16 items in list, and no worker thread
+     * start a new one
+     */
+    if (VfatGlobalData->CloseCount > 16 && !VfatGlobalData->CloseWorkerRunning)
+    {
+        VfatGlobalData->CloseWorkerRunning = TRUE;
+        IoQueueWorkItem(VfatGlobalData->CloseWorkItem, VfatCloseWorker, CriticalWorkQueue, NULL);
+    }
+
+    /* We're done */
+    ExReleaseFastMutex(&VfatGlobalData->CloseMutex);
+
+    return STATUS_SUCCESS;
+}
+
 /*
  * FUNCTION: Closes a file
  */
@@ -25,7 +163,6 @@ VfatCloseFile(
 {
     PVFATFCB pFcb;
     PVFATCCB pCcb;
-    BOOLEAN IsVolume;
     NTSTATUS Status = STATUS_SUCCESS;
 
     DPRINT("VfatCloseFile(DeviceExt %p, FileObject %p)\n",
@@ -40,44 +177,23 @@ VfatCloseFile(
         return STATUS_SUCCESS;
     }
 
-    IsVolume = BooleanFlagOn(pFcb->Flags, FCB_IS_VOLUME);
-    if (IsVolume)
+    if (pCcb)
     {
-        DPRINT("Volume\n");
-        FileObject->FsContext2 = NULL;
+        vfatDestroyCCB(pCcb);
     }
-    else
-    {
-        if (pFcb->OpenHandleCount == 0 && BooleanFlagOn(pFcb->Flags, FCB_CACHE_INITIALIZED))
-        {
-            PFILE_OBJECT tmpFileObject;
-            tmpFileObject = pFcb->FileObject;
-            if (tmpFileObject != NULL)
-            {
-                pFcb->FileObject = NULL;
-                CcUninitializeCacheMap(tmpFileObject, NULL, NULL);
-                ClearFlag(pFcb->Flags, FCB_CACHE_INITIALIZED);
-                ObDereferenceObject(tmpFileObject);
-            }
-        }
 
-#ifdef KDBG
-        pFcb->Flags |= FCB_CLOSED;
-#endif
-        vfatReleaseFCB(DeviceExt, pFcb);
+    /* If we have to close immediately, or if delaying failed, close */
+    if (!BooleanFlagOn(pFcb->Flags, FCB_DELAYED_CLOSE) || !NT_SUCCESS(VfatPostCloseFile(DeviceExt, FileObject)))
+    {
+        VfatCommonCloseFile(DeviceExt, pFcb);
     }
 
     FileObject->FsContext2 = NULL;
     FileObject->FsContext = NULL;
     FileObject->SectionObjectPointer = NULL;
 
-    if (pCcb)
-    {
-        vfatDestroyCCB(pCcb);
-    }
-
 #ifdef ENABLE_SWAPOUT
-    if (IsVolume && DeviceExt->OpenHandleCount == 0)
+    if (BooleanFlagOn(pFcb->Flags, FCB_IS_VOLUME) && DeviceExt->OpenHandleCount == 0)
     {
         VfatCheckForDismount(DeviceExt, FALSE);
     }
index e2fbcca..6dbf032 100644 (file)
@@ -370,6 +370,44 @@ VfatOpenFile(
         return STATUS_CANNOT_DELETE;
     }
 
+    /* If that one was marked for closing, remove it */
+    if (BooleanFlagOn(Fcb->Flags, FCB_DELAYED_CLOSE))
+    {
+        BOOLEAN ConcurrentDeletion;
+        PVFAT_CLOSE_CONTEXT CloseContext;
+
+        /* Get the context */
+        CloseContext = Fcb->CloseContext;
+        /* Is someone already taking over? */
+        if (CloseContext != NULL)
+        {
+            ConcurrentDeletion = FALSE;
+            /* Lock list */
+            ExAcquireFastMutex(&VfatGlobalData->CloseMutex);
+            /* Check whether it was already removed, if not, do it */
+            if (!IsListEmpty(&CloseContext->CloseListEntry))
+            {
+                RemoveEntryList(&CloseContext->CloseListEntry);
+                --VfatGlobalData->CloseCount;
+                ConcurrentDeletion = TRUE;
+            }
+            ExReleaseFastMutex(&VfatGlobalData->CloseMutex);
+
+            /* It's not delayed anymore! */
+            ClearFlag(Fcb->Flags, FCB_DELAYED_CLOSE);
+            /* Release the extra reference (would have been removed by IRP_MJ_CLOSE) */
+            vfatReleaseFCB(DeviceExt, Fcb);
+            Fcb->CloseContext = NULL;
+            /* If no concurrent deletion, free work item */
+            if (!ConcurrentDeletion)
+            {
+                ExFreeToPagedLookasideList(&VfatGlobalData->CloseContextLookasideList, CloseContext);
+            }
+        }
+
+        DPRINT("Reusing delayed close FCB for %wZ\n", &Fcb->PathNameU);
+    }
+
     DPRINT("Attaching FCB to fileObject\n");
     Status = vfatAttachFCBToFileObject(DeviceExt, Fcb, FileObject);
     if (!NT_SUCCESS(Status))
index 41653a2..e99409c 100644 (file)
@@ -21,6 +21,7 @@
  * FILE:             drivers/fs/vfat/iface.c
  * PURPOSE:          VFAT Filesystem
  * PROGRAMMER:       Jason Filby (jasonfilby@yahoo.com)
+ *                   Pierre Schweitzer (pierre@reactos.org)
  */
 
 /* INCLUDES *****************************************************************/
@@ -95,6 +96,18 @@ DriverEntry(
      * has been detected:
     VfatGlobalData->Flags = VFAT_BREAK_ON_CORRUPTION; */
 
+    /* Delayed close support */
+    ExInitializeFastMutex(&VfatGlobalData->CloseMutex);
+    InitializeListHead(&VfatGlobalData->CloseListHead);
+    VfatGlobalData->CloseCount = 0;
+    VfatGlobalData->CloseWorkerRunning = FALSE;
+    VfatGlobalData->CloseWorkItem = IoAllocateWorkItem(DeviceObject);
+    if (VfatGlobalData->CloseWorkItem == NULL)
+    {
+        IoDeleteDevice(DeviceObject);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
     DeviceObject->Flags |= DO_DIRECT_IO;
     DriverObject->MajorFunction[IRP_MJ_CLOSE] = VfatBuildRequest;
     DriverObject->MajorFunction[IRP_MJ_CREATE] = VfatBuildRequest;
@@ -132,6 +145,8 @@ DriverEntry(
                                     NULL, NULL, 0, sizeof(VFATCCB), TAG_CCB, 0);
     ExInitializeNPagedLookasideList(&VfatGlobalData->IrpContextLookasideList,
                                     NULL, NULL, 0, sizeof(VFAT_IRP_CONTEXT), TAG_IRP, 0);
+    ExInitializePagedLookasideList(&VfatGlobalData->CloseContextLookasideList,
+                                   NULL, NULL, 0, sizeof(VFAT_CLOSE_CONTEXT), TAG_CLOSE, 0);
 
     ExInitializeResourceLite(&VfatGlobalData->VolumeListLock);
     InitializeListHead(&VfatGlobalData->VolumeListHead);
index b278329..ef2ded5 100644 (file)
@@ -109,10 +109,11 @@ vfatKdbgHandler(
                      ListEntry = ListEntry->Flink)
                 {
                     Fcb = CONTAINING_RECORD(ListEntry, VFATFCB, FcbListEntry);
-                    DPRINT1("FCB %p (ref: %d, oc: %d %s %s) for FO %p with path: %.*S\n",
+                    DPRINT1("FCB %p (ref: %d, oc: %d %s %s %s) for FO %p with path: %.*S\n",
                             Fcb, Fcb->RefCount, Fcb->OpenHandleCount,
                             ((Fcb->Flags & FCB_CLEANED_UP) ? "U" : "NU"),
                             ((Fcb->Flags & FCB_CLOSED) ? "C" : "NC"),
+                            ((Fcb->Flags & FCB_DELAYED_CLOSE) ? "D" : "ND"),
                             Fcb->FileObject, Fcb->PathNameU.Length, Fcb->PathNameU.Buffer);
                 }
             }
index bec3dc8..f03d513 100644 (file)
@@ -269,6 +269,7 @@ typedef struct
 struct _VFATFCB;
 struct _VFAT_DIRENTRY_CONTEXT;
 struct _VFAT_MOVE_CONTEXT;
+struct _VFAT_CLOSE_CONTEXT;
 
 typedef struct _HASHENTRY
 {
@@ -408,8 +409,14 @@ typedef struct
     NPAGED_LOOKASIDE_LIST FcbLookasideList;
     NPAGED_LOOKASIDE_LIST CcbLookasideList;
     NPAGED_LOOKASIDE_LIST IrpContextLookasideList;
+    PAGED_LOOKASIDE_LIST CloseContextLookasideList;
     FAST_IO_DISPATCH FastIoDispatch;
     CACHE_MANAGER_CALLBACKS CacheMgrCallbacks;
+    FAST_MUTEX CloseMutex;
+    ULONG CloseCount;
+    LIST_ENTRY CloseListHead;
+    BOOLEAN CloseWorkerRunning;
+    PIO_WORKITEM CloseWorkItem;
 } VFAT_GLOBAL_DATA, *PVFAT_GLOBAL_DATA;
 
 extern PVFAT_GLOBAL_DATA VfatGlobalData;
@@ -420,9 +427,10 @@ extern PVFAT_GLOBAL_DATA VfatGlobalData;
 #define FCB_IS_PAGE_FILE        0x0008
 #define FCB_IS_VOLUME           0x0010
 #define FCB_IS_DIRTY            0x0020
+#define FCB_DELAYED_CLOSE       0x0040
 #ifdef KDBG
-#define FCB_CLEANED_UP          0x0040
-#define FCB_CLOSED              0x0080
+#define FCB_CLEANED_UP          0x0080
+#define FCB_CLOSED              0x0100
 #endif
 
 #define NODE_TYPE_FCB ((CSHORT)0x0502)
@@ -510,6 +518,8 @@ typedef struct _VFATFCB
     FAST_MUTEX LastMutex;
     ULONG LastCluster;
     ULONG LastOffset;
+
+    struct _VFAT_CLOSE_CONTEXT * CloseContext;
 } VFATFCB, *PVFATFCB;
 
 #define CCB_DELETE_ON_CLOSE     0x0001
@@ -528,6 +538,7 @@ typedef struct _VFATCCB
 #define TAG_FCB  'BCFV'
 #define TAG_IRP  'PRIV'
 #define TAG_VFAT 'TAFV'
+#define TAG_CLOSE 'xtaF'
 
 #define ENTRIES_PER_SECTOR (BLOCKSIZE / sizeof(FATDirEntry))
 
@@ -588,6 +599,13 @@ typedef struct _VFAT_MOVE_CONTEXT
     BOOLEAN InPlace;
 } VFAT_MOVE_CONTEXT, *PVFAT_MOVE_CONTEXT;
 
+typedef struct _VFAT_CLOSE_CONTEXT
+{
+    PDEVICE_EXTENSION Vcb;
+    PVFATFCB Fcb;
+    LIST_ENTRY CloseListEntry;
+} VFAT_CLOSE_CONTEXT, *PVFAT_CLOSE_CONTEXT;
+
 FORCEINLINE
 NTSTATUS
 VfatMarkIrpContextForQueue(PVFAT_IRP_CONTEXT IrpContext)