From 26aed8eba5c74f4e8ad30da823221e23dfc8f430 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 10 Mar 2016 14:37:30 +0100 Subject: [PATCH] add transactions for startup & exit --- .../openapi/application/TransactionGuard.java | 13 ++++++++ .../module/impl/ModuleManagerComponent.java | 22 +++---------- .../FileBasedIndexProjectHandler.java | 14 +++------ .../application/impl/ApplicationImpl.java | 16 +++++----- .../project/impl/ProjectManagerImpl.java | 31 +++++++------------ 5 files changed, 42 insertions(+), 54 deletions(-) diff --git a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java index 423684a31408..adf405fc0b0f 100644 --- a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java +++ b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java @@ -97,6 +97,19 @@ public abstract class TransactionGuard { getInstance().submitMergeableTransaction(TransactionKind.NO_MERGE, transaction); } + /** + * Runs the given code synchronously inside a transaction. Fails if transactions of given kind are not allowed at this moment. + * @see #startSynchronousTransaction(TransactionKind) + */ + public static void syncTransaction(@NotNull TransactionKind kind, @NotNull Runnable transaction) { + AccessToken token = getInstance().startSynchronousTransaction(kind); + try { + transaction.run(); + } finally { + token.finish(); + } + } + /** * Schedules a transaction and waits for it to be completed. Fails if invoked on UI thread inside an incompatible transaction, * or inside a read action on non-UI thread. diff --git a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java index 29bc57a56508..be5f57eb7291 100644 --- a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java +++ b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java @@ -19,8 +19,7 @@ import com.intellij.ProjectTopics; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.*; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; @@ -126,26 +125,15 @@ public class ModuleManagerComponent extends ModuleManagerImpl { return; } - Runnable runnableWithProgress = new Runnable() { - @Override - public void run() { - for (final Module module : myModuleModel.myModules.values()) { - final Application app = ApplicationManager.getApplication(); - final Runnable swingRunnable = new Runnable() { - @Override - public void run() { - fireModuleAddedInWriteAction(module); - } - }; - ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator(); - app.invokeAndWait(swingRunnable, pi.getModalityState()); - } + Runnable runnableWithProgress = () -> { + for (final Module module : myModuleModel.myModules.values()) { + TransactionGuard.getInstance().submitTransactionAndWait(TransactionKind.ANY_CHANGE, () -> fireModuleAddedInWriteAction(module)); } }; ProgressIndicator progressIndicator = myProgressManager.getProgressIndicator(); if (progressIndicator == null) { - myProgressManager.runProcessWithProgressSynchronously(runnableWithProgress, "Initializing modules...", false, myProject); + myProgressManager.runProcessWithProgressSynchronously(runnableWithProgress, "Initializing Modules...", false, myProject); } else { runnableWithProgress.run(); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java index 7ae45e2d7b05..10ba085f3e92 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java @@ -23,6 +23,7 @@ import com.intellij.ide.IdeBundle; import com.intellij.ide.caches.FileContent; import com.intellij.ide.startup.StartupManagerEx; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileTypeManager; @@ -80,15 +81,10 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple public void run() { PushedFilePropertiesUpdater.getInstance(project).initializeProperties(); - // dumb mode should start before post-startup activities - // only when queueTask is called from UI thread, we can guarantee that - // when the method returns, the application has entered dumb mode - UIUtil.invokeLaterIfNeeded(new Runnable() { - @Override - public void run() { - if (!project.isDisposed() && FileBasedIndex.getInstance() instanceof FileBasedIndexImpl) { - DumbService.getInstance(project).queueTask(new UnindexedFilesUpdater(project, true)); - } + // schedule dumb mode start after the read action we're currently in + TransactionGuard.submitTransaction(() -> { + if (!project.isDisposed() && FileBasedIndex.getInstance() instanceof FileBasedIndexImpl) { + DumbService.getInstance(project).queueTask(new UnindexedFilesUpdater(project, true)); } }); 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 1f9f0fcd1ded..235164eef87a 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 @@ -308,12 +308,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App } } } - runWriteAction(new Runnable() { - @Override - public void run() { - Disposer.dispose(ApplicationImpl.this); - } - }); + TransactionGuard.syncTransaction(TransactionKind.ANY_CHANGE, () -> runWriteAction(() -> Disposer.dispose(this))); Disposer.assertIsEmpty(); return true; @@ -376,6 +371,11 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App @Override public Future executeOnPooledThread(@NotNull final Runnable action) { return ourThreadExecutorsService.submit(new Runnable() { + @Override + public String toString() { + return action.toString(); + } + @Override public void run() { assert !isReadAccessAllowed(): describe(Thread.currentThread()); @@ -864,7 +864,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App } private boolean doExit(boolean allowListenersToCancel, boolean restart) { - saveSettings(); + TransactionGuard.syncTransaction(TransactionKind.ANY_CHANGE, this::saveSettings); if (allowListenersToCancel && !canExit()) { return false; @@ -1230,8 +1230,8 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App assertIsDispatchThread(getStatus(), "Write access is allowed from event dispatch thread only"); HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); // let non-cancellable read actions complete faster, if present if (!TransactionGuard.getInstance().isInsideTransaction() && Registry.is("ide.require.transaction.for.model.changes", false)) { + // please assign exceptions that occur here to Peter LOG.error("Write access is allowed from model transactions only, see TransactionGuard documentation for details"); - //todo throw new IllegalStateException("Write access is allowed from model transactions only, see TransactionGuard documentation for details"); } boolean writeActionPending = myWriteActionPending; myWriteActionPending = true; diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java index 03fd13d40ca8..75102e106d30 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java @@ -28,10 +28,7 @@ import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.notification.NotificationsManager; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.*; import com.intellij.openapi.components.impl.stores.StorageUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -351,9 +348,8 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable { } fireProjectOpened(project); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() { - @Override - public void run() { + try (AccessToken ignored = TransactionGuard.getInstance().startSynchronousTransaction(TransactionKind.ANY_CHANGE)) { + DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> DumbService.getInstance(project).queueTask(new DumbModeTask() { @Override public void performInDumbMode(@NotNull ProgressIndicator indicator) { @@ -364,9 +360,9 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable { public String toString() { return "wait for file watcher"; } - }); - } - }); + }) + ); + } final StartupManagerImpl startupManager = (StartupManagerImpl)StartupManager.getInstance(project); boolean ok = myProgressManager.runProcessWithProgressSynchronously(new Runnable() { @@ -374,15 +370,10 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable { public void run() { startupManager.runStartupActivities(); - // dumb mode should start before post-startup activities - // only when startCacheUpdate is called from UI thread, we can guarantee that - // when the method returns, the application has entered dumb mode - UIUtil.invokeAndWaitIfNeeded(new Runnable() { - @Override - public void run() { - startupManager.startCacheUpdate(); - } - }); + // Startup activities (e.g. the one in FileBasedIndexProjectHandler) have scheduled dumb mode to begin "later" + // Now we schedule-and-wait to the same event queue to guarantee that the dumb mode really begins now: + // Post-startup activities should not ever see unindexed and at the same time non-dumb state + TransactionGuard.getInstance().submitTransactionAndWait(TransactionKind.ANY_CHANGE, startupManager::startCacheUpdate); startupManager.runPostStartupActivitiesFromExtensions(); @@ -615,7 +606,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable { if (checkCanClose && !canClose(project)) return false; final ShutDownTracker shutDownTracker = ShutDownTracker.getInstance(); shutDownTracker.registerStopperThread(Thread.currentThread()); - try { + try (AccessToken ignored = TransactionGuard.getInstance().startSynchronousTransaction(TransactionKind.NO_MERGE)) { if (save) { FileDocumentManager.getInstance().saveAllDocuments(); project.save();