/*
* Copyright 2000-2016 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;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A service managing model transactions.<p/>
*
* A transaction ensures that IntelliJ model (PSI, documents, VFS, project roots etc.) isn't modified in an unexpected way
* while working with it, with either read or write access. The main property of transactions is mutual exclusion: at most one transaction
* can be running at any given time. The code inside transaction can perform read or write actions and, more importantly, show dialogs
* 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.<p/>
*
* Transactions are run on UI thread and have read access. Write actions that modify model are only allowed inside write-safe contexts. These are:
* <ul>
* <li>Transactions</li>
* <li>Direct user activity processing (key/mouse presses, actions)</li>
* <li>{@link Application#invokeLater(Runnable, ModalityState)} calls with a modality state that's either non-modal
* or was started inside a write-safe context (for example, a dialog shown from an action wrapped into a transaction)</li>
* </ul>
*
* 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 in write-safe context on UI thread) or queues it to invoke at some later moment, when it becomes possible.<p/>
*
* 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)
* and waits for it to be finished. For such cases, a context transaction id
* should be supplied to the nested transaction via {@link #submitTransaction(Disposable, TransactionId, Runnable)}.
*
* <p><h1>FAQ</h1></p>
*
* 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. Well known base AnAction
* classes that work with PSI 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.
* <p/>
*
* Q: I've got <b>"Write access is allowed from model transactions only"</b>
* or <b>"Cannot run synchronous submitTransactionAndWait"</b> exception, what do I do?<br/>
* A: You're likely inside an "invokeLater" or "invokeAndWait"-like call.
* <ul>
* <li/> 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)}.<br/>
* <li/> Consider simplifying the code. For example, if the outer code is known to be run on EDT, "invokeLater(AndWait)IfNeeded" call can be removed. If it's in a pooled thread, the "IfNeeded" part can be removed. "invokeAndWaitIfNeeded" can be safely unwrapped, if already on EDT.
* <li/> Consider making the code synchronous. "invokeLater" from EDT rarely makes sense and can often be replaced with synchronous execution (which will likely be inside a user activity: a write-safe context).
* <li/> If you get the assertion from synchronous document commit or VFS refresh, consider making them asynchronous. For documents, use {@link com.intellij.psi.PsiDocumentManager#performLaterWhenAllCommitted(Runnable)}.
* You can also try to get rid of them at all, by making your code work only with VFS and PSI and assuming
* they're refreshed/committed often enough by some other code.
* <li/> If you still have "invokeAndWaitIfNeeded" call with model-changing activity inside, replace it with {@link #submitTransactionAndWait(Runnable)} or {@link Application#invokeAndWait}.
* You might still get assertions after that, that would mean that some caller down the stack is also using "invokeLater"-like call and needs to be fixed as well.
* <li/> If you still absolutely need "invokeLater" use {@link Application#invokeLater} (or GuiUtils for "invokeLaterIfNeeded"),
* 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.
* Don't forget to check inside the "later" runnable that it's still actual, that the model haven't been changed by someone else since its scheduling.
* </ul>
* <p/>
*
* Q: What's the difference between transactions and read/write actions and commands ({@link com.intellij.openapi.command.CommandProcessor})?<br/>
* A: Read actions in background threads ensure that no one will modify the model while you're working with it.<br/>
* Write actions (in Swing thread only) allow to modify the model, but ensure that no background thread is reading it in the meantime.<br/>
* Read action in Swing thread is special: it allows to perform a write action inside. This can lead to troubles
* if some foreign invokeLater call modifies the model while you're showing a modal progress that's not supposed to change anything.
* Transactions (in Swing thread only) allow for interactive model processing with read and write actions inside,
* with modal dialogs and progresses, with a guarantee that only you may modify the model, and not random invokeLater's.<br/>
* Commands are used for tracking document changes for undo/redo functionality, so they're orthogonal to transactions.
*
* @see Application#runReadAction(Runnable)
* @see Application#runWriteAction(Runnable)
* @since 2016.2
* @author peter
*/
public abstract class TransactionGuard {
private static volatile TransactionGuard ourInstance;
public static TransactionGuard getInstance() {
TransactionGuard instance = ourInstance;
if (instance == null) {
ourInstance = instance = ServiceManager.getService(TransactionGuard.class);
}
return instance;
}
/**
* Ensures that some code will be run in a transaction. It's guaranteed that no other transactions can run at the same time,
* except for the ones started from within this runnable. The code will be run on Swing thread immediately
* or after other queued transactions (if any) have been completed.<p/>
*
* For more advanced version, see {@link #submitTransaction(Disposable, TransactionId, Runnable)}.
*
* @param parentDisposable an object whose disposing (via {@link com.intellij.openapi.util.Disposer} makes this transaction invalid,
* and so it won't be run after it has been disposed
* @param transaction code to execute inside a transaction.
*/
public static void submitTransaction(@NotNull Disposable parentDisposable, @NotNull Runnable transaction) {
TransactionGuard guard = getInstance();
guard.submitTransaction(parentDisposable, guard.getContextTransaction(), transaction);
}
/**
* Logs an error if the given modality state was created in a write-unsafe context. For modalities created in write-safe contexts,
* {@link Application#invokeLater(Runnable, ModalityState)} and similar calls will be guaranteed to also run in a write-safe context.
* {@link ModalityState#NON_MODAL} is always write-safe, {@link ModalityState#any()} is always write-unsafe.
*/
public abstract void assertWriteSafeContext(@NotNull ModalityState modality);
/**
* Schedules a given runnable to be executed inside a transaction later on Swing thread.
* Same as {@link #submitTransaction(Disposable, Runnable)}, but the runnable is never executed immediately.
*/
public abstract void submitTransactionLater(@NotNull Disposable parentDisposable, @NotNull Runnable transaction);
/**
* Schedules a transaction and waits for it to be completed. Logs an error if invoked on UI thread inside an incompatible transaction,
* throws {@link IllegalStateException} inside a read action on non-UI thread.
* @see #submitTransaction(Disposable, TransactionId, Runnable)
* @throws ProcessCanceledException if current thread is interrupted
*/
public abstract void submitTransactionAndWait(@NotNull Runnable transaction) throws ProcessCanceledException;
/**
* Executes the given runnable inside a transaction as soon as possible on the UI thread. The runnable is executed either when there's
* no active transaction running, or when the running transaction has the same (or compatible) id as {@code expectedContext}. If the id of
* the current transaction is passed, the transaction is executed immediately. Otherwise adds the runnable to a queue,
* to execute after all transactions scheduled before this one are finished.
* @param parentDisposable an object whose disposing (via {@link com.intellij.openapi.util.Disposer} makes this transaction invalid,
* and so it won't be run after it has been disposed.
* @param expectedContext an optional id of another transaction, to allow execution inside that transaction if it's still running
* @param transaction code to execute inside a transaction.
* @see #getContextTransaction()
*/
public abstract void submitTransaction(@NotNull Disposable parentDisposable, @Nullable TransactionId expectedContext, @NotNull Runnable transaction);
/**
* @return the id of the currently running transaction for using in {@link #submitTransaction(Disposable, TransactionId, Runnable)},
* or null if there's no transaction running or transaction nesting is not allowed in the callee context (e.g. from invokeLater).
*/
public abstract TransactionId getContextTransaction();
}