cleanup, reuse PluginStateChecker

GitOrigin-RevId: 763952172c4edd93d9779af7ca5328093b119b9a
This commit is contained in:
Vladimir Krivosheev
2020-10-20 17:32:04 +00:00
committed by intellij-monorepo-bot
parent f0291f4254
commit 77b89c8bb0
5 changed files with 77 additions and 67 deletions
@@ -16,6 +16,9 @@ import com.intellij.openapi.util.SafeJdomFactory;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.graph.Graph;
import com.intellij.util.graph.GraphAlgorithms;
import com.intellij.util.graph.GraphGenerator;
import org.jdom.JDOMException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -23,12 +26,12 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
@Service
public final class PluginManager {
@@ -228,6 +231,34 @@ public final class PluginManager {
PluginManagerCore.doSetPlugins(list);
}
public boolean processAllBackwardDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
boolean withOptionalDeps,
@NotNull Function<? super IdeaPluginDescriptor, FileVisitResult> consumer) {
Map<PluginId, IdeaPluginDescriptorImpl> idMap = new HashMap<>();
IdeaPluginDescriptorImpl[] allPlugins = (IdeaPluginDescriptorImpl[])PluginManagerCore.getPlugins();
for (IdeaPluginDescriptorImpl plugin : allPlugins) {
idMap.put(plugin.getPluginId(), plugin);
}
CachingSemiGraph<IdeaPluginDescriptorImpl> semiGraph = PluginManagerCore
.createPluginIdGraph(Arrays.asList(allPlugins),
idMap::get,
withOptionalDeps,
PluginManagerCore.findPluginByModuleDependency(PluginManagerCore.ALL_MODULES_MARKER) != null);
Graph<IdeaPluginDescriptorImpl> graph = GraphGenerator.generate(semiGraph);
Set<IdeaPluginDescriptorImpl> dependencies = new LinkedHashSet<>();
GraphAlgorithms.getInstance().collectOutsRecursively(graph, rootDescriptor, dependencies);
for (IdeaPluginDescriptorImpl dependency : dependencies) {
if (dependency == rootDescriptor) {
continue;
}
if (consumer.apply(dependency) == FileVisitResult.TERMINATE) {
return false;
}
}
return true;
}
public @NotNull Disposable createDisposable(@NotNull Class<?> requestor) {
ClassLoader classLoader = requestor.getClassLoader();
if (!(classLoader instanceof PluginAwareClassLoader)) {
@@ -29,7 +29,9 @@ import com.intellij.util.*;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.execution.ParametersListUtil;
import com.intellij.util.graph.*;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.GraphGenerator;
import com.intellij.util.graph.InboundSemiGraph;
import com.intellij.util.lang.UrlClassLoader;
import org.jetbrains.annotations.*;
@@ -577,10 +579,10 @@ public final class PluginManagerCore {
return null;
}
private static @NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> createPluginIdGraph(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
@NotNull Function<? super PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap,
boolean withOptional,
boolean hasAllModules) {
static @NotNull CachingSemiGraph<IdeaPluginDescriptorImpl> createPluginIdGraph(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
@NotNull Function<? super PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap,
boolean withOptional,
boolean hasAllModules) {
Supplier<IdeaPluginDescriptorImpl> javaDep = () -> idToDescriptorMap.apply(JAVA_MODULE_ID);
Set<IdeaPluginDescriptorImpl> uniqueCheck = new HashSet<>();
Map<IdeaPluginDescriptorImpl, List<IdeaPluginDescriptorImpl>> in = new HashMap<>(descriptors.size());
@@ -1393,11 +1395,9 @@ public final class PluginManagerCore {
}
public static @Nullable IdeaPluginDescriptor findPluginByModuleDependency(@NotNull PluginId id) {
for (IdeaPluginDescriptor descriptor : getPlugins()) {
if (descriptor instanceof IdeaPluginDescriptorImpl) {
if (((IdeaPluginDescriptorImpl)descriptor).getModules().contains(id)) {
return descriptor;
}
for (IdeaPluginDescriptorImpl descriptor : ourPlugins) {
if (descriptor.getModules().contains(id)) {
return descriptor;
}
}
return null;
@@ -1482,23 +1482,6 @@ public final class PluginManagerCore {
return true;
}
public static boolean processAllBackwardDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
boolean withOptionalDeps,
@NotNull Function<? super IdeaPluginDescriptor, FileVisitResult> consumer) {
CachingSemiGraph<IdeaPluginDescriptorImpl> semiGraph = createPluginIdGraph(Arrays.asList(ourPlugins),
(id) -> (IdeaPluginDescriptorImpl)getPlugin(id),
withOptionalDeps,
findPluginByModuleDependency(ALL_MODULES_MARKER) != null);
Graph<IdeaPluginDescriptorImpl> graph = GraphGenerator.generate(semiGraph);
Set<IdeaPluginDescriptorImpl> dependencies = new LinkedHashSet<>();
GraphAlgorithms.getInstance().collectOutsRecursively(graph, rootDescriptor, dependencies);
for (IdeaPluginDescriptorImpl dependency : dependencies) {
if (dependency == rootDescriptor) continue;
if (consumer.apply(dependency) == FileVisitResult.TERMINATE) return false;
}
return true;
}
private static @NotNull List<IdeaPluginDescriptorImpl> getOnlyEnabledPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull[] sortedAll) {
List<IdeaPluginDescriptorImpl> enabledPlugins = new ArrayList<>(sortedAll.length);
for (IdeaPluginDescriptorImpl descriptor : sortedAll) {
@@ -1,4 +1,4 @@
// Copyright 2000-2017 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.
// Copyright 2000-2020 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.util.graph.impl;
import com.intellij.openapi.progress.ProgressIndicator;
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class GraphAlgorithmsImpl extends GraphAlgorithms {
public final class GraphAlgorithmsImpl extends GraphAlgorithms {
@Override
public <Node> List<Node> findShortestPath(@NotNull Graph<Node> graph, @NotNull Node start, @NotNull Node finish) {
return findShortestPath((InboundSemiGraph<Node>)graph, start, finish);
@@ -183,8 +183,9 @@ object DynamicPlugins {
return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload"
}
val pluginStateChecker = PluginStateChecker()
if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) {
if (loadedPluginDescriptor != null && isPluginOrModuleLoaded(loadedPluginDescriptor.pluginId) && !descriptor.isUseIdeaClassLoader) {
if (loadedPluginDescriptor != null && pluginStateChecker.isPluginOrModuleLoaded(loadedPluginDescriptor.pluginId) && !descriptor.isUseIdeaClassLoader) {
val pluginClassLoader = loadedPluginDescriptor.pluginClassLoader
if (pluginClassLoader !is PluginClassLoader && !app.isUnitTestMode) {
val loader = baseDescriptor ?: descriptor
@@ -203,7 +204,7 @@ object DynamicPlugins {
ActionManagerImpl.checkUnloadActions(pluginId, descriptor)?.let { return it }
descriptor.pluginDependencies?.forEach { dependency ->
if (isPluginOrModuleLoaded(dependency.id)) {
if (pluginStateChecker.isPluginOrModuleLoaded(dependency.id)) {
val message = checkCanUnloadWithoutRestart(dependency.subDescriptor ?: return@forEach, baseDescriptor ?: descriptor, null, context)
if (message != null) {
return "$message in optional dependency on ${dependency.id}"
@@ -289,7 +290,7 @@ object DynamicPlugins {
val newPluginDescriptor = dependency.configFile?.let { loader.value.load(contextDescriptor, it) } ?: return true
// todo classloader per partial descriptor
newPluginDescriptor.setClassLoader(contextDescriptor.pluginClassLoader)
newPluginDescriptor.classLoader = contextDescriptor.pluginClassLoader
if (dependency.id == dependencyPluginId) {
dependency.subDescriptor = newPluginDescriptor
@@ -309,7 +310,7 @@ object DynamicPlugins {
private fun processImplementationDetailDependenciesOnPlugin(pluginDescriptor: IdeaPluginDescriptorImpl,
processor: (loadedDescriptor: IdeaPluginDescriptorImpl, fullDescriptor: IdeaPluginDescriptorImpl) -> Boolean) {
PluginManagerCore.processAllBackwardDependencies(pluginDescriptor, false) { loadedDescriptor ->
PluginManager.getInstance().processAllBackwardDependencies(pluginDescriptor, false) { loadedDescriptor ->
if (loadedDescriptor.isImplementationDetail) {
val fullDescriptor = PluginDescriptorLoader.loadFullDescriptor(loadedDescriptor as IdeaPluginDescriptorImpl)
if (processor(loadedDescriptor, fullDescriptor)) FileVisitResult.CONTINUE else FileVisitResult.TERMINATE
@@ -485,7 +486,7 @@ object DynamicPlugins {
Language.unregisterLanguages(loadedPluginDescriptor.pluginClassLoader)
}
unloadDependencyDescriptors(pluginDescriptor)
unloadDependencyDescriptors(pluginDescriptor, PluginStateChecker())
unloadPluginDescriptorNotRecursively(pluginDescriptor)
for (project in ProjectUtil.getOpenProjects()) {
@@ -566,17 +567,18 @@ object DynamicPlugins {
val eventId = if (classLoaderUnloaded) "unload.success" else "unload.fail"
val fuData = FeatureUsageData().addPluginInfo(getPluginInfoByDescriptor(loadedPluginDescriptor))
@Suppress("DEPRECATION")
FUCounterUsageLogger.getInstance().logEvent("plugins.dynamic", eventId, fuData)
}
return classLoaderUnloaded
}
private fun unloadDependencyDescriptors(pluginDescriptor: IdeaPluginDescriptorImpl) {
private fun unloadDependencyDescriptors(pluginDescriptor: IdeaPluginDescriptorImpl, pluginStateChecker: PluginStateChecker) {
for (dependency in (pluginDescriptor.pluginDependencies ?: return)) {
if (isPluginOrModuleLoaded(dependency.id)) {
if (pluginStateChecker.isPluginOrModuleLoaded(dependency.id)) {
val subDescriptor = dependency.subDescriptor ?: continue
unloadPluginDescriptorNotRecursively(subDescriptor)
unloadDependencyDescriptors(subDescriptor)
unloadDependencyDescriptors(subDescriptor, pluginStateChecker)
}
}
}
@@ -750,6 +752,7 @@ object DynamicPlugins {
}
val fuData = FeatureUsageData().addPluginInfo(getPluginInfoByDescriptor(pluginDescriptor))
@Suppress("DEPRECATION")
FUCounterUsageLogger.getInstance().logEvent("plugins.dynamic", "load", fuData)
LOG.info("Plugin ${pluginDescriptor.pluginId} loaded without restart in ${System.currentTimeMillis() - loadStartTime} ms")
}
@@ -827,22 +830,14 @@ object DynamicPlugins {
}
}
private fun isPluginOrModuleLoaded(pluginId: PluginId?): Boolean {
if (pluginId != null && PluginManagerCore.isModuleDependency(pluginId)) {
return PluginManagerCore.findPluginByModuleDependency(pluginId) != null
}
return PluginManagerCore.getLoadedPlugins(null).any { it.pluginId == pluginId }
}
@JvmStatic
fun onPluginUnload(parentDisposable: Disposable, callback: Runnable) {
ApplicationManager.getApplication().messageBus.connect(parentDisposable).subscribe(DynamicPluginListener.TOPIC,
object : DynamicPluginListener {
override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor,
isUpdate: Boolean) {
callback.run()
}
})
ApplicationManager.getApplication().messageBus.connect(parentDisposable)
.subscribe(DynamicPluginListener.TOPIC, object : DynamicPluginListener {
override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) {
callback.run()
}
})
}
private fun clearTemporaryLostComponent() {
@@ -892,7 +887,8 @@ object DynamicPlugins {
if (Registry.`is`("ide.plugins.analyze.snapshot")) {
val analysisResult = analyzeSnapshot(snapshotPath, pluginId)
if (analysisResult.isEmpty()) {
@Suppress("ReplaceSizeZeroCheckWithIsEmpty")
if (analysisResult.length == 0) {
LOG.info("Successfully unloaded plugin $pluginId (no strong references to classloader in .hprof file)")
classloadersFromUnloadedPlugins.remove(pluginId)
return true
@@ -939,11 +935,9 @@ private class PluginStateChecker {
private fun analyzeSnapshot(hprofPath: String, pluginId: PluginId): String {
FileChannel.open(Paths.get(hprofPath), StandardOpenOption.READ).use { channel ->
val analysis = HProfAnalysis(
channel,
SystemTempFilenameSupplier()
) { analysisContext, progressIndicator -> AnalyzeClassloaderReferencesGraph(analysisContext, pluginId.idString).analyze(
progressIndicator) }
val analysis = HProfAnalysis(channel, SystemTempFilenameSupplier()) { analysisContext, progressIndicator ->
AnalyzeClassloaderReferencesGraph(analysisContext, pluginId.idString).analyze(progressIndicator)
}
analysis.onlyStrongReferences = true
analysis.includeClassesAsRoots = false
analysis.setIncludeMetaInfo(false)
@@ -1,17 +1,19 @@
package com.jetbrains.packagesearch.intellij.plugin
import com.intellij.ide.plugins.PluginManager
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.NlsSafe
const val PACKAGE_SEARCH_NOTIFICATION_GROUP_ID = "PACKAGESEARCH.NOTIFICATION"
class PluginEnvironment {
val pluginVersion: @NlsSafe String
get() {
return PluginManagerCore.getPlugin(PluginId.getId("com.jetbrains.packagesearch.intellij-plugin"))?.version
?: PackageSearchBundle.message("packagesearch.version.undefined")
}
val pluginVersion
get() = PluginManager.getPlugin(PluginId.getId("com.jetbrains.packagesearch.intellij-plugin"))?.version
?: PackageSearchBundle.message("packagesearch.version.undefined")
val ideVersion
get() = ApplicationInfo.getInstance().strictVersion!!
val ideVersion
get() = ApplicationInfo.getInstance().strictVersion
}