mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
performance optimization: when local file "x.jar" changes, generate all necessary JarFileSystem-level vfs events synchronously, right in the PersistentFsImpl.processEvent(), as opposed to let refresh do it a lot later
GitOrigin-RevId: cd7d12b63a1b3be7a99cee1d4e0d38e9f59aab61
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1b83cd8259
commit
1aef83f4e2
@@ -167,7 +167,7 @@ public class VfsImplUtil {
|
||||
* <code>
|
||||
* FileDocumentManager.getInstance().saveDocument(document);
|
||||
* runExternalToolToChangeFile(virtualFile.getPath()) // changes file externally in milliseconds, probably without changing file's length
|
||||
* VfsUtil.markDirtyAndRefresh(true, true, true, virtualFile); // might be replace with {@link #forceSyncRefresh(VirtualFile)}
|
||||
* VfsUtil.markDirtyAndRefresh(true, true, true, virtualFile); // might be replaced with {@link #forceSyncRefresh(VirtualFile)}
|
||||
* </code>
|
||||
*/
|
||||
public static void forceSyncRefresh(@NotNull VirtualFile file) {
|
||||
@@ -236,20 +236,9 @@ public class VfsImplUtil {
|
||||
for (VFileEvent event : events) {
|
||||
if (!(event.getFileSystem() instanceof LocalFileSystem)) continue;
|
||||
|
||||
if (event instanceof VFileCreateEvent) continue; // new files don't affect existing handlers (and getFile() is costly)
|
||||
|
||||
if (event instanceof VFilePropertyChangeEvent &&
|
||||
!VirtualFile.PROP_NAME.equals(((VFilePropertyChangeEvent)event).getPropertyName())) {
|
||||
continue;
|
||||
}
|
||||
if (!(event instanceof VFileContentChangeEvent)) continue;
|
||||
|
||||
String path = event.getPath();
|
||||
if (event instanceof VFilePropertyChangeEvent) {
|
||||
path = ((VFilePropertyChangeEvent)event).getOldPath();
|
||||
}
|
||||
else if (event instanceof VFileMoveEvent) {
|
||||
path = ((VFileMoveEvent)event).getOldPath();
|
||||
}
|
||||
|
||||
VirtualFile file = event.getFile();
|
||||
if (file == null || !file.isDirectory()) {
|
||||
@@ -330,4 +319,63 @@ public class VfsImplUtil {
|
||||
if (state == null) throw new IllegalArgumentException(localPath + " not in " + ourHandlerCache.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether {@code event} (in LocalFileSystem) affects some jars and if so, generate appropriate additional JarFileSystem-events and corresponding after-event-actions.
|
||||
* For example, "delete/change/move '/tmp/x.jar'" event should generate "delete jar:///tmp/x.jar!/" events.
|
||||
*/
|
||||
@NotNull
|
||||
public static List<VFileDeleteEvent> getJarInvalidationEvents(@NotNull VFileEvent event, @NotNull List<? super Runnable> outApplyActions) {
|
||||
if (!(event instanceof VFileDeleteEvent ||
|
||||
event instanceof VFileMoveEvent ||
|
||||
event instanceof VFilePropertyChangeEvent && VirtualFile.PROP_NAME.equals(((VFilePropertyChangeEvent)event).getPropertyName()))) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String path;
|
||||
if (event instanceof VFilePropertyChangeEvent) {
|
||||
path = ((VFilePropertyChangeEvent)event).getOldPath();
|
||||
}
|
||||
else if (event instanceof VFileMoveEvent) {
|
||||
path = ((VFileMoveEvent)event).getOldPath();
|
||||
}
|
||||
else {
|
||||
path = event.getPath();
|
||||
}
|
||||
|
||||
VirtualFile file = event.getFile();
|
||||
if (file == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Collection<String> jarPaths = ourDominatorsMap.get(path);
|
||||
if (jarPaths == null) {
|
||||
jarPaths = Collections.singletonList(path);
|
||||
}
|
||||
List<VFileDeleteEvent> events = new ArrayList<>(jarPaths.size());
|
||||
for (String jarPath : jarPaths) {
|
||||
Pair<ArchiveFileSystem, ArchiveHandler> handlerPair = ourHandlerCache.get(jarPath);
|
||||
if (handlerPair == null) {
|
||||
continue;
|
||||
}
|
||||
ArchiveFileSystem fileSystem = handlerPair.first;
|
||||
NewVirtualFile root = ManagingFS.getInstance().findRoot(fileSystem.composeRootPath(jarPath), fileSystem);
|
||||
if (root != null) {
|
||||
VFileDeleteEvent jarDeleteEvent = new VFileDeleteEvent(event.getRequestor(), root, event.isFromRefresh());
|
||||
Runnable runnable = () -> {
|
||||
Pair<ArchiveFileSystem, ArchiveHandler> pair = ourHandlerCache.remove(jarPath);
|
||||
if (pair != null) {
|
||||
pair.second.dispose();
|
||||
forEachDirectoryComponent(jarPath, containingDirectoryPath -> {
|
||||
Set<String> handlers = ourDominatorsMap.get(containingDirectoryPath);
|
||||
if (handlers != null && handlers.remove(jarPath) && handlers.isEmpty()) {
|
||||
ourDominatorsMap.remove(containingDirectoryPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
events.add(jarDeleteEvent);
|
||||
outApplyActions.add(runnable);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
}
|
||||
+47
-15
@@ -722,14 +722,31 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
|
||||
|
||||
private void processEvent(@NotNull VFileEvent event) {
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed();
|
||||
// optimisation: skip all groupings
|
||||
if (event.isValid()) {
|
||||
List<VFileEvent> events = Collections.singletonList(event);
|
||||
getPublisher().before(events);
|
||||
if (!event.isValid()) {
|
||||
return;
|
||||
}
|
||||
List<VFileEvent> outValidatedEvents = new ArrayList<>();
|
||||
outValidatedEvents.add(event);
|
||||
List<Runnable> outApplyActions = new ArrayList<>();
|
||||
List<VFileDeleteEvent> jarDeleteEvents = VfsImplUtil.getJarInvalidationEvents(event, outApplyActions);
|
||||
BulkFileListener publisher = getPublisher();
|
||||
if (jarDeleteEvents.isEmpty() && outApplyActions.isEmpty()) {
|
||||
// optimisation: skip all groupings
|
||||
publisher.before(outValidatedEvents);
|
||||
|
||||
applyEvent(event);
|
||||
|
||||
getPublisher().after(events);
|
||||
publisher.after(outValidatedEvents);
|
||||
}
|
||||
else {
|
||||
outApplyActions.add(() -> applyEvent(event));
|
||||
// there are a number of additional jar events generated
|
||||
for (VFileDeleteEvent jarDeleteEvent : jarDeleteEvents) {
|
||||
outApplyActions.add(() -> applyEvent(jarDeleteEvent));
|
||||
outValidatedEvents.add(jarDeleteEvent);
|
||||
}
|
||||
|
||||
applyMultipleEvents(publisher, outApplyActions, outValidatedEvents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,6 +891,15 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
|
||||
groupDeletions(events, startIndex, endIndex, outValidatedEvents, outApplyActions, toIgnore);
|
||||
groupOthers(events, startIndex, endIndex, outValidatedEvents, outApplyActions);
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
VFileEvent event = events.get(i);
|
||||
List<VFileDeleteEvent> jarDeleteEvents = VfsImplUtil.getJarInvalidationEvents(event, outApplyActions);
|
||||
for (VFileDeleteEvent jarDeleteEvent : jarDeleteEvents) {
|
||||
outApplyActions.add((Runnable)() -> applyEvent(jarDeleteEvent));
|
||||
outValidatedEvents.add(jarDeleteEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return endIndex;
|
||||
}
|
||||
|
||||
@@ -989,20 +1015,26 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
|
||||
startIndex = groupAndValidate(events, startIndex, applyActions, validated, files, middleDirs);
|
||||
|
||||
if (!validated.isEmpty()) {
|
||||
PingProgress.interactWithEdtProgress();
|
||||
// do defensive copy to cope with ill-written listeners that save passed list for later processing
|
||||
List<VFileEvent> toSend = ContainerUtil.immutableList(validated.toArray(new VFileEvent[0]));
|
||||
publisher.before(toSend);
|
||||
|
||||
PingProgress.interactWithEdtProgress();
|
||||
applyActions.forEach(Runnable::run);
|
||||
|
||||
PingProgress.interactWithEdtProgress();
|
||||
publisher.after(toSend);
|
||||
applyMultipleEvents(publisher, applyActions, validated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyMultipleEvents(@NotNull BulkFileListener publisher,
|
||||
@NotNull List<? extends Runnable> applyActions,
|
||||
@NotNull List<? extends VFileEvent> applyEvents) {
|
||||
PingProgress.interactWithEdtProgress();
|
||||
// do defensive copy to cope with ill-written listeners that save passed list for later processing
|
||||
List<VFileEvent> toSend = ContainerUtil.immutableList(applyEvents.toArray(new VFileEvent[0]));
|
||||
publisher.before(toSend);
|
||||
|
||||
PingProgress.interactWithEdtProgress();
|
||||
applyActions.forEach(Runnable::run);
|
||||
|
||||
PingProgress.interactWithEdtProgress();
|
||||
publisher.after(toSend);
|
||||
}
|
||||
|
||||
// remove children from specified directories using VirtualDirectoryImpl.removeChildren() optimised for bulk removals
|
||||
private void applyDeletions(@NotNull MultiMap<VirtualDirectoryImpl, VFileDeleteEvent> deletions) {
|
||||
for (Map.Entry<VirtualDirectoryImpl, Collection<VFileDeleteEvent>> entry : deletions.entrySet()) {
|
||||
|
||||
+45
-23
@@ -18,9 +18,11 @@ import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import com.intellij.testFramework.VfsTestUtil;
|
||||
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
|
||||
import com.intellij.testFramework.rules.TempDirectory;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
@@ -39,8 +41,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static com.intellij.openapi.util.io.IoTestUtil.assertTimestampsEqual;
|
||||
import static com.intellij.testFramework.PlatformTestUtil.assertPathsEqual;
|
||||
@@ -225,22 +225,20 @@ public class JarFileSystemTest extends BareTestFixtureTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidZip() throws IOException {
|
||||
File testZip = tempDir.newFile("test.zip");
|
||||
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(testZip))) {
|
||||
writeEntry(zip, "a");
|
||||
writeEntry(zip, "a/b");
|
||||
writeEntry(zip, "a/b/c.txt");
|
||||
writeEntry(zip, "x\\y\\z.txt");
|
||||
writeEntry(zip, "/x/f.txt");
|
||||
writeEntry(zip, "d1/aB");
|
||||
writeEntry(zip, "d1/ab");
|
||||
writeEntry(zip, "D2/f1");
|
||||
writeEntry(zip, "d2/f2");
|
||||
}
|
||||
public void testInvalidZip() {
|
||||
VirtualFile vf = createJar(
|
||||
"a"
|
||||
, "a/b"
|
||||
, "a/b/c.txt"
|
||||
, "x\\y\\z.txt"
|
||||
, "/x/f.txt"
|
||||
, "d1/aB"
|
||||
, "d1/ab"
|
||||
, "D2/f1"
|
||||
, "d2/f2");
|
||||
|
||||
String rootPath = FileUtil.toSystemIndependentName(testZip.getPath()) + JarFileSystem.JAR_SEPARATOR;
|
||||
VirtualFile jarRoot = JarFileSystem.getInstance().findFileByPath(rootPath);
|
||||
String rootPath = vf.getPath() + JarFileSystem.JAR_SEPARATOR;
|
||||
VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(vf);
|
||||
assertNotNull(jarRoot);
|
||||
List<String> entries = new ArrayList<>();
|
||||
VfsUtilCore.visitChildrenRecursively(jarRoot, new VirtualFileVisitor<Object>() {
|
||||
@@ -257,12 +255,6 @@ public class JarFileSystemTest extends BareTestFixtureTestCase {
|
||||
"a/", "a/b/", "a/b/c.txt", "x/", "x/y/", "x/f.txt", "x/y/z.txt", "d1/", "d1/aB", "d1/ab", "D2/", "D2/f1", "d2/", "d2/f2");
|
||||
}
|
||||
|
||||
private static void writeEntry(ZipOutputStream zip, String name) throws IOException {
|
||||
ZipEntry entry = new ZipEntry(name);
|
||||
zip.putNextEntry(entry);
|
||||
zip.closeEntry();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJarRootForLocalFile() {
|
||||
String jarPath = PathManager.getJarPathForClass(Test.class);
|
||||
@@ -391,4 +383,34 @@ public class JarFileSystemTest extends BareTestFixtureTestCase {
|
||||
assertNotNull(jarRoot);
|
||||
assertNotNull(jarRoot.findFileByRelativePath(JarFile.MANIFEST_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJarFileMustInvalidateOnDeleteLocalEntryFile() {
|
||||
VirtualFile vf = createJar("a", "a/b");
|
||||
|
||||
VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(vf);
|
||||
assertNotNull(jarRoot);
|
||||
VirtualFile a = jarRoot.findChild("a");
|
||||
assertNotNull(a);
|
||||
assertTrue(a.isValid());
|
||||
assertTrue(jarRoot.isValid());
|
||||
|
||||
VirtualFile local = JarFileSystem.getInstance().getLocalVirtualFileFor(jarRoot);
|
||||
assertEquals(LocalFileSystem.getInstance(), local.getFileSystem());
|
||||
JarFileSystemImpl.cleanupForNextTest(); // WTF, won't let delete jar otherwise
|
||||
|
||||
MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(getTestRootDisposable());
|
||||
connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
|
||||
@Override
|
||||
public void after(@NotNull List<? extends VFileEvent> events) {
|
||||
// jars must be invalidated immediately after deleting the local root
|
||||
assertFalse(a.isValid());
|
||||
assertFalse(jarRoot.isValid());
|
||||
}
|
||||
});
|
||||
VfsTestUtil.deleteFile(local);
|
||||
assertFalse(a.isValid());
|
||||
assertFalse(jarRoot.isValid());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user