/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.application.impl; import com.intellij.CommonBundle; import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory; import com.intellij.concurrency.JobScheduler; import com.intellij.diagnostic.LogEventException; import com.intellij.diagnostic.PerformanceWatcher; import com.intellij.diagnostic.ThreadDumper; import com.intellij.ide.*; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.idea.ApplicationStarter; import com.intellij.idea.StartupUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.*; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.application.ex.ApplicationUtil; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.StateStorageException; import com.intellij.openapi.components.impl.ApplicationPathMacroManager; import com.intellij.openapi.components.impl.PlatformComponentManagerImpl; import com.intellij.openapi.components.impl.stores.ApplicationStoreImpl; import com.intellij.openapi.components.impl.stores.IApplicationStore; import com.intellij.openapi.components.impl.stores.IComponentStore; import com.intellij.openapi.components.impl.stores.StoreUtil; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.PotemkinProgress; import com.intellij.openapi.progress.util.ProgressWindow; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ex.ProjectEx; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.project.impl.ProjectManagerImpl; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.MessageDialogBuilder; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.PsiLock; import com.intellij.ui.AppIcon; import com.intellij.ui.Splash; import com.intellij.util.*; import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.concurrency.AppScheduledExecutorService; import com.intellij.util.containers.Stack; import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.ui.UIUtil; import consulo.annotations.RequiredDispatchThread; import consulo.annotations.RequiredReadAction; import consulo.annotations.RequiredWriteAction; import consulo.application.ApplicationProperties; import consulo.application.ex.ApplicationEx2; import consulo.start.CommandLineArgs; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import org.jetbrains.ide.PooledThreadExecutor; import org.picocontainer.MutablePicoContainer; import sun.awt.AWTAccessor; import sun.awt.AWTAutoShutdown; import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; public class ApplicationImpl extends PlatformComponentManagerImpl implements ApplicationEx2 { private static final Logger LOG = Logger.getInstance("#com.intellij.application.impl.ApplicationImpl"); final ReadMostlyRWLock myLock; private final ModalityInvokator myInvokator = new ModalityInvokatorImpl(); private final EventDispatcher<ApplicationListener> myDispatcher = EventDispatcher.create(ApplicationListener.class); private final boolean myTestModeFlag; private final boolean myHeadlessMode; private final boolean myCommandLineMode; private final boolean myIsInternal; private final Stack<Class> myWriteActionsStack = new Stack<>(); // accessed from EDT only, no need to sync private final TransactionGuardImpl myTransactionGuard = new TransactionGuardImpl(); private int myInEditorPaintCounter; // EDT only private final long myStartTime; @Nullable private final Splash mySplash; private boolean myDoNotSave; private volatile boolean myDisposeInProgress; private final Disposable myLastDisposable = Disposer.newDisposable(); // will be disposed last private final AtomicBoolean mySaveSettingsIsInProgress = new AtomicBoolean(false); private final ExecutorService ourThreadExecutorsService = PooledThreadExecutor.INSTANCE; @SuppressWarnings("UseOfArchaicSystemPropertyAccessors") private static final int ourDumpThreadsOnLongWriteActionWaiting = Integer.getInteger("dump.threads.on.long.write.action.waiting", 0); private boolean myIsFiringLoadingEvent = false; private boolean myLoaded = false; @NonNls private static final String WAS_EVER_SHOWN = "was.ever.shown"; private int myWriteStackBase; private volatile Thread myWriteActionThread; private final boolean gatherStatistics; private static class ActionPauses { private static final PausesStat WRITE = new PausesStat("Write action"); } private static final ModalityState ANY = new ModalityState() { @Override public boolean dominates(@NotNull ModalityState anotherState) { return false; } @NonNls @Override public String toString() { return "ANY"; } }; @Override protected void bootstrapPicoContainer(@NotNull String name) { super.bootstrapPicoContainer(name); getPicoContainer().registerComponentImplementation(IComponentStore.class, ApplicationStoreImpl.class); getPicoContainer().registerComponentImplementation(ApplicationPathMacroManager.class); } @Override @NotNull public IApplicationStore getStateStore() { return (IApplicationStore)getPicoContainer().getComponentInstance(IComponentStore.class); } @Override public void initializeComponent(@NotNull Object component, boolean service) { getStateStore().initComponent(component); } static { IdeaForkJoinWorkerThreadFactory.setupForkJoinCommonPool(); } public ApplicationImpl(boolean isInternal, boolean isUnitTestMode, boolean isHeadless, boolean isCommandLine, @NotNull String appName, @Nullable Splash splash) { super(null); ApplicationManager.setApplication(this, myLastDisposable); // reset back to null only when all components already disposed getPicoContainer().registerComponentInstance(Application.class, this); AWTExceptionHandler.register(); // do not crash AWT on exceptions String debugDisposer = System.getProperty("idea.disposer.debug"); Disposer.setDebugMode((isInternal || isUnitTestMode || "on".equals(debugDisposer)) && !"off".equals(debugDisposer)); myStartTime = System.currentTimeMillis(); mySplash = splash; myIsInternal = isInternal; myTestModeFlag = isUnitTestMode || Boolean.getBoolean(ApplicationProperties.CONSULO_AS_WEB_APP); myHeadlessMode = isHeadless; myCommandLineMode = isCommandLine; myDoNotSave = isUnitTestMode || isHeadless; gatherStatistics = LOG.isDebugEnabled() || isUnitTestMode() || isInternal(); loadApplicationComponents(); if (myTestModeFlag) { registerShutdownHook(); } if (!isUnitTestMode && !isHeadless) { Disposer.register(this, Disposer.newDisposable(), "ui"); StartupUtil.addExternalInstanceListener(commandLineArgs -> { LOG.info("ApplicationImpl.externalInstanceListener invocation"); final Project project = CommandLineProcessor.processExternalCommandLine(commandLineArgs, null); final IdeFrame frame = WindowManager.getInstance().getIdeFrame(project); if (frame != null) AppIcon.getInstance().requestFocus(frame); }); WindowsCommandLineProcessor.LISTENER = (currentDirectory, commandLine) -> { LOG.info("Received external Windows command line: current directory " + currentDirectory + ", command line " + commandLine); invokeLater(() -> { final List<String> args = StringUtil.splitHonorQuotes(commandLine, ' '); args.remove(0); // process name CommandLineProcessor.processExternalCommandLine(CommandLineArgs.parse(ArrayUtil.toStringArray(args)), currentDirectory); }); }; } Thread edt = UIUtil.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 AppScheduledExecutorService service = (AppScheduledExecutorService)AppExecutorUtil.getAppScheduledExecutorService(); Thread thread = service.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 }); return Thread.currentThread(); }); myLock = new ReadMostlyRWLock(edt); if (isUnitTestMode) { ApplicationStarter.ourLoaded = true; } NoSwingUnderWriteAction.watchForEvents(this); } /** * Executes a {@code runnable} in an "impatient" mode. * In this mode any attempt to call {@link #runReadAction(Runnable)} * would fail (i.e. throw {@link ApplicationUtil.CannotRunReadActionException}) * if there is a pending write action. */ public void executeByImpatientReader(@NotNull Runnable runnable) throws ApplicationUtil.CannotRunReadActionException { if (isDispatchThread()) { runnable.run(); } else { myLock.executeByImpatientReader(runnable); } } private void registerShutdownHook() { ShutDownTracker.getInstance().registerShutdownTask(() -> { if (isDisposed() || myDisposeInProgress) { return; } ShutDownTracker.invokeAndWait(isUnitTestMode(), true, new Runnable() { @Override @RequiredDispatchThread public void run() { if (ApplicationManager.getApplication() != ApplicationImpl.this) return; try { myDisposeInProgress = true; saveAll(); } finally { if (!disposeSelf(true)) { myDisposeInProgress = false; } } } }); }); } @RequiredDispatchThread private boolean disposeSelf(final boolean checkCanCloseProject) { final ProjectManagerImpl manager = (ProjectManagerImpl)ProjectManagerEx.getInstanceEx(); if (manager != null) { final boolean[] canClose = {true}; for (final Project project : manager.getOpenProjects()) { try { CommandProcessor.getInstance().executeCommand(project, () -> { if (!manager.closeProject(project, true, true, checkCanCloseProject)) { canClose[0] = false; } }, ApplicationBundle.message("command.exit"), null); } catch (Throwable e) { LOG.error(e); } if (!canClose[0]) { return false; } } } runWriteAction(() -> Disposer.dispose(ApplicationImpl.this)); Disposer.assertIsEmpty(); return true; } @Override public boolean holdsReadLock() { return myLock.isReadLockedByThisThread(); } private void loadApplicationComponents() { PluginManagerCore.BUILD_NUMBER = ApplicationInfoImpl.getShadowInstance().getBuild().asString(); PluginManagerCore.initPlugins(mySplash); IdeaPluginDescriptor[] plugins = PluginManagerCore.getPlugins(); for (IdeaPluginDescriptor plugin : plugins) { if (!PluginManagerCore.shouldSkipPlugin(plugin)) { loadComponentsConfiguration(plugin.getAppComponents(), plugin, false); } } } @Override protected synchronized Object createComponent(@NotNull Class componentInterface) { Object component = super.createComponent(componentInterface); if (mySplash != null) { mySplash.showProgress("", 0.65f + getPercentageOfComponentsLoaded() * 0.35f); } return component; } @NotNull @Override protected MutablePicoContainer createPicoContainer() { return Extensions.getRootArea().getPicoContainer(); } @Override public boolean isInternal() { return myIsInternal; } @Override public boolean isEAP() { return ApplicationInfoImpl.getShadowInstance().isEAP(); } @Override public boolean isUnitTestMode() { return myTestModeFlag; } @Override public boolean isHeadlessEnvironment() { return myHeadlessMode; } @Override public boolean isCompilerServerMode() { return false; } @Override public boolean isCommandLine() { return myCommandLineMode; } @NotNull @Override public Future<?> executeOnPooledThread(@NotNull final Runnable action) { ReadMostlyRWLock.SuspensionId suspensionId = myLock.currentReadPrivilege(); return ourThreadExecutorsService.submit(new Runnable() { @Override public String toString() { return action.toString(); } @Override public void run() { try (AccessToken ignored = myLock.applyReadPrivilege(suspensionId)) { action.run(); } catch (ProcessCanceledException e) { // ignore } catch (Throwable t) { LOG.error(t); } finally { Thread.interrupted(); // reset interrupted status } } }); } @NotNull @Override public <T> Future<T> executeOnPooledThread(@NotNull final Callable<T> action) { ReadMostlyRWLock.SuspensionId suspensionId = myLock.currentReadPrivilege(); return ourThreadExecutorsService.submit(new Callable<T>() { @Override public T call() { try (AccessToken ignored = myLock.applyReadPrivilege(suspensionId)) { return action.call(); } catch (ProcessCanceledException e) { // ignore } catch (Throwable t) { LOG.error(t); } finally { Thread.interrupted(); // reset interrupted status } return null; } @Override public String toString() { return action.toString(); } }); } @Override public boolean isDispatchThread() { return myLock.isWriteThread(); } @Override @NotNull public ModalityInvokator getInvokator() { return myInvokator; } @Override public void invokeLater(@NotNull final Runnable runnable) { invokeLater(runnable, getDisposed()); } @Override public void invokeLater(@NotNull final Runnable runnable, @NotNull final Condition expired) { invokeLater(runnable, ModalityState.defaultModalityState(), expired); } @Override public void invokeLater(@NotNull final Runnable runnable, @NotNull final ModalityState state) { invokeLater(runnable, state, getDisposed()); } @Override public void invokeLater(@NotNull final Runnable runnable, @NotNull final ModalityState state, @NotNull final Condition expired) { myInvokator.invokeLater(myTransactionGuard.wrapLaterInvocation(runnable, state), state, expired); } @Override public void load(@Nullable String optionsPath) throws IOException { load(PathManager.getConfigPath(), optionsPath == null ? PathManager.getOptionsPath() : optionsPath); } public void load(@NotNull String configPath, @NotNull String optionsPath) throws IOException { IApplicationStore store = getStateStore(); store.setOptionsPath(optionsPath); store.setConfigPath(configPath); myIsFiringLoadingEvent = true; try { fireBeforeApplicationLoaded(); } finally { myIsFiringLoadingEvent = false; } AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("Loading application components"); try { store.load(); } catch (StateStorageException e) { throw new IOException(e.getMessage()); } finally { token.finish(); } myLoaded = true; createLocatorFile(); } private static void createLocatorFile() { File locatorFile = new File(PathManager.getSystemPath() + "/" + ApplicationEx.LOCATOR_FILE_NAME); try { byte[] data = PathManager.getHomePath().getBytes(CharsetToolkit.UTF8_CHARSET); FileUtil.writeToFile(locatorFile, data); } catch (IOException e) { LOG.warn("can't store a location in '" + locatorFile + "'", e); } } @Override public boolean isLoaded() { return myLoaded; } @Override protected <T> T getComponentFromContainer(@NotNull final Class<T> interfaceClass) { if (myIsFiringLoadingEvent) { return null; } return super.getComponentFromContainer(interfaceClass); } private void fireBeforeApplicationLoaded() { for (ApplicationLoadListener listener : ApplicationLoadListener.EP_NAME.getExtensions()) { try { listener.beforeApplicationLoaded(this); } catch (Exception e) { LOG.error(e); } } } @Override public void dispose() { fireApplicationExiting(); ShutDownTracker.getInstance().ensureStopperThreadsFinished(); disposeComponents(); AppScheduledExecutorService service = (AppScheduledExecutorService)AppExecutorUtil.getAppScheduledExecutorService(); service.shutdownAppScheduledExecutorService(); super.dispose(); Disposer.dispose(myLastDisposable); // dispose it last } @RequiredDispatchThread @Override public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process, @NotNull String progressTitle, boolean canBeCanceled, Project project) { return runProcessWithProgressSynchronously(process, progressTitle, canBeCanceled, project, null); } @RequiredDispatchThread @Override public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process, @NotNull final String progressTitle, final boolean canBeCanceled, @Nullable final Project project, final JComponent parentComponent) { return runProcessWithProgressSynchronously(process, progressTitle, canBeCanceled, project, parentComponent, null); } @RequiredDispatchThread @Override public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process, @NotNull final String progressTitle, final boolean canBeCanceled, @Nullable final Project project, final JComponent parentComponent, final String cancelText) { assertIsDispatchThread(); boolean writeAccessAllowed = isWriteAccessAllowed(); if (writeAccessAllowed // Disallow running process in separate thread from under write action. // The thread will deadlock trying to get read action otherwise. || isHeadlessEnvironment() && !isUnitTestMode()) { if (writeAccessAllowed) { LOG.debug("Starting process with progress from within write action makes no sense"); } try { ProgressManager.getInstance().runProcess(process, new EmptyProgressIndicator()); } catch (ProcessCanceledException e) { // ok to ignore. return false; } return true; } final ProgressWindow progress = new ProgressWindow(canBeCanceled, false, project, parentComponent, cancelText); // in case of abrupt application exit when 'ProgressManager.getInstance().runProcess(process, progress)' below // does not have a chance to run, and as a result the progress won't be disposed Disposer.register(this, progress); progress.setTitle(progressTitle); final AtomicBoolean threadStarted = new AtomicBoolean(); //noinspection SSBasedInspection SwingUtilities.invokeLater(() -> { executeOnPooledThread(() -> { try { ProgressManager.getInstance().runProcess(process, progress); } catch (ProcessCanceledException e) { progress.cancel(); // ok to ignore. } catch (RuntimeException e) { progress.cancel(); throw e; } }); threadStarted.set(true); }); progress.startBlocking(); LOG.assertTrue(threadStarted.get()); LOG.assertTrue(!progress.isRunning()); return !progress.isCanceled(); } @Override public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) { if (isDispatchThread()) { runnable.run(); return; } if (holdsReadLock()) { LOG.error("Calling invokeAndWait from read-action leads to possible deadlock."); } LaterInvocator.invokeAndWait(myTransactionGuard.wrapLaterInvocation(runnable, modalityState), modalityState); } @Override @NotNull public ModalityState getCurrentModalityState() { Object[] entities = LaterInvocator.getCurrentModalEntities(); return entities.length > 0 ? new ModalityStateEx(entities) : getNoneModalityState(); } @Override @NotNull public ModalityState getModalityStateForComponent(@NotNull Component c) { Window window = UIUtil.getWindow(c); if (window == null) return getNoneModalityState(); //? return LaterInvocator.modalityStateForWindow(window); } @Override @NotNull public ModalityState getAnyModalityState() { return ANY; } @Override @NotNull public ModalityState getDefaultModalityState() { if (isDispatchThread()) { return getCurrentModalityState(); } ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); return progress == null ? getNoneModalityState() : progress.getModalityState(); } @Override @NotNull public ModalityState getNoneModalityState() { return ModalityState.NON_MODAL; } @Override public long getStartTime() { return myStartTime; } @RequiredDispatchThread @Override public long getIdleTime() { assertIsDispatchThread(); return IdeEventQueue.getInstance().getIdleTime(); } @Override public void exit() { exit(false, false); } @Override public void exit(boolean force, final boolean exitConfirmed) { exit(false, exitConfirmed, true, false); } @Override public void restart() { restart(false); } @Override public void restart(final boolean exitConfirmed) { exit(false, exitConfirmed, true, true); } /* * There are two ways we can get an exit notification. * 1. From user input i.e. ExitAction * 2. From the native system. * We should not process any quit notifications if we are handling another one * * Note: there are possible scenarios when we get a quit notification at a moment when another * quit message is shown. In that case, showing multiple messages sounds contra-intuitive as well */ private static volatile boolean exiting = false; public void exit(final boolean force, final boolean exitConfirmed, final boolean allowListenersToCancel, final boolean restart) { if (!force && exiting) { return; } exiting = true; try { if (!force && !exitConfirmed && getDefaultModalityState() != ModalityState.NON_MODAL) { return; } Runnable runnable = new Runnable() { @Override @RequiredDispatchThread public void run() { if (!force && !confirmExitIfNeeded(exitConfirmed)) { saveAll(); return; } getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).appClosing(); myDisposeInProgress = true; doExit(allowListenersToCancel, restart); myDisposeInProgress = false; } }; if (isDispatchThread()) { runnable.run(); } else { invokeLater(runnable, ModalityState.NON_MODAL); } } finally { exiting = false; } } @RequiredDispatchThread private boolean doExit(boolean allowListenersToCancel, boolean restart) { saveSettings(); if (allowListenersToCancel && !canExit()) { return false; } final boolean success = disposeSelf(allowListenersToCancel); if (!success || isUnitTestMode()) { return false; } int exitCode = 0; if (restart && Restarter.isSupported()) { try { exitCode = Restarter.scheduleRestart(); } catch (IOException e) { LOG.warn("Cannot restart", e); } } System.exit(exitCode); return true; } private static boolean confirmExitIfNeeded(boolean exitConfirmed) { final boolean hasUnsafeBgTasks = ProgressManager.getInstance().hasUnsafeProgressIndicator(); if (exitConfirmed && !hasUnsafeBgTasks) { return true; } DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() { @Override public boolean isToBeShown() { return GeneralSettings.getInstance().isConfirmExit() && ProjectManager.getInstance().getOpenProjects().length > 0; } @Override public void setToBeShown(boolean value, int exitCode) { GeneralSettings.getInstance().setConfirmExit(value); } @Override public boolean canBeHidden() { return !hasUnsafeBgTasks; } @Override public boolean shouldSaveOptionsOnCancel() { return false; } @NotNull @Override public String getDoNotShowMessage() { return "Do not ask me again"; } }; if (hasUnsafeBgTasks || option.isToBeShown()) { String message = ApplicationBundle .message(hasUnsafeBgTasks ? "exit.confirm.prompt.tasks" : "exit.confirm.prompt", ApplicationNamesInfo.getInstance().getFullProductName()); if (MessageDialogBuilder.yesNo(ApplicationBundle.message("exit.confirm.title"), message).yesText(ApplicationBundle.message("command.exit")) .noText(CommonBundle.message("button.cancel")).doNotAsk(option).show() != Messages.YES) { return false; } } return true; } private boolean canExit() { for (ApplicationListener applicationListener : myDispatcher.getListeners()) { if (!applicationListener.canExitApplication()) { return false; } } ProjectManagerEx projectManager = (ProjectManagerEx)ProjectManager.getInstance(); Project[] projects = projectManager.getOpenProjects(); for (Project project : projects) { if (!projectManager.canClose(project)) { return false; } } return true; } @Override public void runReadAction(@NotNull final Runnable action) { if (isReadAccessAllowed()) { action.run(); } else { startRead(); try { action.run(); } finally { endRead(); } } } @Override public <T> T runReadAction(@NotNull final Computable<T> computation) { if (isReadAccessAllowed()) { return computation.compute(); } startRead(); try { return computation.compute(); } finally { endRead(); } } @Override public <T, E extends Throwable> T runReadAction(@NotNull ThrowableComputable<T, E> computation) throws E { if (isReadAccessAllowed()) { return computation.compute(); } startRead(); try { return computation.compute(); } finally { endRead(); } } private void startRead() { assertNoPsiLock(); myLock.readLock(); } private void endRead() { myLock.readUnlock(); } public boolean runWriteActionWithProgressInDispatchThread(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText, @NotNull Consumer<ProgressIndicator> action) { Class<?> clazz = action.getClass(); startWrite(clazz); try { PotemkinProgress indicator = new PotemkinProgress(title, project, parentComponent, cancelText); indicator.runInSwingThread(() -> action.accept(indicator)); return !indicator.isCanceled(); } finally { endWrite(clazz); } } public boolean runWriteActionWithProgressInBackgroundThread(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText, @NotNull Consumer<ProgressIndicator> action) { Class<?> clazz = action.getClass(); startWrite(clazz); try { PotemkinProgress indicator = new PotemkinProgress(title, project, parentComponent, cancelText); indicator.runInBackground(() -> { assert myWriteActionThread == null; myWriteActionThread = Thread.currentThread(); try { action.accept(indicator); } finally { myWriteActionThread = null; } }); return !indicator.isCanceled(); } finally { endWrite(clazz); } } @RequiredDispatchThread @Override public void runWriteAction(@NotNull final Runnable action) { Class<? extends Runnable> clazz = action.getClass(); startWrite(clazz); try { action.run(); } finally { endWrite(clazz); } } @RequiredDispatchThread @Override public <T> T runWriteAction(@NotNull final Computable<T> computation) { Class<? extends Computable> clazz = computation.getClass(); startWrite(clazz); try { return computation.compute(); } finally { endWrite(clazz); } } @RequiredDispatchThread @Override public <T, E extends Throwable> T runWriteAction(@NotNull ThrowableComputable<T, E> computation) throws E { Class<? extends ThrowableComputable> clazz = computation.getClass(); startWrite(clazz); try { return computation.compute(); } finally { endWrite(clazz); } } @RequiredDispatchThread @Override public boolean hasWriteAction(@NotNull Class<?> actionClass) { assertReadAccessAllowed(); for (int i = myWriteActionsStack.size() - 1; i >= 0; i--) { Class action = myWriteActionsStack.get(i); if (actionClass == action || ReflectionUtil.isAssignable(actionClass, action)) return true; } return false; } @RequiredReadAction @Override public void assertReadAccessAllowed() { if (!isReadAccessAllowed()) { LOG.error("Read access is allowed from event dispatch thread or inside read-action only" + " (see com.intellij.openapi.application.Application.runReadAction())", "Current thread: " + describe(Thread.currentThread()), "; dispatch thread: " + EventQueue.isDispatchThread() + "; isDispatchThread(): " + isDispatchThread(), "SystemEventQueueThread: " + describe(getEventQueueThread())); } } @NonNls private static String describe(Thread o) { if (o == null) return "null"; return o + " " + System.identityHashCode(o); } private static Thread getEventQueueThread() { EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); return AWTAccessor.getEventQueueAccessor().getDispatchThread(eventQueue); } @Override public boolean isReadAccessAllowed() { if (isDispatchThread()) { return myWriteActionThread == null; // no reading from EDT during background write action } return myLock.isReadLockedByThisThread() || myWriteActionThread == Thread.currentThread(); } @RequiredDispatchThread @Override public void assertIsDispatchThread() { if (isDispatchThread()) return; if (ShutDownTracker.isShutdownHookRunning()) return; assertIsDispatchThread("Access is allowed from event dispatch thread only."); } private void assertIsDispatchThread(@NotNull String message) { if (isDispatchThread()) return; final Attachment dump = new Attachment("threadDump.txt", ThreadDumper.dumpThreadsToString()); throw new LogEventException(message, " EventQueue.isDispatchThread()=" + EventQueue.isDispatchThread() + " isDispatchThread()=" + isDispatchThread() + " Toolkit.getEventQueue()=" + Toolkit.getDefaultToolkit().getSystemEventQueue() + " Current thread: " + describe(Thread.currentThread()) + " SystemEventQueueThread: " + describe(getEventQueueThread()), dump); } @RequiredDispatchThread @Override public void assertIsDispatchThread(@Nullable final JComponent component) { if (component == null) return; if (isDispatchThread()) { return; } if (Boolean.TRUE.equals(component.getClientProperty(WAS_EVER_SHOWN))) { assertIsDispatchThread(); } else { final JRootPane root = component.getRootPane(); if (root != null) { component.putClientProperty(WAS_EVER_SHOWN, Boolean.TRUE); assertIsDispatchThread(); } } } @Override public void assertTimeConsuming() { if (myTestModeFlag || myHeadlessMode || ShutDownTracker.isShutdownHookRunning()) return; LOG.assertTrue(!isDispatchThread(), "This operation is time consuming and must not be called on EDT"); } @Override public boolean tryRunReadAction(@NotNull Runnable action) { //if we are inside read action, do not try to acquire read lock again since it will deadlock if there is a pending writeAction if (isReadAccessAllowed()) { action.run(); } else { assertNoPsiLock(); if (!myLock.tryReadLock()) return false; try { action.run(); } finally { endRead(); } } return true; } @Override public boolean isActive() { if (isUnitTestMode()) return true; Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); if (ApplicationActivationStateManager.getState().isInactive() && activeWindow != null) { ApplicationActivationStateManager.updateState(activeWindow); } return ApplicationActivationStateManager.getState().isActive(); } @NotNull @Override public AccessToken acquireReadActionLock() { // if we are inside read action, do not try to acquire read lock again since it will deadlock if there is a pending writeAction return isReadAccessAllowed() ? AccessToken.EMPTY_ACCESS_TOKEN : new ReadAccessToken(); } private volatile boolean myWriteActionPending; @Override public boolean isWriteActionPending() { return myWriteActionPending; } private void startWrite(@NotNull Class clazz) { if (!isWriteAccessAllowed()) { assertIsDispatchThread("Write access is allowed from event dispatch thread only"); } HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); // let non-cancellable read actions complete faster, if present boolean writeActionPending = myWriteActionPending; if (gatherStatistics && myWriteActionsStack.isEmpty() && !writeActionPending) { ActionPauses.WRITE.started(); } myWriteActionPending = true; try { ActivityTracker.getInstance().inc(); fireBeforeWriteActionStart(clazz); if (!myLock.isWriteLocked()) { assertNoPsiLock(); if (!myLock.tryWriteLock()) { Future<?> reportSlowWrite = ourDumpThreadsOnLongWriteActionWaiting <= 0 ? null : JobScheduler.getScheduler().scheduleWithFixedDelay(() -> PerformanceWatcher.getInstance().dumpThreads("waiting", true), ourDumpThreadsOnLongWriteActionWaiting, ourDumpThreadsOnLongWriteActionWaiting, TimeUnit.MILLISECONDS); myLock.writeLock(); if (reportSlowWrite != null) { reportSlowWrite.cancel(false); } } } } finally { myWriteActionPending = writeActionPending; } myWriteActionsStack.push(clazz); fireWriteActionStarted(clazz); } private void endWrite(Class clazz) { try { fireWriteActionFinished(clazz); // fire listeners before popping stack because if somebody starts write action in a listener, // there is a danger of unlocking the write lock before other listeners have been run (since write lock became non-reentrant). } finally { myWriteActionsStack.pop(); if (gatherStatistics && myWriteActionsStack.isEmpty() && !myWriteActionPending) { ActionPauses.WRITE.finished("write action (" + clazz + ")"); } if (myWriteActionsStack.size() == myWriteStackBase) { myLock.writeUnlock(); } if (myWriteActionsStack.isEmpty()) { fireAfterWriteActionFinished(clazz); } } } @RequiredDispatchThread @NotNull @Override public AccessToken acquireWriteActionLock(@NotNull Class clazz) { return new WriteAccessToken(clazz); } private class WriteAccessToken extends AccessToken { @NotNull private final Class clazz; public WriteAccessToken(@NotNull Class clazz) { this.clazz = clazz; startWrite(clazz); markThreadNameInStackTrace(); } @Override public void finish() { try { endWrite(clazz); } finally { unmarkThreadNameInStackTrace(); } } private void markThreadNameInStackTrace() { String id = id(); if (id != null) { final Thread thread = Thread.currentThread(); thread.setName(thread.getName() + id); } } private void unmarkThreadNameInStackTrace() { String id = id(); if (id != null) { final Thread thread = Thread.currentThread(); String name = thread.getName(); name = StringUtil.replace(name, id, ""); thread.setName(name); } } private String id() { Class aClass = getClass(); String name = aClass.getName(); while (name == null) { aClass = aClass.getSuperclass(); name = aClass.getName(); } name = name.substring(name.lastIndexOf('.') + 1); name = name.substring(name.lastIndexOf('$') + 1); if (!name.equals("AccessToken")) { return " [" + name + "]"; } return null; } } private class ReadAccessToken extends AccessToken { private ReadAccessToken() { startRead(); } @Override public void finish() { endRead(); } } private final boolean myExtraChecks = isUnitTestMode(); private void assertNoPsiLock() { if (myExtraChecks) { LOG.assertTrue(!Thread.holdsLock(PsiLock.LOCK), "Thread must not hold PsiLock while performing readAction"); } } @RequiredWriteAction @Override public void assertWriteAccessAllowed() { LOG.assertTrue(isWriteAccessAllowed(), "Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())"); } @Override public boolean isWriteAccessAllowed() { return isDispatchThread() && myLock.isWriteLocked() || myWriteActionThread == Thread.currentThread(); } // cheaper version of isWriteAccessAllowed(). must be called from EDT private boolean isInsideWriteActionEDTOnly() { return !myWriteActionsStack.isEmpty(); } @Override public boolean isWriteActionInProgress() { return myLock.isWriteLocked(); } public void executeSuspendingWriteAction(@Nullable Project project, @NotNull String title, @NotNull Runnable runnable) { assertIsDispatchThread(); if (!myLock.isWriteLocked()) { runModalProgress(project, title, runnable); return; } myTransactionGuard.submitTransactionAndWait(() -> { int prevBase = myWriteStackBase; myWriteStackBase = myWriteActionsStack.size(); try (AccessToken ignored = myLock.writeSuspend()) { runModalProgress(project, title, () -> { try (AccessToken ignored1 = myLock.grantReadPrivilege()) { runnable.run(); } }); } finally { myWriteStackBase = prevBase; } }); } private static void runModalProgress(@Nullable Project project, @NotNull String title, @NotNull Runnable runnable) { ProgressManager.getInstance().run(new Task.Modal(project, title, false) { @Override public void run(@NotNull ProgressIndicator indicator) { runnable.run(); } }); } public void editorPaintStart() { myInEditorPaintCounter++; } public void editorPaintFinish() { myInEditorPaintCounter--; LOG.assertTrue(myInEditorPaintCounter >= 0); } @Override public void addApplicationListener(@NotNull ApplicationListener l) { myDispatcher.addListener(l); } @Override public void addApplicationListener(@NotNull ApplicationListener l, @NotNull Disposable parent) { myDispatcher.addListener(l, parent); } @Override public void removeApplicationListener(@NotNull ApplicationListener l) { myDispatcher.removeListener(l); } private void fireApplicationExiting() { myDispatcher.getMulticaster().applicationExiting(); } private void fireBeforeWriteActionStart(@NotNull Class action) { myDispatcher.getMulticaster().beforeWriteActionStart(action); } private void fireWriteActionStarted(@NotNull Class action) { myDispatcher.getMulticaster().writeActionStarted(action); } private void fireWriteActionFinished(@NotNull Class action) { myDispatcher.getMulticaster().writeActionFinished(action); } private void fireAfterWriteActionFinished(@NotNull Class action) { myDispatcher.getMulticaster().afterWriteActionFinished(action); } // public for testing purposes public void _saveSettings() { if (mySaveSettingsIsInProgress.compareAndSet(false, true)) { try { StoreUtil.save(getStateStore(), null); } finally { mySaveSettingsIsInProgress.set(false); } } } @Override public void saveSettings() { if (myDoNotSave) return; _saveSettings(); } @Override public void saveAll() { if (myDoNotSave) return; FileDocumentManager.getInstance().saveAllDocuments(); Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); for (Project openProject : openProjects) { ProjectEx project = (ProjectEx)openProject; project.save(); } saveSettings(); } @Override public void doNotSave() { doNotSave(true); } @Override public void doNotSave(boolean value) { myDoNotSave = value; } @Override public boolean isDoNotSave() { return myDoNotSave; } @NotNull @Override public <T> T[] getExtensions(@NotNull final ExtensionPointName<T> extensionPointName) { return Extensions.getRootArea().getExtensionPoint(extensionPointName).getExtensions(); } @Override public boolean isDisposeInProgress() { return myDisposeInProgress || ShutDownTracker.isShutdownHookRunning(); } @Override public boolean isRestartCapable() { return Restarter.isSupported(); } @Override protected boolean logSlowComponents() { return super.logSlowComponents() || ApplicationInfoImpl.getShadowInstance().isEAP(); } @TestOnly public void setDisposeInProgress(boolean disposeInProgress) { myDisposeInProgress = disposeInProgress; } @NonNls @Override public String toString() { return "Application" + (isDisposed() ? " (Disposed)" : "") + (isUnitTestMode() ? " (Unit test)" : "") + (isInternal() ? " (Internal)" : "") + (isHeadlessEnvironment() ? " (Headless)" : "") + (isCommandLine() ? " (Command line)" : ""); } }