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 5027cba4fc4b..e4a3bf7723aa 100644
--- a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java
+++ b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java
@@ -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.
@@ -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.
*
- * 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.
+ * 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.
*
* 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 "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 #submitMergeableTransaction(Disposable, TransactionKind, Runnable)}
- *
- *
- * Q: I've got "Nested transactions are not allowed" exception, what do I do?
- * 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)}
*
*
* Q: What's the difference between transactions and read/write actions and commands ({@link com.intellij.openapi.command.CommandProcessor})?
@@ -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.
+ * 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.
*
- * 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 submitTransactionAndWait(@NotNull TransactionKind kind, @NotNull final Computable transaction) throws ProcessCanceledException {
- final Ref 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.
*
@@ -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();
}
diff --git a/platform/core-api/src/com/intellij/openapi/application/TransactionId.java b/platform/core-api/src/com/intellij/openapi/application/TransactionId.java
new file mode 100644
index 000000000000..c42233849a6f
--- /dev/null
+++ b/platform/core-api/src/com/intellij/openapi/application/TransactionId.java
@@ -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 {
+}
diff --git a/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java b/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java
index 2b73e59d05a7..11d0f3a00fd8 100644
--- a/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java
+++ b/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java
@@ -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 myQueue = new LinkedBlockingQueue();
+ private final Queue myQueue = new LinkedBlockingQueue();
private final Set myMergeableKinds = ContainerUtil.newHashSet();
- private String myTransactionStartTrace;
- private ModalityState myTransactionModality;
- private boolean myUserActivity;
+ private final Map 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 queue = getQueue(prevTransaction);
+ queue.addAll(myCurrentTransaction.myQueue);
+ if (!queue.isEmpty()) {
pollQueueLater();
}
+
+ myWritingAllowed = wasWritingAllowed;
+ myCurrentTransaction = prevTransaction;
}
};
}
+ @NotNull
+ private Queue 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 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 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 myQueue = new LinkedBlockingQueue();
+
+ @Override
+ public String toString() {
+ return "Transaction " + myStartCounter;
+ }
+ }
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java
index 0e4bb8ec6b28..9b6500f42bea 100644
--- a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java
+++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java
@@ -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;
diff --git a/platform/platform-tests/testSrc/com/intellij/application/TransactionTest.groovy b/platform/platform-tests/testSrc/com/intellij/application/TransactionTest.groovy
index c744cc514fc4..1465e8aed36f 100644
--- a/platform/platform-tests/testSrc/com/intellij/application/TransactionTest.groovy
+++ b/platform/platform-tests/testSrc/com/intellij/application/TransactionTest.groovy
@@ -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 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']
+ }
+ }
+
}
diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java
index 0280eff63ed1..4b022e798557 100644
--- a/platform/util/src/com/intellij/util/ui/UIUtil.java
+++ b/platform/util/src/com/intellij/util/ui/UIUtil.java
@@ -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) {