make getAttributes on archive file systems non-blocking (EA-211700 freeze)

GitOrigin-RevId: 8b007c94f3c9bd0626498f2cdc24efacbb0359fa
This commit is contained in:
Peter Gromov
2019-12-04 09:08:56 +00:00
committed by intellij-monorepo-bot
parent 053ff7f9b7
commit f21bbb94a3
4 changed files with 78 additions and 30 deletions
@@ -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<VirtualFile, FileAttributes> myAttrGetter = new DiskQueryRelay<>(super::getAttributes);
@Nullable
@Override
public FileAttributes getAttributes(@NotNull VirtualFile file) {
return myAttrGetter.accessDiskWithCheckCanceled(file);
}
@NotNull
@Override
public String getProtocol() {
@@ -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<Param, Result> {
private final Function<Param, Result> 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<Param, Future<Result>> myTasks = ContainerUtil.newConcurrentMap();
public DiskQueryRelay(@NotNull Function<Param, Result> function) {
myFunction = function;
}
public Result accessDiskWithCheckCanceled(@NotNull Param arg) {
if (ProgressIndicatorProvider.getGlobalProgressIndicator() == null) {
return myFunction.apply(arg);
}
Future<Result> 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);
}
}
@@ -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<VirtualFile, FileAttributes> 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;
@@ -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<String, Future<FileAttributes>> ourAttrTasks = ContainerUtil.newConcurrentMap();
private static final Map<File, Future<String[]>> ourListTasks = ContainerUtil.newConcurrentMap();
private static <T, V> V accessDiskWithCheckCanceled(Map<T, Future<V>> store, T arg, Function<T, V> 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<V> 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<String, FileAttributes> myAttrGetter = new DiskQueryRelay<>(FileSystemUtil::getAttributes);
private final DiskQueryRelay<File, String[]> myChildrenGetter = new DiskQueryRelay<>(dir -> dir.list(DirectoryAccessChecker.getFileFilter(dir)));
@Override
public void refresh(boolean asynchronous) {