mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
allow to merge nested transactions by id
This commit is contained in:
@@ -18,9 +18,8 @@ package com.intellij.openapi.application;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A service managing model transactions.<p/>
|
||||
@@ -35,8 +34,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
* should be performed inside a transaction. No write action should be performed from within an {@code invokeLater}-like call
|
||||
* unless wrapped into a transaction.<p/>
|
||||
*
|
||||
* The recommended way to perform a transaction is to invoke {@link #submitTransaction(Runnable)}. It either runs the transaction immediately
|
||||
* (if on UI thread and there's no other transaction running) or queues it to invoke at some later moment, when it becomes possible.<p/>
|
||||
* 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.<p/>
|
||||
*
|
||||
* Sometimes transactions need to be processed immediately, even if another transaction is already running. Example:
|
||||
* outer transaction has shown a dialog with an editor, and typing into that editor (which requires a transaction for changing document)
|
||||
@@ -71,21 +70,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
*
|
||||
* Q: I've got <b>"Write access is allowed from model transactions only"</b> exception, what do I do?<br/>
|
||||
* A: You're likely inside an "invokeLater"-like call. Please consider replacing it with {@link #submitTransaction(Disposable, Runnable)} or
|
||||
* {@link #submitMergeableTransaction(Disposable, TransactionKind, Runnable)}
|
||||
* <p/>
|
||||
*
|
||||
* Q: I've got <b>"Nested transactions are not allowed"</b> exception, what do I do?<br/>
|
||||
* A: First, identify the place in the stack where the outer transaction is started (the exception attachment should contain it).
|
||||
* Then, see if there is any Swing event pumping
|
||||
* in between two transactions (e.g. a dialog is shown). If not, one of the two involved transactions is superfluous, remove it. If there
|
||||
* is event pumping present, check if the client code (e.g. the one showing the dialog) is prepared to such nested model modifications.
|
||||
* For example, refactoring dialogs might be prepared to {@link TransactionKind#TEXT_EDITING} kind
|
||||
* (for text field editing inside the dialogs) but not to
|
||||
* other model changes, e.g. root changes. The outer transaction code might then specify which kinds it's prepared to (by using
|
||||
* {@link #acceptNestedTransactions(TransactionKind...)}), and the inner transaction code should have the very same transaction kind
|
||||
* (by using {@link #submitMergeableTransaction(Disposable, TransactionKind, Runnable)} or {@link #startSynchronousTransaction(TransactionKind)}).
|
||||
* If the nested transaction is not expected by the outer code, it must be made asynchronous by using either {@link #submitTransaction(Disposable, Runnable)}
|
||||
* or {@link #submitMergeableTransaction(Disposable, TransactionKind, Runnable)}.
|
||||
* {@link #submitMergeableTransaction(Disposable, TransactionId, Runnable)}
|
||||
* <p/>
|
||||
*
|
||||
* Q: What's the difference between transactions and read/write actions and commands ({@link com.intellij.openapi.command.CommandProcessor})?<br/>
|
||||
@@ -95,7 +80,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
*
|
||||
* @see Application#runReadAction(Runnable)
|
||||
* @see Application#runWriteAction(Runnable)
|
||||
* @since 146.*
|
||||
* @since 2016.2
|
||||
* @author peter
|
||||
*/
|
||||
public abstract class TransactionGuard {
|
||||
@@ -112,11 +97,11 @@ public abstract class TransactionGuard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that some code will be run in a transaction. It's guaranteed that no other transactions are run at the same time.
|
||||
* The code will be run on Swing thread immediately or after all other queued transactions (if any) have been completed.<p/>
|
||||
* 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 #submitMergeableTransaction(Disposable, TransactionKind, Runnable)}
|
||||
* Transactions submitted via this method use {@link TransactionKind#ANY_CHANGE} kind.
|
||||
* For more advanced version, see {@link #submitMergeableTransaction(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
|
||||
@@ -153,20 +138,6 @@ public abstract class TransactionGuard {
|
||||
*/
|
||||
public abstract void submitTransactionAndWait(@NotNull TransactionKind kind, @NotNull Runnable transaction) throws ProcessCanceledException;
|
||||
|
||||
/**
|
||||
* Same as {@link #submitTransactionAndWait(TransactionKind, Runnable)}, but returns a value computed by the transaction.
|
||||
*/
|
||||
public <T> T submitTransactionAndWait(@NotNull TransactionKind kind, @NotNull final Computable<T> transaction) throws ProcessCanceledException {
|
||||
final Ref<T> result = Ref.create();
|
||||
submitTransactionAndWait(kind, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
result.set(transaction.compute());
|
||||
}
|
||||
});
|
||||
return result.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* A synchronous version of {@link #submitMergeableTransaction(Disposable, TransactionKind, Runnable)}.
|
||||
* @return a token object for this transaction. Call {@link AccessToken#finish()} (inside finally) when the transaction is complete.
|
||||
@@ -194,6 +165,19 @@ public abstract class TransactionGuard {
|
||||
*/
|
||||
public abstract void submitMergeableTransaction(@NotNull Disposable parentDisposable, @NotNull TransactionKind kind, @NotNull Runnable transaction);
|
||||
|
||||
/**
|
||||
* 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 mergeInto}. 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 mergeInto 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 #getCurrentMergeableTransaction()
|
||||
*/
|
||||
public abstract void submitMergeableTransaction(@NotNull Disposable parentDisposable, @Nullable TransactionId mergeInto, @NotNull Runnable transaction);
|
||||
|
||||
/**
|
||||
* Allow incoming transactions of the specified kinds to be executed immediately, instead of being queued until the current transaction is finished.<p/>
|
||||
*
|
||||
@@ -215,4 +199,9 @@ public abstract class TransactionGuard {
|
||||
*/
|
||||
public abstract void assertInsideTransaction(boolean transactionRequired, @NotNull String errorMessage);
|
||||
|
||||
/**
|
||||
* @return the id of the currently running transaction for using in {@link #submitMergeableTransaction(Disposable, TransactionId, Runnable)},
|
||||
* or null if there's no transaction running or merging is not allowed in the callee context (e.g. from invokeLater).
|
||||
*/
|
||||
public abstract TransactionId getCurrentMergeableTransaction();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.intellij.openapi.application;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
|
||||
/**
|
||||
* A unique object identifying each running transaction. Can be retrieved from {@link TransactionGuard#getCurrentMergeableTransaction()}
|
||||
* while inside a transaction, and used to merge other transactions into it via
|
||||
* {@link TransactionGuard#submitMergeableTransaction(Disposable, TransactionId, Runnable)}.
|
||||
*
|
||||
* @author peter
|
||||
* @since 2016.2
|
||||
*/
|
||||
public interface TransactionId {
|
||||
}
|
||||
+137
-66
@@ -16,91 +16,99 @@
|
||||
package com.intellij.openapi.application;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.diagnostic.Attachment;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.impl.DebugUtil;
|
||||
import com.intellij.util.concurrency.Semaphore;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class TransactionGuardImpl extends TransactionGuard {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.application.TransactionGuardImpl");
|
||||
private final Queue<Runnable> myQueue = new LinkedBlockingQueue<Runnable>();
|
||||
private final Queue<Transaction> myQueue = new LinkedBlockingQueue<Transaction>();
|
||||
private final Set<TransactionKind> myMergeableKinds = ContainerUtil.newHashSet();
|
||||
private String myTransactionStartTrace;
|
||||
private ModalityState myTransactionModality;
|
||||
private boolean myUserActivity;
|
||||
private final Map<ProgressIndicator, TransactionIdImpl> myProgresses = ContainerUtil.createConcurrentWeakMap();
|
||||
private TransactionIdImpl myCurrentTransaction;
|
||||
private boolean myWritingAllowed;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public AccessToken startSynchronousTransaction(@NotNull TransactionKind kind) throws IllegalStateException {
|
||||
ModalityState modality = ModalityState.current();
|
||||
if (isInsideTransaction()) {
|
||||
if (modality.equals(myTransactionModality) || myUserActivity) {
|
||||
return AccessToken.EMPTY_ACCESS_TOKEN;
|
||||
}
|
||||
|
||||
if (myMergeableKinds.contains(kind)) {
|
||||
final ModalityState prev = myTransactionModality;
|
||||
myTransactionModality = modality;
|
||||
return new AccessToken() {
|
||||
@Override
|
||||
public void finish() {
|
||||
myTransactionModality = prev;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (isInsideTransaction() && !myWritingAllowed && !myMergeableKinds.contains(kind)) {
|
||||
// please assign exceptions that occur here to Peter
|
||||
LOG.error("Nested transactions are not allowed, see FAQ in TransactionGuard class javadoc. Transaction start trace is in attachment. Kind is " + kind,
|
||||
new Attachment("trace.txt", myTransactionStartTrace));
|
||||
LOG.error("Synchronous transactions are allowed only from user actions. " +
|
||||
"Please use submit*Transaction instead of invokeLater. " +
|
||||
"See FAQ in TransactionGuard class javadoc.");
|
||||
return AccessToken.EMPTY_ACCESS_TOKEN;
|
||||
}
|
||||
myTransactionModality = modality;
|
||||
myTransactionStartTrace = DebugUtil.currentStackTrace();
|
||||
|
||||
return startTransactionUnchecked();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AccessToken startTransactionUnchecked() {
|
||||
final TransactionIdImpl prevTransaction = myCurrentTransaction;
|
||||
final boolean wasWritingAllowed = myWritingAllowed;
|
||||
|
||||
myWritingAllowed = true;
|
||||
myCurrentTransaction = new TransactionIdImpl();
|
||||
|
||||
return new AccessToken() {
|
||||
@Override
|
||||
public void finish() {
|
||||
myTransactionStartTrace = null;
|
||||
myTransactionModality = null;
|
||||
if (!myQueue.isEmpty()) {
|
||||
Queue<Transaction> queue = getQueue(prevTransaction);
|
||||
queue.addAll(myCurrentTransaction.myQueue);
|
||||
if (!queue.isEmpty()) {
|
||||
pollQueueLater();
|
||||
}
|
||||
|
||||
myWritingAllowed = wasWritingAllowed;
|
||||
myCurrentTransaction = prevTransaction;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Queue<Transaction> getQueue(@Nullable TransactionIdImpl prevTransaction) {
|
||||
return prevTransaction == null ? myQueue : prevTransaction.myQueue;
|
||||
}
|
||||
|
||||
private void pollQueueLater() {
|
||||
//todo replace with SwingUtilities when write actions are required to run under a guard
|
||||
final Application app = ApplicationManager.getApplication();
|
||||
app.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isInsideTransaction()) return;
|
||||
|
||||
Runnable next = myQueue.poll();
|
||||
if (next != null) {
|
||||
runSyncTransaction(TransactionKind.ANY_CHANGE, next);
|
||||
Queue<Transaction> queue = getQueue(myCurrentTransaction);
|
||||
Transaction next = queue.peek();
|
||||
if (next != null && canRunTransactionNow(next, false)) {
|
||||
queue.remove();
|
||||
runSyncTransaction(next);
|
||||
}
|
||||
}
|
||||
}, app.getDisposed());
|
||||
}
|
||||
|
||||
private void runSyncTransaction(@NotNull TransactionKind kind, @NotNull Runnable code) {
|
||||
AccessToken token = startSynchronousTransaction(kind);
|
||||
private void runSyncTransaction(@NotNull Transaction transaction) {
|
||||
AccessToken token = startTransactionUnchecked();
|
||||
try {
|
||||
code.run();
|
||||
if (!Disposer.isDisposed(transaction.parentDisposable)) {
|
||||
transaction.runnable.run();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
token.finish();
|
||||
@@ -109,34 +117,40 @@ public class TransactionGuardImpl extends TransactionGuard {
|
||||
|
||||
private boolean isInsideTransaction() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
return myTransactionStartTrace != null;
|
||||
return myCurrentTransaction != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitMergeableTransaction(@NotNull final Disposable parentDisposable, @NotNull final TransactionKind kind, @NotNull final Runnable _transaction) {
|
||||
@NotNull final Runnable transaction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!Disposer.isDisposed(parentDisposable)) {
|
||||
_transaction.run();
|
||||
}
|
||||
}
|
||||
};
|
||||
submitMergeableTransaction(parentDisposable, kind, getCurrentMergeableTransaction(), _transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitMergeableTransaction(@NotNull Disposable parentDisposable, @Nullable TransactionId mergeInto, @NotNull Runnable transaction) {
|
||||
submitMergeableTransaction(parentDisposable, TransactionKind.ANY_CHANGE, (TransactionIdImpl)mergeInto, transaction);
|
||||
}
|
||||
|
||||
private void submitMergeableTransaction(@NotNull final Disposable parentDisposable,
|
||||
@NotNull final TransactionKind kind,
|
||||
@Nullable final TransactionIdImpl expectedId,
|
||||
@NotNull final Runnable _transaction) {
|
||||
@NotNull final Transaction transaction = new Transaction(_transaction, expectedId, kind, parentDisposable);
|
||||
final Application app = ApplicationManager.getApplication();
|
||||
final boolean isDispatchThread = app.isDispatchThread();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canRunTransactionNow(kind)) {
|
||||
runSyncTransaction(kind, transaction);
|
||||
if (canRunTransactionNow(transaction, isDispatchThread)) {
|
||||
runSyncTransaction(transaction);
|
||||
}
|
||||
else {
|
||||
myQueue.offer(transaction);
|
||||
getQueue(expectedId).offer(transaction);
|
||||
pollQueueLater();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Application app = ApplicationManager.getApplication();
|
||||
if (app.isDispatchThread()) {
|
||||
if (isDispatchThread) {
|
||||
runnable.run();
|
||||
} else {
|
||||
//todo add ModalityState.any() when write actions are required to run under a guard
|
||||
@@ -144,8 +158,17 @@ public class TransactionGuardImpl extends TransactionGuard {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean canRunTransactionNow(@NotNull TransactionKind kind) {
|
||||
return !isInsideTransaction() || myMergeableKinds.contains(kind) || ModalityState.current().equals(myTransactionModality);
|
||||
private boolean canRunTransactionNow(Transaction transaction, boolean sync) {
|
||||
TransactionIdImpl currentId = myCurrentTransaction;
|
||||
if (currentId == null || myMergeableKinds.contains(transaction.kind)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sync && !myWritingAllowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return transaction.mergeInto != null && currentId.myStartCounter <= transaction.mergeInto.myStartCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,13 +202,16 @@ public class TransactionGuardImpl extends TransactionGuard {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitTransactionAndWait(@NotNull TransactionKind kind, @NotNull final Runnable transaction) throws ProcessCanceledException {
|
||||
public void submitTransactionAndWait(@NotNull TransactionKind kind, @NotNull final Runnable runnable) throws ProcessCanceledException {
|
||||
Application app = ApplicationManager.getApplication();
|
||||
if (app.isDispatchThread()) {
|
||||
if (!canRunTransactionNow(kind)) {
|
||||
throw new AssertionError("Cannot run submitTransactionAndWait from another transaction, kind " + kind + " is not allowed");
|
||||
Transaction transaction = new Transaction(runnable, getCurrentMergeableTransaction(), kind, app);
|
||||
if (!canRunTransactionNow(transaction, true)) {
|
||||
throw new AssertionError("Cannot run synchronous submitTransactionAndWait from invokeLater. " +
|
||||
"Please use asynchronous submit*Transaction. " +
|
||||
"See TransactionGuard FAQ for details.");
|
||||
}
|
||||
runSyncTransaction(kind, transaction);
|
||||
runSyncTransaction(transaction);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,7 +223,7 @@ public class TransactionGuardImpl extends TransactionGuard {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
transaction.run();
|
||||
runnable.run();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
exception[0] = e;
|
||||
@@ -224,7 +250,7 @@ public class TransactionGuardImpl extends TransactionGuard {
|
||||
* please consider using {@code ActionManager.tryToExecute()} instead, or ensure in some other way that the action is enabled
|
||||
* and can be invoked in the current modality state.
|
||||
*/
|
||||
public <T extends Throwable> void performUserActivity(Runnable activity) {
|
||||
public void performUserActivity(Runnable activity) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
AccessToken token = startActivity(true);
|
||||
try {
|
||||
@@ -240,32 +266,77 @@ public class TransactionGuardImpl extends TransactionGuard {
|
||||
*/
|
||||
@NotNull
|
||||
public AccessToken startActivity(boolean userActivity) {
|
||||
if (myUserActivity == userActivity) {
|
||||
if (myWritingAllowed == userActivity) {
|
||||
return AccessToken.EMPTY_ACCESS_TOKEN;
|
||||
}
|
||||
|
||||
final boolean prev = myUserActivity;
|
||||
myUserActivity = userActivity;
|
||||
final boolean prev = myWritingAllowed;
|
||||
myWritingAllowed = userActivity;
|
||||
return new AccessToken() {
|
||||
@Override
|
||||
public void finish() {
|
||||
myUserActivity = prev;
|
||||
myWritingAllowed = prev;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isWriteActionAllowed() {
|
||||
return !Registry.is("ide.require.transaction.for.model.changes", false) || isInsideTransaction() || myUserActivity;
|
||||
return !Registry.is("ide.require.transaction.for.model.changes", false) || isInsideTransaction() || myWritingAllowed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitTransactionLater(@NotNull final Disposable parentDisposable, @NotNull final Runnable transaction) {
|
||||
final TransactionIdImpl id = getCurrentMergeableTransaction();
|
||||
Application app = ApplicationManager.getApplication();
|
||||
app.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
submitMergeableTransaction(parentDisposable, TransactionKind.ANY_CHANGE, transaction);
|
||||
submitMergeableTransaction(parentDisposable, id, transaction);
|
||||
}
|
||||
}, app.getDisposed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionIdImpl getCurrentMergeableTransaction() {
|
||||
if (!ApplicationManager.getApplication().isDispatchThread()) {
|
||||
ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
|
||||
return indicator != null ? myProgresses.get(indicator) : null;
|
||||
}
|
||||
|
||||
return myWritingAllowed ? myCurrentTransaction : null;
|
||||
}
|
||||
|
||||
public void registerProgress(@NotNull ProgressIndicator indicator, @Nullable TransactionIdImpl contextTransaction) {
|
||||
if (contextTransaction != null) {
|
||||
myProgresses.put(indicator, contextTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Transaction {
|
||||
@NotNull final Runnable runnable;
|
||||
@Nullable final TransactionIdImpl mergeInto;
|
||||
@NotNull final TransactionKind kind;
|
||||
@NotNull final Disposable parentDisposable;
|
||||
|
||||
Transaction(@NotNull Runnable runnable,
|
||||
@Nullable TransactionIdImpl mergeInto,
|
||||
@NotNull TransactionKind kind,
|
||||
@NotNull Disposable parentDisposable) {
|
||||
this.runnable = runnable;
|
||||
this.mergeInto = mergeInto;
|
||||
this.kind = kind;
|
||||
this.parentDisposable = parentDisposable;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TransactionIdImpl implements TransactionId {
|
||||
private static final AtomicLong ourTransactionCounter = new AtomicLong();
|
||||
final long myStartCounter = ourTransactionCounter.getAndIncrement();
|
||||
final Queue<Transaction> myQueue = new LinkedBlockingQueue<Transaction>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Transaction " + myStartCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.intellij.openapi.progress.util;
|
||||
import com.intellij.ide.IdeEventQueue;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.TransactionGuard;
|
||||
import com.intellij.openapi.application.TransactionGuardImpl;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
@@ -93,6 +95,9 @@ public class ProgressWindow extends ProgressIndicatorBase implements BlockingPro
|
||||
myShouldShowCancel = shouldShowCancel;
|
||||
myCancelText = cancelText;
|
||||
setModalityProgress(shouldShowBackground ? null : this);
|
||||
TransactionGuardImpl guard = (TransactionGuardImpl)TransactionGuard.getInstance();
|
||||
guard.registerProgress(this, guard.getCurrentMergeableTransaction());
|
||||
|
||||
myFocusTrackback = new FocusTrackback(this, WindowManager.getInstance().suggestParentWindow(project), false);
|
||||
|
||||
Component parent = parentComponent;
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.intellij.openapi.application.Application
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.TransactionGuard
|
||||
import com.intellij.openapi.application.TransactionGuardImpl
|
||||
import com.intellij.openapi.application.TransactionId
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
@@ -15,6 +17,8 @@ import javax.swing.*
|
||||
* @author peter
|
||||
*/
|
||||
class TransactionTest extends LightPlatformTestCase {
|
||||
List<String> log = []
|
||||
|
||||
static TransactionGuardImpl getGuard() {
|
||||
return TransactionGuard.getInstance() as TransactionGuardImpl
|
||||
}
|
||||
@@ -33,6 +37,7 @@ class TransactionTest extends LightPlatformTestCase {
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
Registry.get("ide.require.transaction.for.model.changes").resetToDefault()
|
||||
log.clear()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
@@ -54,7 +59,6 @@ class TransactionTest extends LightPlatformTestCase {
|
||||
|
||||
public void "test parent disposable"() {
|
||||
def parent = Disposer.newDisposable()
|
||||
def log = []
|
||||
|
||||
SwingUtilities.invokeLater { TransactionGuard.submitTransaction parent, { log << "1" } }
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
@@ -65,4 +69,95 @@ class TransactionTest extends LightPlatformTestCase {
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
assert log == ['1']
|
||||
}
|
||||
|
||||
public void "test no current id inside invokeLater"() {
|
||||
SwingUtilities.invokeLater {
|
||||
log << '2'
|
||||
assert !guard.currentMergeableTransaction
|
||||
}
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '1'
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
}
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
|
||||
|
||||
public void "test has id inside nested transaction"() {
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '1'
|
||||
def id = guard.currentMergeableTransaction
|
||||
assert id
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '2'
|
||||
assert guard.currentMergeableTransaction
|
||||
assert id != guard.currentMergeableTransaction
|
||||
}
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
|
||||
public void "test modal progress started from inside a transaction has the same id"() {
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '1'
|
||||
def id = guard.currentMergeableTransaction
|
||||
assert id
|
||||
ProgressManager.instance.runProcessWithProgressSynchronously({
|
||||
assert !ApplicationManager.application.dispatchThread
|
||||
assert id == guard.currentMergeableTransaction
|
||||
log << '2'
|
||||
}, 'title', true, project)
|
||||
}
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
public void "test no id on pooled thread"() {
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '1'
|
||||
ApplicationManager.application.executeOnPooledThread({
|
||||
assert !ApplicationManager.application.dispatchThread
|
||||
assert !guard.currentMergeableTransaction
|
||||
log << '2'
|
||||
}).get()
|
||||
}
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
|
||||
public void "test do not merge transactions with null id"() {
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '1'
|
||||
guard.submitMergeableTransaction testRootDisposable, (TransactionId)null, { log << '2' }
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
assert log == ['1']
|
||||
}
|
||||
assert log == ['1']
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
|
||||
public void "test do not merge into newly started nested transactions"() {
|
||||
TransactionGuard.submitTransaction testRootDisposable, {
|
||||
log << '1'
|
||||
def id = guard.currentMergeableTransaction
|
||||
SwingUtilities.invokeLater {
|
||||
guard.submitMergeableTransaction testRootDisposable, id, { log << '4' }
|
||||
}
|
||||
guard.submitMergeableTransaction testRootDisposable, id, {
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
log << '2'
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
guard.submitMergeableTransaction testRootDisposable, id, { log << '5' }
|
||||
def nestedId = guard.currentMergeableTransaction
|
||||
SwingUtilities.invokeLater {
|
||||
guard.submitMergeableTransaction testRootDisposable, nestedId, { log << '3' }
|
||||
assert log == ['1', '2']
|
||||
}
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
}
|
||||
assert log == ['1', '2', '3']
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
assert log == ['1', '2', '3', '4', '5']
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1895,9 +1895,9 @@ public class UIUtil {
|
||||
AWTEvent event = eventQueue.peekEvent();
|
||||
if (event == null) return false;
|
||||
try {
|
||||
AWTEvent event1 = eventQueue.getNextEvent();
|
||||
if (event1 instanceof InvocationEvent) {
|
||||
((InvocationEvent)event1).dispatch();
|
||||
event = eventQueue.getNextEvent();
|
||||
if (event instanceof InvocationEvent) {
|
||||
eventQueue.getClass().getDeclaredMethod("dispatchEvent", AWTEvent.class).invoke(eventQueue, event);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user