@AcceptNestedTransactions annotation and assert that project-modal dialogs can't be shown under write actions or transactions

This commit is contained in:
peter
2016-03-10 17:46:02 +01:00
parent fe228056d2
commit 0aa0ee24de
4 changed files with 63 additions and 9 deletions
@@ -0,0 +1,38 @@
/*
* 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 java.lang.annotation.*;
/**
* Add this annotation to modal dialogs (DialogWrapper inheritors) shown from within transactions,
* to allow nested transactions inside those dialogs.
*
* @see TransactionGuard#acceptNestedTransactions(TransactionKind...)
* @since 146.*
* @author peter
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AcceptNestedTransactions {
/**
* @return the kinds of transaction to allow inside the dialog
*/
TransactionKind.Common[] value();
}
@@ -60,7 +60,10 @@ import org.jetbrains.annotations.NotNull;
*
* Having said all that, 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.
* or at least make them and the code that shows them prepared for possible model changes while the dialog is shown.<p/>
*
* 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 <b>"Write access is allowed from model transactions only"</b> exception, what do I do?<br/>
* A: Add a transaction somewhere into the call stack, to the outermost callee where having read/write model consistency is needed.
@@ -177,8 +180,10 @@ public abstract class TransactionGuard {
/**
* Allow incoming transactions of the specified kinds to be executed immediately, instead of being queued until the current transaction is finished.<p/>
*
* Example: outer transaction has shown a dialog with an editor, and typing into that editor (which requires a transaction for changing document)
* should be allowed.
* Example: outer transaction has shown a dialog with an editor, and typing into that editor (which requires a transaction for changing document).
* should be allowed.<p/>
*
* For dialogs, consider using {@link AcceptNestedTransactions} annotation instead of explicit call to this method.
* @param kinds kinds of transactions to allow
* @return a token object for this session. Please call {@link AccessToken#finish()} (inside finally clause) when you don't want
* nested transactions anymore.
@@ -18,8 +18,8 @@ package com.intellij.openapi.application;
import com.intellij.openapi.editor.Document;
/**
* A kind of transaction used in {@link TransactionGuard#submitMergeableTransaction(TransactionKind, Runnable)}
* and {@link TransactionGuard#acceptNestedTransactions(TransactionKind...)}.
* A kind of transaction used in {@link TransactionGuard#submitMergeableTransaction(TransactionKind, Runnable)},
* {@link TransactionGuard#acceptNestedTransactions(TransactionKind...)} and {@link AcceptNestedTransactions}.
*/
public interface TransactionKind {
/**
@@ -24,9 +24,7 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.MnemonicHelper;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.help.HelpManager;
import com.intellij.openapi.keymap.KeymapUtil;
@@ -1641,6 +1639,16 @@ public abstract class DialogWrapper {
@NotNull
private AsyncResult<Boolean> invokeShow() {
Window window = myPeer.getWindow();
if (window instanceof JDialog && ((JDialog)window).getModalityType() == Dialog.ModalityType.DOCUMENT_MODAL) {
if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
LOG.error("Project-modal dialogs should not be shown under a write action.");
}
if (TransactionGuard.getInstance().isInsideTransaction()) {
LOG.error("Project-modal dialogs should not be shown inside a transaction. See TransactionGuard documentation.");
}
}
final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
ensureEventDispatchThread();
@@ -1658,7 +1666,10 @@ public abstract class DialogWrapper {
}
});
myPeer.show();
AcceptNestedTransactions anno = getClass().getAnnotation(AcceptNestedTransactions.class);
try (AccessToken ignore = anno == null ? null : TransactionGuard.getInstance().acceptNestedTransactions((TransactionKind[])anno.value())) {
myPeer.show();
}
return result;
}