From a6ffc3f9a35de8fb58d69a70ccaac9458385e260 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 23 May 2016 09:46:16 +0200 Subject: [PATCH] update TransactionGuard javadoc and cache the instance to avoid "already disposed" exceptions from bg threads after closing the IDE --- .../openapi/application/TransactionGuard.java | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 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 af316ec005cb..a94dfd7adfdc 100644 --- a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java +++ b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java @@ -30,12 +30,22 @@ import org.jetbrains.annotations.Nullable; * and process UI events in other ways: it's guaranteed that no one will be able to sneak in with an unexpected model change using * {@link javax.swing.SwingUtilities#invokeLater(Runnable)} or analogs.

* - * Transactions are run on UI thread. They have read access by default. All write actions that don't happen as the result of direct user input - * should be performed inside a transaction. No write action should be performed from within an {@code invokeLater}-like call - * unless wrapped into a transaction.

+ * Transactions are run on UI thread and have read access. Write actions that modify model are only allowed inside write-safe contexts. These are: + *

+ * + * All other contexts are considered write-unsafe, and model modifications are not allowed from them. + * Even if user activity happens in such context + * (e.g. someone clicks a button in a dialog shown from {@link javax.swing.SwingUtilities#invokeLater(Runnable)}), + * this will result in an assertion. + * Synchronous transactions ({@link #submitTransactionAndWait(Runnable)} are also not allowed from such invokeLater calls. * * The recommended way to perform a transaction is to invoke {@link #submitTransaction(Disposable, Runnable)}. It either runs the transaction immediately - * (if on UI thread and not inside invokeLater) or queues it to invoke at some later moment, when it becomes possible.

+ * (if in write-safe context on UI thread) or queues it to invoke at some later moment, when it becomes possible.

* * Sometimes transactions need to be processed as soon as possible, even if another transaction is already running. Example: * outer transaction has shown a dialog with a modal progress that performs a write action inside (which requires a transaction) @@ -44,37 +54,27 @@ import org.jetbrains.annotations.Nullable; * *

FAQ

* - * Q: How large/long should transactions be? - * A: As short as possible, but not shorter. Take them for minimal period of time that you need the model you're working with - * to be consistent. If your action doesn't display any modal progresses or dialogs, transaction can be omitted. - * If the action only displays a dialog (e.g. Settings) and does nothing else, and that dialog is ready to possible PSI/VFS events, - * there should also be no transaction for all the dialog showing time. Actions inside the dialog should care of transactions themselves. - * If the dialog isn't prepared to any model changes from outside, a transaction around showing the dialog is advised.

+ * Q: When should transactions be used? + * A: Whenever the code inside isn't prepared to model being modified from the outside world. Which is, almost always. AnAction-s + * are wrapped into transactions by default. It only makes sense to opt out (by overriding AnAction#startInTransaction), if your actions + * don't modify the PSI/document/VFS model in any way, and can be invoked in a dialog that's shown from invokeLater. + * Example: editor actions in dialogs like "Enter Password", which doesn't care about model and can be requested to be shown from background threads + * in any modality state. + *

* - * The most complicated case is when the action both displays modal dialogs and performs modifications. The only case when those dialogs - * should be shown under a transaction is when the mere reason of their showing lies somewhere in PSI/VFS/project model, and they are not - * prepared to foreign code affecting the state of things at the moment of showing. For example, dialogs asking for making files writable - * are shown only because VFS indicates the file is read-only. So they wouldn't make sense if they allowed modifications to that file - * while they're shown. Their clients are not prepared to such changes either. So such dialogs should be shown under the same transaction, - * as the following meaningful modifications performed by the action. Most refactoring dialogs are similar and refactoring actions should - * take transactions for the whole refactoring process, with all the dialogs inside.

- * - * But note that some background processes may need occasional transactions, and will therefore be paused until the dialog is closed. - * Therefore, it's still advisable that the transactions be as short as possible and preferably exclude any modal dialogs - * for which transaction-ness is not critical. So a better overall strategy would be to either make the dialogs non-modal, - * or at least make them and the code that shows them prepared for possible model changes while the dialog is shown.

- * - * Dialogs that have per-project modality must never be shown from a transaction, because this would disallow making changes in another - * project: they'd be blocked by the running transaction. - * - * Q: I've got "Write access is allowed from model transactions only" exception, what do I do?
- * A: You're likely inside an "invokeLater"-like call. Please consider replacing it with {@link #submitTransaction(Disposable, Runnable)} or - * {@link #submitTransaction(Disposable, TransactionId, Runnable)} + * Q: I've got "Write access is allowed from model transactions only" + * or "Cannot run synchronous submitTransactionAndWait" exception, what do I do?
+ * A: You're likely inside an "invokeLater"-like call. If this code is showing a dialog that requires model consistency during its lifetime, + * consider replacing invokeLater with {@link #submitTransaction(Disposable, Runnable)} or + * {@link #submitTransaction(Disposable, TransactionId, Runnable)}. + * Otherwise, use {@link Application#invokeLater(Runnable, ModalityState)} + * and pass a modality state that appeared in a write-safe context (e.g. a background progress started in an action). + * Most likely {@link ModalityState#defaultModalityState()} will do. *

* * Q: What's the difference between transactions and read/write actions and commands ({@link com.intellij.openapi.command.CommandProcessor})?
* A: Transactions are more abstract and can contain several write actions and even commands inside. Read/write actions guarantee that no - * one else will modify the model, while transactions allow for some modification, but in a way controlled by transaction kinds. Commands + * one else will modify the model, while transactions allow for some modification, but in a controlled way. Commands * are used for tracking document changes for undo/redo functionality, so they're orthogonal to transactions. * * @see Application#runReadAction(Runnable) @@ -83,9 +83,14 @@ import org.jetbrains.annotations.Nullable; * @author peter */ public abstract class TransactionGuard { + private static volatile TransactionGuard ourInstance; public static TransactionGuard getInstance() { - return ServiceManager.getService(TransactionGuard.class); + TransactionGuard instance = ourInstance; + if (instance == null) { + ourInstance = instance = ServiceManager.getService(TransactionGuard.class); + } + return instance; } /**