From d5e89ac267b4bcbc899aa42ef8a285154d863872 Mon Sep 17 00:00:00 2001 From: Ruslan Cheremin Date: Thu, 9 Oct 2025 18:23:51 +0200 Subject: [PATCH] [vfs] IJPL-189502 cache-avoiding files use already cached children if available + cache-avoiding VirtualFile impl uses already loaded children from VFS, if they are available, instead of accessing the underlying FS GitOrigin-RevId: 541d5007c0de2331f44f3efefecbdb9930d9da99 --- .../analysis-api/api-dump-experimental.txt | 1 + .../CacheAvoidingVirtualFileWrapper.java | 20 +++++++++---------- .../openapi/vfs/newvfs/NewVirtualFile.java | 18 +++++++++++++++++ .../vfs/newvfs/impl/VirtualDirectoryImpl.java | 1 + .../vfs/newvfs/impl/VirtualFileImpl.java | 5 +++++ .../newvfs/impl/VirtualFileSystemEntry.java | 5 +++++ 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/platform/analysis-api/api-dump-experimental.txt b/platform/analysis-api/api-dump-experimental.txt index 7217964158ab..17b5f4fbe3e0 100644 --- a/platform/analysis-api/api-dump-experimental.txt +++ b/platform/analysis-api/api-dump-experimental.txt @@ -342,6 +342,7 @@ c:com.intellij.openapi.fileEditor.OpenFileDescriptor a:com.intellij.openapi.vfs.newvfs.NewVirtualFile - com.intellij.openapi.vfs.VirtualFile - com.intellij.openapi.vfs.VirtualFileWithId +- *:allChildrenLoaded():Z - *a:isOffline():Z - *a:setOffline(Z):V a:com.intellij.openapi.vfs.newvfs.RefreshQueue diff --git a/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/CacheAvoidingVirtualFileWrapper.java b/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/CacheAvoidingVirtualFileWrapper.java index ac0be5ec746c..49fe48799c31 100644 --- a/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/CacheAvoidingVirtualFileWrapper.java +++ b/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/CacheAvoidingVirtualFileWrapper.java @@ -16,6 +16,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.file.Path; +import java.util.Collection; import java.util.function.Supplier; /** @@ -71,7 +72,13 @@ public final class CacheAvoidingVirtualFileWrapper extends VirtualFile implement @Override public VirtualFile[] getChildren() { - //MAYBE RC: cache children once calculated? + if (wrappedFile.allChildrenLoaded()) {//fast-track: + Collection cachedChildren = wrappedFile.getCachedChildren(); + return cachedChildren.stream() + .map(child -> ((NewVirtualFile)child).asCacheAvoiding()) + .toArray(VirtualFile[]::new); + } + NewVirtualFileSystem fileSystem = wrappedFile.getFileSystem(); String[] childNames = fileSystem.list(wrappedFile); VirtualFile[] children = new VirtualFile[childNames.length]; @@ -84,7 +91,7 @@ public final class CacheAvoidingVirtualFileWrapper extends VirtualFile implement } @Override - public @Nullable VirtualFile findChild(@NotNull String childName) { + public VirtualFile findChild(@NotNull String childName) { NewVirtualFileSystem fileSystem = wrappedFile.getFileSystem(); NewVirtualFile child = wrappedFile.findChildIfCached(childName); if (child != null) { @@ -104,14 +111,7 @@ public final class CacheAvoidingVirtualFileWrapper extends VirtualFile implement @Override public @NotNull VirtualFile findOrCreateChildData(Object requestor, @NotNull String name) throws IOException { - VirtualFile child = findChild(name); - if (child != null) return child; - //MAYBE RC: below we create new _cached_ child, which violates this class general contract that it does NOT create new cache - // entries. From one side, it seems to be explicitly requested -- but maybe it is better to still adhere the - // contract? Maybe it is more consistent to create a non-cached child (=fileSystem.createChildFile(requestor, parent, name)) - // -- i.e. create physical file, and TransientVirtualFileImpl around it? - child = createChildData(requestor, name); - return new CacheAvoidingVirtualFileWrapper((NewVirtualFile)child); + return findChild(name); } // ===================================================================================== diff --git a/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java b/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java index ca8241e81e60..9320b2b0078c 100644 --- a/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java +++ b/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java @@ -95,8 +95,26 @@ public abstract class NewVirtualFile extends VirtualFile implements VirtualFileW }); } + /** + * @return true if VFS _thinks_ it already cached _in_memory_ all the children of this file, false otherwise. + * It doesn't mean there are no yet-uncached children in the actual underlying FS: VFS may catch up changes + * in the underlying FS with delay -- it just means VFS _thinks_ it caches all the children, i.e., it _was_ + * all the children in the folder at some moment. + */ + @ApiStatus.Experimental + public boolean allChildrenLoaded() { + return false;//= safe, but not good for performance + } + + /** + * @return children that VFS already loaded in memory. This may be not all the children in the actual underlying FS, + * since: (a) FS changes may be caught to VFS with delay (b) some children could be known to VFS, but not loaded + * in-memory (=stored in VFS persistent storage) + * @see #allChildrenLoaded() + */ public abstract @NotNull @Unmodifiable Collection getCachedChildren(); + /** @return all the children that are cached in VFS, including the ones that are not cached-in-memory now */ @SuppressWarnings("SpellCheckingInspection") public abstract @NotNull @Unmodifiable Iterable iterInDbChildren(); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java index 3ceccc667a5a..ef8363938327 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java @@ -978,6 +978,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { }); } + @Override public boolean allChildrenLoaded() { return directoryData.children.areAllChildrenLoaded(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java index 6a8bcca6195c..8aa02256be8c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java @@ -48,6 +48,11 @@ public final class VirtualFileImpl extends VirtualFileSystemEntry { return null; } + @Override + public boolean allChildrenLoaded() { + return true; + } + @Override public @NotNull Collection getCachedChildren() { return Collections.emptyList(); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java index dc8af77b24b2..c561dd899e6b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java @@ -872,6 +872,11 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { throw new UnsupportedOperationException(); } + @Override + public boolean allChildrenLoaded() { + throw new UnsupportedOperationException(); + } + @Override public @NotNull Collection getCachedChildren() { throw new UnsupportedOperationException();