IDEA-245031 mark plugin classloaders as being unloaded to ensure that new extension instances will be not created during unload

GitOrigin-RevId: a453671f96a6c90b4fe6a68952f711d38accf500
This commit is contained in:
Vladimir Krivosheev
2020-10-26 18:52:59 +00:00
committed by intellij-monorepo-bot
parent 04e6bed3be
commit b7d1be4eb3
4 changed files with 55 additions and 1 deletions
@@ -52,6 +52,8 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
private final int instanceId;
private volatile int state = ACTIVE;
public PluginClassLoader(@NotNull List<URL> urls,
@NotNull ClassLoader @NotNull [] parents,
@NotNull PluginDescriptor pluginDescriptor,
@@ -90,6 +92,16 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
}
}
@ApiStatus.Internal
public int getState() {
return state;
}
@ApiStatus.Internal
public void setState(int state) {
this.state = state;
}
@Override
public int getInstanceId() {
return instanceId;
@@ -3,12 +3,16 @@ package com.intellij.ide.plugins.cl;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.extensions.PluginId;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.util.Collection;
public interface PluginAwareClassLoader {
int ACTIVE = 1;
int UNLOAD_IN_PROGRESS = 2;
@NotNull PluginDescriptor getPluginDescriptor();
@NotNull PluginId getPluginId();
@@ -22,4 +26,7 @@ public interface PluginAwareClassLoader {
long getLoadedClassCount();
@NotNull Collection<URL> getUrls();
@MagicConstant(intValues = {ACTIVE, UNLOAD_IN_PROGRESS})
int getState();
}
@@ -3,6 +3,7 @@ package com.intellij.openapi.extensions.impl;
import com.intellij.diagnostic.ActivityCategory;
import com.intellij.diagnostic.StartUpMeasurer;
import com.intellij.ide.plugins.cl.PluginAwareClassLoader;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ComponentManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -465,6 +466,9 @@ public abstract class ExtensionPointImpl<@NotNull T> implements ExtensionPoint<T
// (EP->adapter in one thread, adapter->EP in the other thread)
private synchronized @Nullable T processAdapter(@NotNull ExtensionComponentAdapter adapter) {
try {
if (!checkThatClassloaderIsActive(adapter)) {
return null;
}
return adapter.createInstance(componentManager);
}
catch (ExtensionNotApplicableException ignore) {
@@ -488,6 +492,10 @@ public abstract class ExtensionPointImpl<@NotNull T> implements ExtensionPoint<T
@NotNull Class<T> extensionClassForCheck,
@NotNull List<? extends ExtensionComponentAdapter> adapters) {
try {
if (!checkThatClassloaderIsActive(adapter)) {
return null;
}
boolean isNotifyThatAdded = listeners != null && listeners.length != 0 && !adapter.isInstanceCreated() && !isDynamic;
// do not call CHECK_CANCELED here in loop because it is called by createInstance()
T extension = adapter.createInstance(componentManager);
@@ -526,6 +534,16 @@ public abstract class ExtensionPointImpl<@NotNull T> implements ExtensionPoint<T
return null;
}
private static boolean checkThatClassloaderIsActive(@NotNull ExtensionComponentAdapter adapter) {
ClassLoader classLoader = adapter.getPluginDescriptor().getPluginClassLoader();
if (classLoader instanceof PluginAwareClassLoader &&
((PluginAwareClassLoader)classLoader).getState() != PluginAwareClassLoader.ACTIVE) {
LOG.warn(adapter + " not loaded because classloader is being unloaded");
return false;
}
return true;
}
// used in upsource
// remove extensions for which implementation class is not available
@SuppressWarnings("unused")
@@ -15,6 +15,7 @@ import com.intellij.ide.IdeEventQueue
import com.intellij.ide.SaveAndSyncHandler
import com.intellij.ide.actions.RevealFileAction
import com.intellij.ide.impl.ProjectUtil
import com.intellij.ide.plugins.cl.PluginAwareClassLoader
import com.intellij.ide.plugins.cl.PluginClassLoader
import com.intellij.ide.ui.TopHitCache
import com.intellij.ide.ui.UIThemeProvider
@@ -473,10 +474,14 @@ object DynamicPlugins {
app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).beforePluginUnload(pluginDescriptor, options.isUpdate)
IdeEventQueue.getInstance().flushQueue()
// must be after flushQueue (e.g. https://youtrack.jetbrains.com/issue/IDEA-252010)
app.runWriteAction {
// must be after flushQueue (e.g. https://youtrack.jetbrains.com/issue/IDEA-252010)
val forbidGettingServicesToken = app.forbidGettingServices("Plugin $pluginId being unloaded.")
try {
// https://youtrack.jetbrains.com/issue/IDEA-245031
// mark plugin classloaders as being unloaded to ensure that new extension instances will be not created during unload
setClassLoaderState(pluginDescriptor, PluginClassLoader.UNLOAD_IN_PROGRESS)
processLoadedOptionalDependenciesOnPlugin(pluginId) { mainDescriptor, subDescriptor ->
// must be before unloadPluginDescriptorNotRecursively as this method nullize classloader
val classLoader = (subDescriptor ?: mainDescriptor).pluginClassLoader as? PluginClassLoader
@@ -587,6 +592,11 @@ object DynamicPlugins {
FUCounterUsageLogger.getInstance().logEvent("plugins.dynamic", eventId, fuData)
}
}
if (!classLoaderUnloaded) {
setClassLoaderState(pluginDescriptor, PluginAwareClassLoader.ACTIVE)
}
return classLoaderUnloaded
}
@@ -1107,4 +1117,11 @@ private fun unloadClassLoader(pluginDescriptor: IdeaPluginDescriptorImpl, timeou
val watcher = GCWatcher.tracking(pluginDescriptor.classLoader)
ClassLoaderConfigurator.setPluginClassLoaderForMainAndSubPlugins(pluginDescriptor, null)
return watcher.tryCollect(timeoutMs)
}
private fun setClassLoaderState(pluginDescriptor: IdeaPluginDescriptorImpl, state: Int) {
(pluginDescriptor.classLoader as? PluginClassLoader)?.state = state
for (dependency in (pluginDescriptor.pluginDependencies ?: return)) {
dependency.subDescriptor?.let { setClassLoaderState(it, state) }
}
}