From b15e34043dc7b2414cf582e7b01ee9c32f73629a Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 12 Apr 2021 12:22:21 +0200 Subject: [PATCH] prevent AWT auto shutdown, don't create app in EDT GitOrigin-RevId: 861d20e85bdee38f4ef371984ae360c5ff8310d8 --- .../com/intellij/idea/ApplicationLoader.kt | 83 ++++++++++--------- .../src/com/intellij/idea/StartupUtil.java | 19 ++++- .../application/impl/ApplicationImpl.java | 53 +++++++++--- .../impl/NoSwingUnderWriteAction.java | 12 +-- .../AppScheduledExecutorService.java | 14 ++-- .../util/src/com/intellij/util/ui/EDT.java | 8 +- 6 files changed, 122 insertions(+), 67 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt b/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt index 833777bacdc7..71a2c6e0e619 100644 --- a/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt +++ b/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt @@ -3,6 +3,7 @@ @file:ApiStatus.Internal package com.intellij.idea +import com.intellij.BundleBase import com.intellij.diagnostic.* import com.intellij.diagnostic.StartUpMeasurer.Activities import com.intellij.icons.AllIcons @@ -35,9 +36,9 @@ import com.intellij.util.io.createDirectories import com.intellij.util.io.storage.HeavyProcessLatch import com.intellij.util.lang.ZipFilePool import com.intellij.util.ui.AsyncProcessIcon +import com.intellij.util.ui.EDT import net.miginfocom.layout.PlatformDefaults import org.jetbrains.annotations.ApiStatus -import java.awt.EventQueue import java.io.IOException import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -52,61 +53,61 @@ private val SAFE_JAVA_ENV_PARAMETERS = arrayOf(JetBrainsProtocolHandler.REQUIRED private val LOG = Logger.getInstance("#com.intellij.idea.ApplicationLoader") fun initApplication(rawArgs: List, prepareUiFuture: CompletionStage<*>) { + val args = processProgramArguments(rawArgs) + val initAppActivity = StartupUtil.startupStart.endAndStart(Activities.INIT_APP) val loadAndInitPluginFutureActivity = initAppActivity.startChild("plugin descriptor init waiting") val loadAndInitPluginFuture = PluginManagerCore.initPlugins(StartupUtil::class.java.classLoader) loadAndInitPluginFuture.thenRun(loadAndInitPluginFutureActivity::end) - // use main thread, avoid thread switching - (prepareUiFuture as CompletableFuture<*>).join() + prepareUiFuture.thenComposeAsync({ + val isInternal = java.lang.Boolean.getBoolean(ApplicationManagerEx.IS_INTERNAL_PROPERTY) + val app = ApplicationImpl(isInternal, false, Main.isHeadless(), Main.isCommandLine(), EDT.getEventDispatchThread() + ?: throw IllegalStateException("Init UI first")) + (UIManager.getLookAndFeel() as? DarculaLaf)?.appCreated(app) - val args = processProgramArguments(rawArgs) - EventQueue.invokeLater { - runActivity("create app") { - val isInternal = java.lang.Boolean.getBoolean(ApplicationManagerEx.IS_INTERNAL_PROPERTY) - ApplicationImpl(isInternal, false, Main.isHeadless(), Main.isCommandLine()) { app -> - (UIManager.getLookAndFeel() as? DarculaLaf)?.appCreated(app) + if (isInternal) { + BundleBase.assertOnMissedKeys(true) + } - loadAndInitPluginFuture - .thenAcceptAsync({ plugins -> - runActivity("app component registration") { - app.registerComponents(plugins, app, null) - } + loadAndInitPluginFuture + .thenAccept { plugins -> + runActivity("app component registration") { + app.registerComponents(plugins, app, null) + } - if (args.isEmpty()) { - startApp(app, IdeStarter(), initAppActivity, plugins, args) - } - else { - // `ApplicationStarter` is an extension, so to find a starter extensions must be registered first - findCustomAppStarterAndStart(plugins, args, app, initAppActivity) - } + if (args.isEmpty()) { + startApp(app, IdeStarter(), initAppActivity, plugins, args) + } + else { + // `ApplicationStarter` is an extension, so to find a starter extensions must be registered first + findCustomAppStarterAndStart(plugins, args, app, initAppActivity) + } - if (!Main.isHeadless()) { - ForkJoinPool.commonPool().execute { - runActivity("icons preloading") { - if (isInternal) { - IconLoader.setStrictGlobally(true) - } + if (!Main.isHeadless()) { + ForkJoinPool.commonPool().execute { + runActivity("icons preloading") { + if (isInternal) { + IconLoader.setStrictGlobally(true) + } - AsyncProcessIcon("") - AnimatedIcon.Blinking(AllIcons.Ide.FatalError) - AnimatedIcon.FS() - } + AsyncProcessIcon("") + AnimatedIcon.Blinking(AllIcons.Ide.FatalError) + AnimatedIcon.FS() + } - runActivity("migLayout") { - // IDEA-170295 - PlatformDefaults.setLogicalPixelBase(PlatformDefaults.BASE_FONT_SIZE) - } - } + runActivity("migLayout") { + // IDEA-170295 + PlatformDefaults.setLogicalPixelBase(PlatformDefaults.BASE_FONT_SIZE) } - }, { if (EventQueue.isDispatchThread()) ForkJoinPool.commonPool().execute(it) else it.run() }) - .exceptionally { - StartupAbortedException.processException(it) - null } + } } + }, Executor { if (EDT.isCurrentThreadEdt()) ForkJoinPool.commonPool().execute(it) else it.run() } ) + .exceptionally { + StartupAbortedException.processException(it) + null } - } } private fun startApp(app: ApplicationImpl, diff --git a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java index 2ae5c3cc25a8..ff5a83f4641f 100644 --- a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java +++ b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java @@ -27,6 +27,7 @@ import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.ConfigImportHelper; import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.impl.AWTExceptionHandler; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.ShutDownTracker; @@ -40,6 +41,7 @@ import com.intellij.ui.IconManager; import com.intellij.ui.mac.MacOSApplicationProvider; import com.intellij.ui.scale.JBUIScale; import com.intellij.util.EnvironmentUtil; +import com.intellij.util.concurrency.AppScheduledExecutorService; import com.intellij.util.lang.ZipFilePool; import com.intellij.util.ui.StartupUiUtil; import com.intellij.util.ui.accessibility.ScreenReader; @@ -477,9 +479,20 @@ public final class StartupUtil { catch (UnsupportedLookAndFeelException e) { throw new RuntimeException(e); } - activity.end(); StartUpMeasurer.setCurrentState(LoadingState.LAF_INITIALIZED); + + activity = activity.endAndStart("awt thread busy notification"); + // instantiate AppDelayQueue which starts "Periodic task thread" which we'll mark busy to prevent this EDT to die + // that thread was chosen because we know for sure it's running + Thread thread = AppScheduledExecutorService.getPeriodicTasksThread(); + // needed for EDT not to exit suddenly + AWTAutoShutdown.getInstance().notifyThreadBusy(thread); + ShutDownTracker.getInstance().registerShutdownTask(() -> { + // allow for EDT to exit + AWTAutoShutdown.getInstance().notifyThreadFree(thread); + }); + activity.end(); }, it -> EventQueue.invokeLater(it)/* don't use here method reference (EventQueue class must be loaded on demand) */); if (isUsingSeparateWriteThread()) { @@ -954,6 +967,10 @@ public final class StartupUtil { } } } + + // do not crash AWT on exceptions + AWTExceptionHandler.register(); + activity.end(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 180a4370bf2a..816c39579cb8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -111,21 +111,12 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application } public ApplicationImpl(boolean isInternal, boolean isUnitTestMode, boolean isHeadless, boolean isCommandLine) { - this(isInternal, isUnitTestMode, isHeadless, isCommandLine, null); - } - - public ApplicationImpl(boolean isInternal, boolean isUnitTestMode, boolean isHeadless, boolean isCommandLine, Consumer initTask) { super(null); registerServiceInstance(TransactionGuard.class, myTransactionGuard, ComponentManagerImpl.getFakeCorePluginDescriptor()); registerServiceInstance(ApplicationInfo.class, ApplicationInfoImpl.getShadowInstance(), ComponentManagerImpl.getFakeCorePluginDescriptor()); registerServiceInstance(Application.class, this, ComponentManagerImpl.getFakeCorePluginDescriptor()); - if (initTask != null) { - // myLock is a final field, we cannot do init in EDT and set it later - initTask.accept(this); - } - if (isUnitTestMode || isInternal) { BundleBase.assertOnMissedKeys(true); } @@ -147,6 +138,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application if (!isUnitTestMode && !isHeadless) { Disposable uiRootDisposable = Disposer.newDisposable(); + //noinspection deprecation Disposer.register(this, uiRootDisposable, "ui"); } @@ -155,7 +147,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application EdtInvocationManager.invokeAndWaitIfNeeded(() -> { // instantiate AppDelayQueue which starts "Periodic task thread" which we'll mark busy to prevent this EDT to die // that thread was chosen because we know for sure it's running - Thread thread = ((AppScheduledExecutorService)AppExecutorUtil.getAppScheduledExecutorService()).getPeriodicTasksThread(); + Thread thread = AppScheduledExecutorService.getPeriodicTasksThread(); AWTAutoShutdown.getInstance().notifyThreadBusy(thread); // needed for EDT not to exit suddenly Disposer.register(this, () -> { AWTAutoShutdown.getInstance().notifyThreadFree(thread); // allow for EDT to exit - needed for Upsource @@ -172,6 +164,47 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application NoSwingUnderWriteAction.watchForEvents(this); } + // this constructor must be called only by ApplicationLoader + public ApplicationImpl(boolean isInternal, + boolean isUnitTestMode, + boolean isHeadless, + boolean isCommandLine, + @NotNull Thread edtThread) { + super(null); + + registerServiceInstance(TransactionGuard.class, myTransactionGuard, ComponentManagerImpl.getFakeCorePluginDescriptor()); + registerServiceInstance(ApplicationInfo.class, ApplicationInfoImpl.getShadowInstance(), ComponentManagerImpl.getFakeCorePluginDescriptor()); + registerServiceInstance(Application.class, this, ComponentManagerImpl.getFakeCorePluginDescriptor()); + + Disposer.setDebugMode(isInternal || isUnitTestMode || Disposer.isDebugDisposerOn()); + + myIsInternal = isInternal; + myTestModeFlag = isUnitTestMode; + myHeadlessMode = isHeadless; + myCommandLineMode = isCommandLine; + + mySaveAllowed = !(isUnitTestMode || isHeadless); + + if (!isUnitTestMode && !isHeadless) { + Disposable uiRootDisposable = Disposer.newDisposable(); + //noinspection deprecation + Disposer.register(this, uiRootDisposable, "ui"); + } + + Activity activity = StartUpMeasurer.startActivity("AppDelayQueue instantiation", ActivityCategory.DEFAULT); + myLock = new ReadMostlyRWLock(edtThread); + // Acquire IW lock on EDT indefinitely in legacy mode + if (!USE_SEPARATE_WRITE_THREAD || isUnitTestMode) { + EventQueue.invokeLater(() -> acquireWriteIntentLock(getClass())); + } + activity.end(); + + NoSwingUnderWriteAction.watchForEvents(this); + + // reset back to null only when all components already disposed + ApplicationManager.setApplication(this, myLastDisposable); + } + /** * Executes a {@code runnable} in an "impatient" mode. * In this mode any attempt to call {@link #runReadAction(Runnable)} diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/NoSwingUnderWriteAction.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/NoSwingUnderWriteAction.java index cd26a23fe127..f9a8e4bc7a1f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/NoSwingUnderWriteAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/NoSwingUnderWriteAction.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 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-2021 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.openapi.application.impl; import com.intellij.ide.IdeEventQueue; @@ -15,20 +15,20 @@ import java.util.concurrent.atomic.AtomicBoolean; final class NoSwingUnderWriteAction { private static final Logger LOG = Logger.getInstance(NoSwingUnderWriteAction.class); - static void watchForEvents(Application application) { + static void watchForEvents(@NotNull Application app) { AtomicBoolean reported = new AtomicBoolean(); IdeEventQueue.getInstance().addPostprocessor(e -> { - if (application.isWriteAccessAllowed() && reported.compareAndSet(false, true)) { + if (app.isWriteAccessAllowed() && reported.compareAndSet(false, true)) { LOG.error("AWT events are not allowed inside write action: " + e); } return true; - }, application); + }, app); - application.addApplicationListener(new ApplicationListener() { + app.addApplicationListener(new ApplicationListener() { @Override public void afterWriteActionFinished(@NotNull Object action) { reported.set(false); } - }, application); + }, app); } } diff --git a/platform/util/src/com/intellij/util/concurrency/AppScheduledExecutorService.java b/platform/util/src/com/intellij/util/concurrency/AppScheduledExecutorService.java index 8b91b6ec14ef..c4fbf3f6e4bf 100644 --- a/platform/util/src/com/intellij/util/concurrency/AppScheduledExecutorService.java +++ b/platform/util/src/com/intellij/util/concurrency/AppScheduledExecutorService.java @@ -1,4 +1,4 @@ -// 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. +// Copyright 2000-2021 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.concurrency; import com.intellij.openapi.diagnostic.Logger; @@ -27,16 +27,15 @@ public final class AppScheduledExecutorService extends SchedulingWrapper { private final LowMemoryWatcherManager myLowMemoryWatcherManager; private final MyThreadFactory myCountingThreadFactory; - private static class Holder { + private static final class Holder { private static final AppScheduledExecutorService INSTANCE = new AppScheduledExecutorService("Global instance", 1, TimeUnit.MINUTES); } - @NotNull - static ScheduledExecutorService getInstance() { + static @NotNull ScheduledExecutorService getInstance() { return Holder.INSTANCE; } - private static class MyThreadFactory extends CountingThreadFactory { + private static final class MyThreadFactory extends CountingThreadFactory { private BiConsumer newThreadListener; private final ThreadFactory myThreadFactory = Executors.privilegedThreadFactory(); @@ -195,9 +194,8 @@ public final class AppScheduledExecutorService extends SchedulingWrapper { } } - @NotNull - public Thread getPeriodicTasksThread() { - return delayQueue.getThread(); + public static @NotNull Thread getPeriodicTasksThread() { + return Holder.INSTANCE.delayQueue.getThread(); } @TestOnly diff --git a/platform/util/src/com/intellij/util/ui/EDT.java b/platform/util/src/com/intellij/util/ui/EDT.java index 0ed5ef42e973..4a66e5ecc2b7 100644 --- a/platform/util/src/com/intellij/util/ui/EDT.java +++ b/platform/util/src/com/intellij/util/ui/EDT.java @@ -1,9 +1,10 @@ -// 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. +// Copyright 2000-2021 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.ui; import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; @@ -33,6 +34,11 @@ public final class EDT { return thread == myEventDispatchThread; } + @ApiStatus.Internal + public static @Nullable Thread getEventDispatchThread() { + return myEventDispatchThread; + } + /** * Checks whether the current thread is EDT. *