diff --git a/java/java-impl/src/com/intellij/openapi/vfs/impl/jrt/JrtFileSystemImpl.java b/java/java-impl/src/com/intellij/openapi/vfs/impl/jrt/JrtFileSystemImpl.java index 893c50539556..499f37619339 100644 --- a/java/java-impl/src/com/intellij/openapi/vfs/impl/jrt/JrtFileSystemImpl.java +++ b/java/java-impl/src/com/intellij/openapi/vfs/impl/jrt/JrtFileSystemImpl.java @@ -5,8 +5,10 @@ import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.projectRoots.JdkUtil; +import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.DiskQueryRelay; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; @@ -21,6 +23,7 @@ import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -30,6 +33,14 @@ public class JrtFileSystemImpl extends JrtFileSystem { Collections.synchronizedMap(new THashMap<>(FileUtil.PATH_HASHING_STRATEGY)); private final AtomicBoolean mySubscribed = new AtomicBoolean(false); + private final DiskQueryRelay myAttrGetter = new DiskQueryRelay<>(super::getAttributes); + + @Nullable + @Override + public FileAttributes getAttributes(@NotNull VirtualFile file) { + return myAttrGetter.accessDiskWithCheckCanceled(file); + } + @NotNull @Override public String getProtocol() { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/DiskQueryRelay.java b/platform/platform-impl/src/com/intellij/openapi/vfs/DiskQueryRelay.java new file mode 100644 index 000000000000..c1acd3a608a0 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/DiskQueryRelay.java @@ -0,0 +1,53 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.vfs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicatorProvider; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.concurrent.Future; +import java.util.function.Function; + +/** + * A utility to run a potentially long function on a pooled thread, wait for it in an interruptible way and reuse that computation if it's + * needed again if it's still running. Function results should be ready for concurrent access, preferably thread-safe. + */ +@ApiStatus.Internal +public class DiskQueryRelay { + private final Function myFunction; + + /** + * We remember the submitted tasks in "myTasks" until they're finished, to avoid creating many-many similar threads + * in case the callee is interrupted by "checkCanceled", restarted, comes again with the same query, is interrupted again, and so on. + */ + private final Map> myTasks = ContainerUtil.newConcurrentMap(); + + public DiskQueryRelay(@NotNull Function function) { + myFunction = function; + } + + public Result accessDiskWithCheckCanceled(@NotNull Param arg) { + if (ProgressIndicatorProvider.getGlobalProgressIndicator() == null) { + return myFunction.apply(arg); + } + + Future future = myTasks.computeIfAbsent(arg, eachArg -> ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + return myFunction.apply(eachArg); + } + finally { + myTasks.remove(eachArg); + } + })); + if (future.isDone()) { + // maybe it was very fast and completed before being put into a map + myTasks.remove(arg, future); + } + return ProgressIndicatorUtils.awaitWithCheckCanceled(future); + } + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java index ddcd80be097f..5615e2dd4f00 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java @@ -4,9 +4,11 @@ package com.intellij.openapi.vfs.impl.jar; import com.intellij.concurrency.ConcurrentCollectionFactory; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.DiskQueryRelay; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.impl.ArchiveHandler; @@ -32,6 +34,14 @@ public class JarFileSystemImpl extends JarFileSystem { myNoCopyJarDir = !runningFromDist ? null : new File(PathManager.getHomePath()); } + private final DiskQueryRelay myAttrGetter = new DiskQueryRelay<>(super::getAttributes); + + @Nullable + @Override + public FileAttributes getAttributes(@NotNull VirtualFile file) { + return myAttrGetter.accessDiskWithCheckCanceled(file); + } + @Override public void setNoCopyJarForPath(@NotNull String pathInJar) { if (myNoCopyJarPaths == null) return; diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java index 88c97fe75267..4832a623a5d6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java @@ -4,8 +4,6 @@ package com.intellij.openapi.vfs.impl.local; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicatorProvider; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.*; import com.intellij.openapi.util.registry.Registry; @@ -28,8 +26,6 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.nio.file.*; import java.util.*; -import java.util.concurrent.Future; -import java.util.function.Function; /** * @author Dmitry Avdeev @@ -130,7 +126,7 @@ public abstract class LocalFileSystemBase extends LocalFileSystem { @NotNull @Override public String[] list(@NotNull VirtualFile file) { - String[] names = accessDiskWithCheckCanceled(ourListTasks, convertToIOFile(file), dir -> dir.list(DirectoryAccessChecker.getFileFilter(dir))); + String[] names = myChildrenGetter.accessDiskWithCheckCanceled(convertToIOFile(file)); return names == null ? ArrayUtil.EMPTY_STRING_ARRAY : names; } @@ -724,33 +720,11 @@ public abstract class LocalFileSystemBase extends LocalFileSystem { if (file.getParent() == null && path.startsWith("//")) { return FAKE_ROOT_ATTRIBUTES; // fake Windows roots } - return accessDiskWithCheckCanceled(ourAttrTasks, FileUtil.toSystemDependentName(path), FileSystemUtil::getAttributes); + return myAttrGetter.accessDiskWithCheckCanceled(FileUtil.toSystemDependentName(path)); } - private static final Map> ourAttrTasks = ContainerUtil.newConcurrentMap(); - private static final Map> ourListTasks = ContainerUtil.newConcurrentMap(); - - private static V accessDiskWithCheckCanceled(Map> store, T arg, Function fun) { - if (ProgressIndicatorProvider.getGlobalProgressIndicator() == null) { - return fun.apply(arg); - } - - // We remember the submitted tasks in "store" map until they're finished, to avoid creating many-many similar threads - // in case the callee is interrupted by "checkCanceled", restarted, comes again with the same query, is interrupted again, and so on. - Future future = store.computeIfAbsent(arg, path -> ApplicationManager.getApplication().executeOnPooledThread(() -> { - try { - return fun.apply(path); - } - finally { - store.remove(path); - } - })); - if (future.isDone()) { - // maybe it was very fast and completed before being put into a map - store.remove(arg, future); - } - return ProgressIndicatorUtils.awaitWithCheckCanceled(future); - } + private final DiskQueryRelay myAttrGetter = new DiskQueryRelay<>(FileSystemUtil::getAttributes); + private final DiskQueryRelay myChildrenGetter = new DiskQueryRelay<>(dir -> dir.list(DirectoryAccessChecker.getFileFilter(dir))); @Override public void refresh(boolean asynchronous) {