From 4bba72935ca98f69e208152790491e2f91da4f4a Mon Sep 17 00:00:00 2001 From: Sergey Ignatov Date: Wed, 4 Sep 2019 11:08:58 +0300 Subject: [PATCH] async collect watch roots if we have a project with lots of roots, that may speedup the project loading for 500ms GitOrigin-RevId: 8173102a892525b76cbfb02727e01ba02c99930c --- .../impl/ProjectRootManagerComponent.java | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java index 76b4d1047ee1..524238ad7dc3 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java @@ -3,8 +3,7 @@ package com.intellij.openapi.roots.impl; import com.intellij.ProjectTopics; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationListener; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.*; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.components.impl.stores.BatchUpdateListener; import com.intellij.openapi.diagnostic.Logger; @@ -23,6 +22,7 @@ import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.WatchedRootsProvider; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.impl.VirtualFilePointerContainerImpl; @@ -32,6 +32,9 @@ import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.project.ProjectKt; +import com.intellij.ui.GuiUtils; +import com.intellij.util.ConcurrencyUtil; +import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.FileBasedIndex; import com.intellij.util.indexing.FileBasedIndexImpl; @@ -39,6 +42,7 @@ import com.intellij.util.indexing.FileBasedIndexProjectHandler; import com.intellij.util.indexing.UnindexedFilesUpdater; import com.intellij.util.messages.MessageBusConnection; import gnu.trove.THashSet; +import org.jetbrains.annotations.CalledInAwt; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -46,6 +50,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; /** * ProjectRootManager extended with ability to watch events. @@ -55,6 +62,9 @@ public class ProjectRootManagerComponent extends ProjectRootManagerImpl implemen private static final boolean LOG_CACHES_UPDATE = ApplicationManager.getApplication().isInternal() && !ApplicationManager.getApplication().isUnitTestMode(); + private final ExecutorService myExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("Project Root Manager", 1); + private Future myCollectWatchRootsFuture = CompletableFuture.completedFuture(null); + private boolean myPointerChangesDetected; private int myInsideRefresh; @NotNull @@ -114,13 +124,26 @@ public class ProjectRootManagerComponent extends ProjectRootManagerImpl implemen LocalFileSystem.getInstance().removeWatchedRoots(myRootsToWatch); } + @CalledInAwt private void addRootsToWatch() { - if (!myProject.isDefault()) { - Set recursivePaths = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY); - Set flatPaths = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY); - collectWatchRoots(recursivePaths, flatPaths); - myRootsToWatch = LocalFileSystem.getInstance().replaceWatchedRoots(myRootsToWatch, recursivePaths, flatPaths); - } + if (myProject.isDefault()) return; + + Disposable prev = myRootPointersDisposable; + Disposable next = Disposer.newDisposable(); + + Application application = ApplicationManager.getApplication(); + ExecutorService service = application.isUnitTestMode() ? ConcurrencyUtil.newSameThreadExecutorService() : myExecutor; + + myCollectWatchRootsFuture.cancel(false); + myCollectWatchRootsFuture = service.submit(() -> { + if (myProject.isDisposed()) return; + Pair, Set> pair = ReadAction.compute(() -> collectWatchRoots(next)); + GuiUtils.invokeLaterIfNeeded(() -> { + myRootPointersDisposable = next; + Disposer.dispose(prev); // dispose after the re-creating container to keep VFPs from disposing and re-creating back + myRootsToWatch = LocalFileSystem.getInstance().replaceWatchedRoots(myRootsToWatch, pair.first, pair.second); + }, ModalityState.any()); + }); } private void beforeRootsChange(boolean fileTypes) { @@ -175,8 +198,9 @@ public class ProjectRootManagerComponent extends ProjectRootManagerImpl implemen addRootsToWatch(); } - private void collectWatchRoots(@NotNull Set recursivePaths, @NotNull Set flatPaths) { - ApplicationManager.getApplication().assertIsDispatchThread(); + private Pair, Set> collectWatchRoots(@NotNull Disposable disposable) { + Set recursivePaths = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY); + Set flatPaths = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY); String projectFilePath = myProject.getProjectFilePath(); if (projectFilePath != null && !Project.DIRECTORY_STORE_FOLDER.equals(new File(projectFilePath).getParentFile().getName())) { @@ -201,31 +225,30 @@ public class ProjectRootManagerComponent extends ProjectRootManagerImpl implemen } } - Disposable oldDisposable = myRootPointersDisposable; - myRootPointersDisposable = Disposer.newDisposable(); + List recursiveUrls = ContainerUtil.map(recursivePaths, VfsUtilCore::pathToUrl); Set excludedUrls = new THashSet<>(); // changes in files provided by this method should be watched manually because no-one's bothered to set up correct pointers for them - for (DirectoryIndexExcludePolicy excludePolicy : DirectoryIndexExcludePolicy.EP_NAME.getExtensions(getProject())) { + for (DirectoryIndexExcludePolicy excludePolicy : DirectoryIndexExcludePolicy.EP_NAME.getExtensions(myProject)) { Collections.addAll(excludedUrls, excludePolicy.getExcludeUrlsForProject()); } // avoid creating empty unnecessary container if (!recursiveUrls.isEmpty() || !flatPaths.isEmpty() || !excludedUrls.isEmpty()) { - Disposer.register(this, myRootPointersDisposable); + Disposer.register(this, disposable); // creating a container with these URLs with the sole purpose to get events to getRootsValidityChangedListener() when these roots change VirtualFilePointerContainer container = - VirtualFilePointerManager.getInstance().createContainer(myRootPointersDisposable, getRootsValidityChangedListener()); + VirtualFilePointerManager.getInstance().createContainer(disposable, getRootsValidityChangedListener()); ((VirtualFilePointerContainerImpl)container).addAllJarDirectories(recursiveUrls, true); flatPaths.forEach(path -> container.add(VfsUtilCore.pathToUrl(path))); ((VirtualFilePointerContainerImpl)container).addAll(excludedUrls); } - Disposer.dispose(oldDisposable); // dispose after the re-creating container to keep VFPs from disposing and re-creating back - // module roots already fire validity change events, see usages of ProjectRootManagerComponent.getRootsValidityChangedListener collectModuleWatchRoots(recursivePaths, flatPaths); + + return Pair.create(recursivePaths, flatPaths); } private void collectModuleWatchRoots(@NotNull Set recursivePaths, @NotNull Set flatPaths) { @@ -305,6 +328,8 @@ public class ProjectRootManagerComponent extends ProjectRootManagerImpl implemen @Override public void dispose() { + myCollectWatchRootsFuture.cancel(false); + myExecutor.shutdownNow(); assertListenersAreDisposed(); }