move empty directory detection from VirtualFilePointerManagerImpl to RefreshWorker to be less freezy (IDEA-CR-42309)

This commit is contained in:
Alexey Kudravtsev
2019-01-23 14:34:26 +03:00
parent 8ce36d2809
commit 4c13f677c9
12 changed files with 57 additions and 32 deletions
@@ -952,7 +952,7 @@ class IndexTest extends JavaCodeInsightFixtureTestCase {
eventList.add(new VFilePropertyChangeEvent(null, file, VirtualFile.PROP_NAME, filename, filename2, true))
eventList.add(new VFilePropertyChangeEvent(null, file, VirtualFile.PROP_NAME, filename2, filename, true))
eventList.add(new VFileDeleteEvent(null, file, true))
eventList.add(new VFileCreateEvent(null, file.parent, filename, false, true))
eventList.add(new VFileCreateEvent(null, file.parent, filename, false, true, false))
}
IndexedFilesListener indexedFilesListener = ((FileBasedIndexImpl)FileBasedIndex.instance).changedFilesCollector
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
public class VFileCreateEvent extends VFileEvent {
@NotNull private final VirtualFile myParent;
private final boolean myDirectory;
private final boolean myEmptyDirectory;
@NotNull private final String myChildName;
private final FileAttributes myAttributes;
private VirtualFile myCreatedFile;
@@ -23,12 +24,14 @@ public class VFileCreateEvent extends VFileEvent {
@NotNull VirtualFile parent,
@NotNull String childName,
final boolean isDirectory,
final boolean isFromRefresh) {
final boolean isFromRefresh,
boolean isEmptyDirectory) {
super(requestor, isFromRefresh);
myChildName = childName;
myParent = parent;
myDirectory = isDirectory;
myAttributes = null;
myEmptyDirectory = isEmptyDirectory;
}
public VFileCreateEvent(Object requestor,
@@ -41,6 +44,7 @@ public class VFileCreateEvent extends VFileEvent {
myParent = parent;
myDirectory = attributes.isDirectory();
myAttributes = attributes;
myEmptyDirectory = isEmptyDirectory;
}
@NotNull
@@ -52,6 +56,13 @@ public class VFileCreateEvent extends VFileEvent {
return myDirectory;
}
/**
* @return true if the newly created file is a directory which has no children.
*/
public boolean isEmptyDirectory() {
return isDirectory() && myEmptyDirectory;
}
@NotNull
public VirtualFile getParent() {
return myParent;
@@ -34,11 +34,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
@@ -357,14 +352,6 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp
private List<FilePointerPartNode> myNodesToUpdateUrl = Collections.emptyList();
private List<FilePointerPartNode> myNodesToFire = Collections.emptyList();
private static boolean isEmptyDir(@NotNull String path) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(path))) {
return !stream.iterator().hasNext();
}
catch (IOException e) {
return false;
}
}
@Override
public void before(@NotNull final List<? extends VFileEvent> events) {
ApplicationManager.getApplication().assertIsDispatchThread(); // guarantees no attempts to get read action lock under "this" lock
@@ -384,7 +371,7 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp
final VFileCreateEvent createEvent = (VFileCreateEvent)event;
// when a new empty directory "/a/b" is created, there's no need to fire any deeper pointers like "/a/b/c/d.txt" - they're not created yet
// OTOH when refresh found a new directory "/a/b" which is non-empty, we must fire deeper pointers because they may exist already
boolean fireSubdirectoryPointers = createEvent.isDirectory() && !isEmptyDir(createEvent.getPath());
boolean fireSubdirectoryPointers = createEvent.isDirectory() && !createEvent.isEmptyDirectory();
addRelevantPointers(createEvent.getParent(), true, createEvent.getChildName(), toFireEvents, fireSubdirectoryPointers);
}
else if (event instanceof VFileCopyEvent) {
@@ -34,6 +34,10 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
/**
@@ -247,7 +251,10 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
final FileAttributes attributes = delegate.getAttributes(fake);
if (attributes == null) return null;
final String realName = delegate.getCanonicallyCasedName(fake);
final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, attributes, true);
boolean isDirectory = attributes.isDirectory();
boolean isEmptyDirectory = isDirectory && !hasChildren(Paths.get(fake.getPath()));
final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, attributes, true, isEmptyDirectory);
RefreshQueue.getInstance().processSingleEvent(event);
return findChild(realName);
}
@@ -693,4 +700,17 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
}
}
}
/**
* @return true if {@code path} represents a directory which has children.
*/
public static boolean hasChildren(@NotNull Path path) {
// make sure to not load all children
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
return stream.iterator().hasNext();
}
catch (IOException | SecurityException e) {
return false;
}
}
}
@@ -270,7 +270,8 @@ class LocalFileSystemRefreshWorker {
boolean directory = attrs.isDirectory();
if (child == null) { // new file is created
myHelper.scheduleCreation(myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent(), name, convert(file, attrs));
VirtualFile parent = myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent();
myHelper.scheduleCreation(parent, name, file, convert(file, attrs));
return FileVisitResult.CONTINUE;
}
@@ -297,7 +298,8 @@ class LocalFileSystemRefreshWorker {
oldIsSymlink != isLink ||
oldIsSpecial != isSpecial) { // symlink or directory or special changed
myHelper.scheduleDeletion(child);
myHelper.scheduleCreation(myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent(), child.getName(), convert(file, attrs));
VirtualFile parent = myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent();
myHelper.scheduleCreation(parent, child.getName(), file, convert(file, attrs));
// ignore everything else
child.markClean();
return FileVisitResult.CONTINUE;
@@ -415,7 +415,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
@Override
public VirtualFile createChildDirectory(Object requestor, @NotNull VirtualFile parent, @NotNull String dir) throws IOException {
getDelegate(parent).createChildDirectory(requestor, parent, dir);
processEvent(new VFileCreateEvent(requestor, parent, dir, true, false));
processEvent(new VFileCreateEvent(requestor, parent, dir, true, false, true));
final VirtualFile child = parent.findChild(dir);
if (child == null) {
@@ -428,7 +428,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
@Override
public VirtualFile createChildFile(Object requestor, @NotNull VirtualFile parent, @NotNull String file) throws IOException {
getDelegate(parent).createChildFile(requestor, parent, file);
processEvent(new VFileCreateEvent(requestor, parent, file, false, false));
processEvent(new VFileCreateEvent(requestor, parent, file, false, false, false));
final VirtualFile child = parent.findChild(file);
if (child == null) {
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -192,7 +193,7 @@ public class RefreshWorker {
String name = pair.first;
FileAttributes childAttributes = pair.second;
if (childAttributes != null) {
myHelper.scheduleCreation(dir, name, childAttributes);
myHelper.scheduleCreation(dir, name, Paths.get(dir.getPath(), name), childAttributes);
}
else {
if (LOG.isTraceEnabled()) LOG.trace("[+] fs=" + fs + " dir=" + dir + " name=" + name);
@@ -276,7 +277,7 @@ public class RefreshWorker {
String name = pair.first;
FileAttributes childAttributes = pair.second;
if (childAttributes != null) {
myHelper.scheduleCreation(dir, name, childAttributes);
myHelper.scheduleCreation(dir, name, Paths.get(dir.getPath(), name), childAttributes);
}
}
@@ -341,7 +342,7 @@ public class RefreshWorker {
if (currentIsDirectory != upToDateIsDirectory || currentIsSymlink != upToDateIsSymlink || currentIsSpecial != upToDateIsSpecial) {
myHelper.scheduleDeletion(child);
myHelper.scheduleCreation(parent, child.getName(), childAttributes);
myHelper.scheduleCreation(parent, child.getName(), Paths.get(parent.getPath(), child.getName()), childAttributes);
return true;
}
@@ -7,8 +7,10 @@ import com.intellij.openapi.util.io.FileAttributes;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.events.*;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@@ -44,9 +46,11 @@ class VfsEventGenerationHelper {
myEvents.add(new VFileContentChangeEvent(null, file, file.getModificationStamp(), -1, oldTimestamp, newTimestamp, oldLength, newLength, true));
}
void scheduleCreation(@NotNull VirtualFile parent, @NotNull String childName, @NotNull FileAttributes attributes) {
void scheduleCreation(@NotNull VirtualFile parent, @NotNull String childName, @NotNull Path path, @NotNull FileAttributes attributes, boolean isDirectory) {
boolean isEmptyDir = isDirectory && !VirtualDirectoryImpl.hasChildren(path);
if (LOG.isTraceEnabled()) LOG.trace("create parent=" + parent + " name=" + childName + " attr=" + attributes);
myEvents.add(new VFileCreateEvent(null, parent, childName, attributes, true));
myEvents.add(new VFileCreateEvent(null, parent, childName, attributes, true, isEmptyDir));
}
void scheduleDeletion(@NotNull VirtualFile file) {
@@ -64,7 +64,7 @@ public class VfsUtilLightTest extends BareTestFixtureTestCase {
VirtualFile jarRoot = VirtualFileManager.getInstance().findFileByUrl("jar://" + FileUtil.toSystemIndependentName(jarFile.getPath()) + "!/");
assertNotNull(jarRoot);
VFileCreateEvent event = new VFileCreateEvent(this, jarRoot, "x.txt", false, false);
VFileCreateEvent event = new VFileCreateEvent(this, jarRoot, "x.txt", false, false, false);
assertEquals(FileUtil.toSystemIndependentName(jarFile.getPath()) + "!/x.txt", event.getPath());
}
}
@@ -333,7 +333,7 @@ public class VfsUtilPerformanceTest extends BareTestFixtureTestCase {
events.clear();
TempFileSystem fs = TempFileSystem.getInstance();
IntStream.range(0, N)
.mapToObj(i -> new VFileCreateEvent(this, temp, i + ".txt", false, false))
.mapToObj(i -> new VFileCreateEvent(this, temp, i + ".txt", false, false, false))
.peek(event -> {
if (fs.findModelChild(temp, event.getChildName()) == null) {
fs.createChildFile(this, temp, event.getChildName());
@@ -92,7 +92,7 @@ public class VirtualFilePointerRootsTest extends PlatformTestCase {
myVirtualFilePointerManager.shelveAllPointersIn(() -> {
for (int i = 0; i < 100_000; i++) {
myVirtualFilePointerManager.create(VfsUtilCore.pathToUrl("/a/b/c/d/" + i), disposable, listener);
events.add(new VFileCreateEvent(this, temp, "xxx" + i, false, true));
events.add(new VFileCreateEvent(this, temp, "xxx" + i, false, true, false));
}
PlatformTestUtil.startPerformanceTest("vfp update", 7_000, () -> {
for (int i = 0; i < 100; i++) {
@@ -360,7 +360,7 @@ public class PersistentFsTest extends PlatformTestCase {
checkEvents("Before:[VFileCreateEvent->xx.created, VFileDeleteEvent->file.txt]\n" +
"After:[VFileCreateEvent->xx.created, VFileDeleteEvent->file.txt]\n",
new VFileDeleteEvent(this, vFile, false),
new VFileCreateEvent(this, vFile.getParent(), "xx.created", false, false),
new VFileCreateEvent(this, vFile.getParent(), "xx.created", false, false, false),
new VFileDeleteEvent(this, vFile, false));
}
@@ -377,8 +377,8 @@ public class PersistentFsTest extends PlatformTestCase {
"Before:[VFileDeleteEvent->c]\n" +
"After:[VFileDeleteEvent->c]\n",
new VFileDeleteEvent(this, vFile, false),
new VFileCreateEvent(this, vFile.getParent(), "xx.created", false, false),
new VFileCreateEvent(this, vFile.getParent(), "xx.created2", false, false),
new VFileCreateEvent(this, vFile.getParent(), "xx.created", false, false, false),
new VFileCreateEvent(this, vFile.getParent(), "xx.created2", false, false, false),
new VFileDeleteEvent(this, vFile.getParent(), false));
}