diff --git a/platform/core-api/api-dump-unreviewed.txt b/platform/core-api/api-dump-unreviewed.txt index 774b658dd00f..beecfd29b69e 100644 --- a/platform/core-api/api-dump-unreviewed.txt +++ b/platform/core-api/api-dump-unreviewed.txt @@ -281,6 +281,10 @@ com.intellij.ide.plugins.DynamicPluginListener - pluginLoaded(com.intellij.ide.plugins.IdeaPluginDescriptor):V - pluginUnloaded(com.intellij.ide.plugins.IdeaPluginDescriptor,Z):V f:com.intellij.ide.plugins.DynamicPluginListener$Companion +com.intellij.ide.plugins.DynamicPluginVetoer +- sf:Companion:com.intellij.ide.plugins.DynamicPluginVetoer$Companion +- a:vetoPluginUnload(com.intellij.ide.plugins.IdeaPluginDescriptor):java.lang.String +f:com.intellij.ide.plugins.DynamicPluginVetoer$Companion @:com.intellij.ide.plugins.DynamicallyLoaded - java.lang.annotation.Annotation com.intellij.ide.plugins.IdeaPluginDependency diff --git a/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginListener.kt b/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginListener.kt index 9c7f7f092a45..e99efe261dbd 100644 --- a/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginListener.kt +++ b/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginListener.kt @@ -4,6 +4,7 @@ package com.intellij.ide.plugins import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.util.messages.Topic +@Deprecated("Use DynamicPluginVetoer instead") class CannotUnloadPluginException(value: String) : ProcessCanceledException(RuntimeException(value)) interface DynamicPluginListener { @@ -28,12 +29,7 @@ interface DynamicPluginListener { fun pluginUnloaded(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) { } - /** - * Checks if the plugin can be dynamically unloaded at this moment. - * Method should throw [CannotUnloadPluginException] if it isn't possible for some reason. - * - * Not dispatched for a content modules (plugin model V2). - */ + @Deprecated("Use DynamicPluginVetoer instead") @Throws(CannotUnloadPluginException::class) fun checkUnloadPlugin(pluginDescriptor: IdeaPluginDescriptor) { } diff --git a/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginVetoer.kt b/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginVetoer.kt new file mode 100644 index 000000000000..db8492e3aa23 --- /dev/null +++ b/platform/core-api/src/com/intellij/ide/plugins/DynamicPluginVetoer.kt @@ -0,0 +1,25 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.plugins + +import com.intellij.openapi.extensions.ExtensionPointName +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls + +interface DynamicPluginVetoer { + /** + * Checks if the plugin can be dynamically unloaded at this moment. + * + * Not dispatched for content modules (plugin model V2). + * + * @return Error message to display + * 'null' to let the plugin be unloaded + */ + fun vetoPluginUnload(pluginDescriptor: IdeaPluginDescriptor): @Nls String? + + companion object { + @JvmField + @ApiStatus.Internal + val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.intellij.ide.dynamicPluginVetoer"); + } +} + diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt index e081d3e1923e..77cdb40e5c80 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt @@ -268,12 +268,10 @@ object DynamicPlugins { return null } - try { - app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).checkUnloadPlugin(module) - } - catch (e: CannotUnloadPluginException) { - return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload" + val vetoMessage = DynamicPluginVetoer.EP_NAME.computeSafeIfAny { + it.vetoPluginUnload(module) } + if (vetoMessage != null) return vetoMessage } if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/LocalizationPluginListener.kt b/platform/platform-impl/src/com/intellij/ide/plugins/LocalizationPluginListener.kt index 7caa72c70b41..5b359f951732 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/LocalizationPluginListener.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/LocalizationPluginListener.kt @@ -5,11 +5,10 @@ import com.intellij.ide.plugins.LocalizationPluginHelper.isActiveLocalizationPlu import com.intellij.ide.ui.LanguageAndRegionUi import com.intellij.l10n.LocalizationStateService import com.intellij.openapi.diagnostic.logger -import java.util.Locale +import java.util.* internal class LocalizationPluginListener : DynamicPluginListener { - - override fun checkUnloadPlugin(pluginDescriptor: IdeaPluginDescriptor) { + override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) { if (isActiveLocalizationPlugin(pluginDescriptor)) { logger().info("[i18n] Language setting was reset to default during unload Localization plugin") diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index a8a68703cc75..b71804e6b3c9 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -504,6 +504,8 @@ + + diff --git a/platform/xdebugger-impl/resources/META-INF/xdebugger.xml b/platform/xdebugger-impl/resources/META-INF/xdebugger.xml index 539e0d404834..5dc1933f2df4 100644 --- a/platform/xdebugger-impl/resources/META-INF/xdebugger.xml +++ b/platform/xdebugger-impl/resources/META-INF/xdebugger.xml @@ -43,6 +43,8 @@ + + diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java index 632a656464c9..ec49ce0fb829 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java @@ -14,8 +14,7 @@ import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.execution.ui.RunContentManager; import com.intellij.execution.ui.RunContentWithExecutorListener; import com.intellij.ide.DataManager; -import com.intellij.ide.plugins.CannotUnloadPluginException; -import com.intellij.ide.plugins.DynamicPluginListener; +import com.intellij.ide.plugins.DynamicPluginVetoer; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.idea.ActionsBundle; import com.intellij.notification.NotificationGroup; @@ -40,6 +39,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManagerListener; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeGlassPaneUtil; @@ -76,8 +76,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.MouseEvent; -import java.util.List; import java.util.*; +import java.util.List; import java.util.concurrent.ExecutorService; import static com.intellij.xdebugger.impl.CoroutineUtilsKt.createMutableStateFlow; @@ -174,19 +174,6 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers } }); - messageBusConnection.subscribe(DynamicPluginListener.TOPIC, new DynamicPluginListener() { - @Override - public void checkUnloadPlugin(@NotNull IdeaPluginDescriptor pluginDescriptor) { - XDebugSession[] sessions = getDebugSessions(); - for (XDebugSession session : sessions) { - XDebugProcess process = session.getDebugProcess(); - if (process.dependsOnPlugin(pluginDescriptor)) { - throw new CannotUnloadPluginException("Plugin is not unload-safe because of the started debug session"); - } - } - } - }); - GutterUiRunToCursorEditorListener listener = new GutterUiRunToCursorEditorListener(); EditorMouseMotionListener bpPromoter = new BreakpointPromoterEditorListener(coroutineScope); EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster(); @@ -611,6 +598,25 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers return documentLine < editor.getDocument().getLineCount() ? documentLine : -1; } + static class XDebuggerPluginVetoer implements DynamicPluginVetoer { + @Override + public @Nls @Nullable String vetoPluginUnload(@NotNull IdeaPluginDescriptor pluginDescriptor) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + XDebuggerManager manager = project.getServiceIfCreated(XDebuggerManager.class); + if (manager == null) continue; + + XDebugSession[] sessions = manager.getDebugSessions(); + for (XDebugSession session : sessions) { + XDebugProcess process = session.getDebugProcess(); + if (process.dependsOnPlugin(pluginDescriptor)) { + return "Plugin is not unload-safe because of the started debug session"; + } + } + } + return null; + } + } + @ApiStatus.Internal public CoroutineScope getCoroutineScope() { return myCoroutineScope; diff --git a/plugins/performanceTesting/core/resources/META-INF/plugin.xml b/plugins/performanceTesting/core/resources/META-INF/plugin.xml index f84cc521996c..ae85e6a79b36 100644 --- a/plugins/performanceTesting/core/resources/META-INF/plugin.xml +++ b/plugins/performanceTesting/core/resources/META-INF/plugin.xml @@ -60,6 +60,8 @@ + + diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/PlaybackRunnerExtended.java b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/PlaybackRunnerExtended.java index 87fed7cc44d3..4529df3f4f05 100644 --- a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/PlaybackRunnerExtended.java +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/PlaybackRunnerExtended.java @@ -1,7 +1,6 @@ package com.jetbrains.performancePlugin; -import com.intellij.ide.plugins.CannotUnloadPluginException; -import com.intellij.ide.plugins.DynamicPluginListener; +import com.intellij.ide.plugins.DynamicPluginVetoer; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.project.Project; @@ -10,31 +9,28 @@ import com.intellij.openapi.ui.playback.PlaybackRunner; import com.intellij.openapi.ui.playback.commands.AbstractCommand; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.registry.Registry; -import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; public final class PlaybackRunnerExtended extends PlaybackRunner { public static final String NOTIFICATION_GROUP = "PerformancePlugin"; private Project myProject; private boolean myStopped; - private final DynamicPluginListener myDynamicPluginListener; public PlaybackRunnerExtended(String script, StatusCallback callback, @NotNull Project project) { super(script, callback, Registry.is("performance.plugin.playback.runner.useDirectActionCall", false), false, true); setProject(project); - myDynamicPluginListener = new DynamicPluginListener() { - @Override - public void checkUnloadPlugin(@NotNull IdeaPluginDescriptor pluginDescriptor) throws CannotUnloadPluginException { - if (PluginId.getId("com.jetbrains.performancePlugin").equals(pluginDescriptor.getPluginId())) { - throw new CannotUnloadPluginException("Cannot unload plugin during playback execution"); - } - } - }; + + PlaybackRunnerExtendedPluginUnloadVetoer.activeExecutionCount.incrementAndGet(); + Disposer.register(onStop, () -> { + PlaybackRunnerExtendedPluginUnloadVetoer.activeExecutionCount.decrementAndGet(); + }); } @Override @@ -56,12 +52,6 @@ public final class PlaybackRunnerExtended extends PlaybackRunner { return myProject; } - @Override - protected void subscribeListeners(MessageBusConnection connection) { - super.subscribeListeners(connection); - connection.subscribe(DynamicPluginListener.TOPIC, myDynamicPluginListener); - } - @Override protected void onStop() { super.onStop(); @@ -99,4 +89,17 @@ public final class PlaybackRunnerExtended extends PlaybackRunner { RunCallbackHandler.applyPatchesToCommandCallback(myProject, callback); return callback; } + + static class PlaybackRunnerExtendedPluginUnloadVetoer implements DynamicPluginVetoer { + static final AtomicInteger activeExecutionCount = new AtomicInteger(); + + @Override + public @Nls @Nullable String vetoPluginUnload(@NotNull IdeaPluginDescriptor pluginDescriptor) { + if (activeExecutionCount.get() > 0 && + PluginId.getId("com.jetbrains.performancePlugin").equals(pluginDescriptor.getPluginId())) { + return "Cannot unload plugin during playback execution"; + } + return null; + } + } }