This is an automated email from the ASF dual-hosted git repository.
rpuch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 2b7dbb6643 IGNITE-22710 Add exception translation to Transaction
implementations (#4070)
2b7dbb6643 is described below
commit 2b7dbb6643ba18c0dad17b9ddb5a750dd8c1e39d
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Tue Jul 16 22:42:39 2024 +0400
IGNITE-22710 Add exception translation to Transaction implementations
(#4070)
---
.../internal/lang/IgniteExceptionMapperUtil.java | 50 +++++++++++--
.../org/apache/ignite/internal/util/ViewUtils.java | 1 +
.../ignite/internal/lang/ExceptionUtilsTest.java | 56 ++++++++++++++
.../asserts/CompletableFutureAssert.java | 29 +++++++-
.../ItSchemaForwardCompatibilityTest.java | 3 +-
.../schemasync/ItSchemaSyncSingleNodeTest.java | 27 ++++---
.../ignite/internal/table/ItDurableFinishTest.java | 2 +-
.../internal/table/ItTransactionRecoveryTest.java | 5 +-
.../replicator/IncompatibleSchemaException.java | 1 +
.../StaleTransactionOperationException.java | 5 +-
.../tx/MismatchingTransactionOutcomeException.java | 5 +-
.../internal/tx/TransactionInternalException.java | 81 +++++++++++++++++++++
.../tx/impl/IgniteAbstractTransactionImpl.java | 31 +++++++-
.../tx/impl/TransactionsExceptionMapperUtil.java | 85 ++++++++++++++++++++++
.../apache/ignite/internal/tx/TxManagerTest.java | 7 +-
15 files changed, 356 insertions(+), 32 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/lang/IgniteExceptionMapperUtil.java
b/modules/core/src/main/java/org/apache/ignite/internal/lang/IgniteExceptionMapperUtil.java
index b77c94023b..f830a65bc7 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/lang/IgniteExceptionMapperUtil.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/lang/IgniteExceptionMapperUtil.java
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
+import java.util.function.Function;
import org.apache.ignite.lang.ErrorGroups.Common;
import org.apache.ignite.lang.IgniteCheckedException;
import org.apache.ignite.lang.IgniteException;
@@ -84,9 +85,29 @@ public class IgniteExceptionMapperUtil {
* @return Public exception.
*/
public static Throwable mapToPublicException(Throwable origin) {
+ return mapToPublicException(origin, ex -> new
IgniteException(INTERNAL_ERR, ex));
+ }
+
+ /**
+ * This method provides a mapping from internal exception to Ignite public
ones.
+ *
+ * <p>The rules of mapping are the following:</p>
+ * <ul>
+ * <li>any instance of {@link Error} is returned as is, except {@link
AssertionError}
+ * that will always be mapped to {@link IgniteException} with the
{@link Common#INTERNAL_ERR} error code.</li>
+ * <li>any instance of {@link IgniteException} or {@link
IgniteCheckedException} is returned as is.</li>
+ * <li>if there are no any mappers that can do a mapping from the
given error to a public exception,
+ * then unknownProblemMapper is used to map the exception.</li>
+ * </ul>
+ *
+ * @param origin Exception to be mapped.
+ * @param unknownProblemMapper Mapper used to map an unknown exception.
+ * @return Public exception.
+ */
+ public static Throwable mapToPublicException(Throwable origin,
Function<Throwable, Throwable> unknownProblemMapper) {
if (origin instanceof Error) {
if (origin instanceof AssertionError) {
- return new IgniteException(INTERNAL_ERR, origin);
+ return mapCheckingResultIsPublic(origin, unknownProblemMapper);
}
return origin;
}
@@ -103,9 +124,7 @@ public class IgniteExceptionMapperUtil {
if (m != null) {
res = map(m, origin);
- assert res instanceof IgniteException || res instanceof
IgniteCheckedException :
- "Unexpected mapping of internal exception to a public one
[origin=" + origin + ", mapped=" + res + ']';
-
+ checkResultIsPublic(res, origin);
} else {
res = origin;
}
@@ -115,7 +134,28 @@ public class IgniteExceptionMapperUtil {
}
// There are no exception mappings for the given exception. This case
should be considered as internal error.
- return new IgniteException(INTERNAL_ERR, origin);
+ return mapCheckingResultIsPublic(origin, unknownProblemMapper);
+ }
+
+ private static Throwable mapCheckingResultIsPublic(Throwable origin,
Function<Throwable, Throwable> unknownProblemMapper) {
+ Throwable result = unknownProblemMapper.apply(origin);
+
+ checkResultIsPublic(result, origin);
+
+ return result;
+ }
+
+ private static void checkResultIsPublic(Throwable mappingResult, Throwable
origin) {
+ assert mappingResult instanceof IgniteException || mappingResult
instanceof IgniteCheckedException
+ : "Unexpected mapping of internal exception to a public one
[origin=" + origin + ", mapped=" + mappingResult
+ + "]: not a public exception";
+ assert isPublicPackage(mappingResult.getClass().getPackage())
+ : "Unexpected mapping of internal exception to a public one
[origin=" + origin + ", mapped=" + mappingResult
+ + "]: exception is not defined in a public package";
+ }
+
+ private static boolean isPublicPackage(Package somePackage) {
+ return !somePackage.getName().startsWith("org.apache.ignite.internal");
}
/**
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/util/ViewUtils.java
b/modules/core/src/main/java/org/apache/ignite/internal/util/ViewUtils.java
index da3ff46506..6e2ed70096 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/ViewUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/ViewUtils.java
@@ -86,6 +86,7 @@ public final class ViewUtils {
* @param e Exception.
* @return Properly copied exception or a new error, if exception can not
be copied.
*/
+ // TODO: consider removing after IGNITE-22721 gets resolved.
private static <T extends Throwable & TraceableException> Throwable
copyExceptionWithCauseIfPossible(T e) {
Throwable copy = ExceptionUtils.copyExceptionWithCause(e.getClass(),
e.traceId(), e.code(), e.getMessage(), e);
if (copy != null) {
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/lang/ExceptionUtilsTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/lang/ExceptionUtilsTest.java
index 88e26ee2df..c9f126a1da 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/lang/ExceptionUtilsTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/lang/ExceptionUtilsTest.java
@@ -22,11 +22,16 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
+import java.util.UUID;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import org.apache.ignite.internal.util.ExceptionUtils;
+import org.apache.ignite.lang.ErrorGroups.Transactions;
import org.apache.ignite.lang.IgniteCheckedException;
import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.lang.TraceableException;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -90,6 +95,50 @@ public class ExceptionUtilsTest {
);
}
+ @Test
+ void withCauseDoesNotApplyDefaultCodeWhenCodeIsThere() {
+ TraceableException translated = ExceptionUtils.withCause(
+ TestUncheckedExceptionWithTraceCodeAndCause::new,
+ Transactions.TX_COMMIT_ERR,
+ new IgniteException(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR)
+ );
+
+ assertThat(translated.code(),
is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+ }
+
+ @Test
+ void
withCauseDoesNotApplyDefaultCodeWhenCodeIsInExceptionWrappedInExecutionException()
{
+ TraceableException translated = ExceptionUtils.withCause(
+ TestUncheckedExceptionWithTraceCodeAndCause::new,
+ Transactions.TX_COMMIT_ERR,
+ new ExecutionException(new
IgniteException(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR))
+ );
+
+ assertThat(translated.code(),
is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+ }
+
+ @Test
+ void
withCauseDoesNotApplyDefaultCodeWhenCodeIsInExceptionWrappedInCompletionException()
{
+ TraceableException translated = ExceptionUtils.withCause(
+ TestUncheckedExceptionWithTraceCodeAndCause::new,
+ Transactions.TX_COMMIT_ERR,
+ new CompletionException(new
IgniteException(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR))
+ );
+
+ assertThat(translated.code(),
is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+ }
+
+ @Test
+ void withCauseAppliesDefaultCodeWhenHandlingNonIgniteException() {
+ TraceableException translated = ExceptionUtils.withCause(
+ TestUncheckedExceptionWithTraceCodeAndCause::new,
+ Transactions.TX_COMMIT_ERR,
+ new RuntimeException()
+ );
+
+ assertThat(translated.code(), is(Transactions.TX_COMMIT_ERR));
+ }
+
/** Test exception class. */
public static class TestException extends IgniteException {
public TestException() {
@@ -187,4 +236,11 @@ public class ExceptionUtilsTest {
super(code, message, cause);
}
}
+
+ /** Test exception class. */
+ public static class TestUncheckedExceptionWithTraceCodeAndCause extends
IgniteException {
+ public TestUncheckedExceptionWithTraceCodeAndCause(UUID traceId, int
code, Throwable cause) {
+ super(traceId, code, cause);
+ }
+ }
}
diff --git
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/asserts/CompletableFutureAssert.java
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/asserts/CompletableFutureAssert.java
index cac1d9fa20..db7176760b 100644
---
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/asserts/CompletableFutureAssert.java
+++
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/asserts/CompletableFutureAssert.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.testframework.asserts;
+import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.concurrent.CompletableFuture;
@@ -44,13 +45,37 @@ public class CompletableFutureAssert {
public static <X extends Throwable> X assertWillThrowFast(
CompletableFuture<?> future,
Class<X> expectedExceptionClass
+ ) {
+ return assertWillThrow(future, expectedExceptionClass, 1, SECONDS);
+ }
+
+ /**
+ * Asserts that the given future completes with an exception being an
instance of the given class (in time) and returns
+ * that exception for further examination.
+ *
+ * <p>Unlike
+ * {@link
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher#willThrow(Class,
int, TimeUnit)},
+ * this method allows to examine the actual exception thrown further in
the test.
+ *
+ * @param future Future to work on.
+ * @param expectedExceptionClass Expected class of the exception.
+ * @param timeout Duration to wait for future completion.
+ * @param timeUnit Time unit of the duration.
+ * @param <X> Exception type.
+ * @return Matched exception.
+ */
+ public static <X extends Throwable> X assertWillThrow(
+ CompletableFuture<?> future,
+ Class<X> expectedExceptionClass,
+ long timeout,
+ TimeUnit timeUnit
) {
Object normalResult;
try {
- normalResult = future.get(1, TimeUnit.SECONDS);
+ normalResult = future.get(timeout, timeUnit);
} catch (TimeoutException e) {
- return fail("Expected the future to be completed with an exception
of class in 1 second, but it did not complete in time");
+ return fail("Expected the future to be completed with an exception
of class in time, but it did not");
} catch (Throwable e) {
Throwable unwrapped = unwrapThrowable(e);
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
index 47d2e0054e..4b130b40d9 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
@@ -114,7 +114,8 @@ class ItSchemaForwardCompatibilityTest extends
ClusterPerTestIntegrationTest {
))
);
- assertThat(ex.code(), is(Transactions.TX_UNEXPECTED_STATE_ERR));
+ // TODO: IGNITE-20415 - assert that the failure is because of a
changed schema.
+ assertThat(ex.code(), is(Transactions.TX_COMMIT_ERR));
assertThat(tx.state(), is(TxState.ABORTED));
}
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
index cb37e8f382..093693fe06 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
@@ -100,12 +100,14 @@ class ItSchemaSyncSingleNodeTest extends
ClusterPerTestIntegrationTest {
alterTable(TABLE_NAME);
- IgniteException ex;
+ int errorCode;
int tableId = unwrapTableViewInternal(table).tableId();
if (operation.sql()) {
- ex = assertThrows(IgniteException.class, () ->
operation.execute(table, tx, cluster));
+ IgniteException ex = assertThrows(IgniteException.class, () ->
operation.execute(table, tx, cluster));
+ errorCode = ex.code();
+
assertThat(
ex.getMessage(),
containsString(String.format(
@@ -114,7 +116,9 @@ class ItSchemaSyncSingleNodeTest extends
ClusterPerTestIntegrationTest {
))
);
} else {
- ex = assertThrows(IncompatibleSchemaException.class, () ->
operation.execute(table, tx, cluster));
+ IncompatibleSchemaException ex =
assertThrows(IncompatibleSchemaException.class, () -> operation.execute(table,
tx, cluster));
+ errorCode = ex.code();
+
assertThat(
ex.getMessage(),
is(String.format(
@@ -124,7 +128,7 @@ class ItSchemaSyncSingleNodeTest extends
ClusterPerTestIntegrationTest {
);
}
- assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+ assertThat(errorCode, is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
assertThat(tx.state(), is(TxState.ABORTED));
}
@@ -244,19 +248,23 @@ class ItSchemaSyncSingleNodeTest extends
ClusterPerTestIntegrationTest {
dropTable(TABLE_NAME);
- IgniteException ex;
+ int errorCode;
int tableId = unwrapTableViewInternal(table).tableId();
if (operation.sql()) {
- ex = assertThrows(IgniteException.class, () ->
operation.execute(table, tx, cluster));
+ IgniteException ex = assertThrows(IgniteException.class, () ->
operation.execute(table, tx, cluster));
+ errorCode = ex.code();
+
assertThat(
ex.getMessage(),
// TODO https://issues.apache.org/jira/browse/IGNITE-22309
use tableName instead
containsString(String.format("Table was dropped
[tableId=%s]", tableId))
);
} else {
- ex = assertThrows(IncompatibleSchemaException.class, () ->
operation.execute(table, tx, cluster));
+ IncompatibleSchemaException ex =
assertThrows(IncompatibleSchemaException.class, () -> operation.execute(table,
tx, cluster));
+ errorCode = ex.code();
+
assertThat(
ex.getMessage(),
// TODO https://issues.apache.org/jira/browse/IGNITE-22309
use tableName instead
@@ -264,7 +272,7 @@ class ItSchemaSyncSingleNodeTest extends
ClusterPerTestIntegrationTest {
);
}
- assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+ assertThat(errorCode, is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
assertThat(tx.state(), is(TxState.ABORTED));
}
@@ -299,7 +307,8 @@ class ItSchemaSyncSingleNodeTest extends
ClusterPerTestIntegrationTest {
containsString(String.format("Commit failed because a table
was already dropped [table=%s]", table.name()))
);
- assertThat(((TransactionException) ex).code(),
is(Transactions.TX_UNEXPECTED_STATE_ERR));
+ // TODO: IGNITE-20415 - assert that the failure is because of a
changed schema.
+ assertThat(((TransactionException) ex).code(),
is(Transactions.TX_COMMIT_ERR));
assertThat(tx.state(), is(TxState.ABORTED));
}
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
index 4055bcba2d..227b28e14d 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
@@ -310,7 +310,7 @@ public class ItDurableFinishTest extends
ClusterPerTestIntegrationTest {
Throwable cause =
ExceptionUtils.unwrapCause(transactionException.getCause());
- assertInstanceOf(MismatchingTransactionOutcomeException.class, cause);
+ assertInstanceOf(MismatchingTransactionOutcomeException.class,
cause.getCause());
}
private void markTxAbortedInTxStateStorage(IgniteImpl primaryNode,
InternalTransaction tx, Table publicTable) {
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
index eec1eb4745..76cad253f5 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
@@ -23,12 +23,14 @@ import static
org.apache.ignite.internal.TestWrappers.unwrapTableImpl;
import static
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_PROFILE;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.bypassingThreadAssertions;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
+import static
org.apache.ignite.internal.testframework.asserts.CompletableFutureAssert.assertWillThrow;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrow;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static
org.apache.ignite.internal.tx.impl.ResourceVacuumManager.RESOURCE_VACUUM_INTERVAL_MILLISECONDS_PROPERTY;
import static
org.apache.ignite.internal.tx.test.ItTransactionTestUtils.waitAndGetPrimaryReplica;
import static org.apache.ignite.internal.util.ExceptionUtils.extractCodeFrom;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -789,7 +791,8 @@ public class ItTransactionRecoveryTest extends
ClusterPerTestIntegrationTest {
cancelLease(commitPartNode, tblReplicationGrp);
- assertThat(commitFut,
willThrow(MismatchingTransactionOutcomeException.class, 30, SECONDS));
+ TransactionException txEx = assertWillThrow(commitFut,
TransactionException.class, 30, SECONDS);
+ assertThat(txEx.getCause(),
instanceOf(MismatchingTransactionOutcomeException.class));
RecordView<Tuple> view =
txCrdNode.tables().table(TABLE_NAME).recordView();
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
index 5c4f0027f8..99f20b5fda 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
@@ -25,6 +25,7 @@ import org.apache.ignite.tx.TransactionException;
* Thrown when, during an attempt to execute a transactional operation, it
turns out that the operation cannot be executed
* because an incompatible schema change has happened.
*/
+// TODO: IGNITE-20415 - make this extend TransactionInternalException.
public class IncompatibleSchemaException extends TransactionException
implements ExpectedReplicationException {
private static final String SCHEMA_CHANGED_MESSAGE = "Table schema was
updated after the transaction was started "
+ "[table=%s, startSchema=%d, operationSchema=%d]";
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
index 31055db028..1dab412af1 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
@@ -21,10 +21,11 @@ import static
org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static
org.apache.ignite.lang.ErrorGroups.Transactions.TX_STALE_OPERATION_ERR;
import java.util.UUID;
-import org.apache.ignite.tx.TransactionException;
+import org.apache.ignite.internal.tx.TransactionInternalException;
/** Error that occurs when a stale operation of a completed transaction is
detected. */
-public class StaleTransactionOperationException extends TransactionException {
+// TODO: IGNITE-20415 - make this exception public.
+public class StaleTransactionOperationException extends
TransactionInternalException {
/**
* Constructor.
*
diff --git
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
index 5ae4ea70d3..6fc15f87c7 100644
---
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
+++
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
@@ -19,15 +19,14 @@ package org.apache.ignite.internal.tx;
import static
org.apache.ignite.lang.ErrorGroups.Transactions.TX_UNEXPECTED_STATE_ERR;
-import org.apache.ignite.tx.TransactionException;
-
/**
* The exception is thrown when the transaction result differs from the
intended one.
*
* <p>For example, {@code tx.commit()} is called for a transaction, but the
verification logic decided to abort it instead. The transaction
* will be finished with {@link TxState#ABORTED} and the call to {@code
tx.commit()} will throw this exception.
*/
-public class MismatchingTransactionOutcomeException extends
TransactionException {
+// TODO: IGNITE-20415 - split this into public exception (in a public package)
and internal exception (carrying internal state).
+public class MismatchingTransactionOutcomeException extends
TransactionInternalException {
private static final long serialVersionUID = -7953057695915339651L;
diff --git
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionInternalException.java
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionInternalException.java
new file mode 100644
index 0000000000..8a726212c3
--- /dev/null
+++
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionInternalException.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.tx;
+
+import java.util.UUID;
+import org.apache.ignite.internal.lang.IgniteInternalException;
+
+/**
+ * An internal exception from the transaction subsystem.
+ */
+public class TransactionInternalException extends IgniteInternalException {
+ /**
+ * Creates a new transaction exception with the given error code and cause.
+ *
+ * @param code Full error code.
+ * @param cause Optional nested exception (can be {@code null}).
+ */
+ public TransactionInternalException(int code, Throwable cause) {
+ super(code, cause);
+ }
+
+ /**
+ * Creates a new transaction exception with the given error code and
detail message.
+ *
+ * @param code Full error code.
+ * @param message Detail message.
+ */
+ public TransactionInternalException(int code, String message) {
+ super(code, message);
+ }
+
+
+ /**
+ * Creates a new transaction exception with the given trace id, error code
and cause.
+ *
+ * @param traceId Unique identifier of this exception.
+ * @param code Full error code.
+ * @param cause Optional nested exception (can be {@code null}).
+ */
+ public TransactionInternalException(UUID traceId, int code, Throwable
cause) {
+ super(traceId, code, cause);
+ }
+
+ /**
+ * Creates a new transaction exception with the given error code, detail
message and cause.
+ *
+ * @param code Full error code.
+ * @param message Detail message.
+ * @param cause Optional nested exception (can be {@code null}).
+ */
+ public TransactionInternalException(int code, String message, Throwable
cause) {
+ super(code, message, cause);
+ }
+
+ /**
+ * Creates a new transaction exception with the given trace id, error
code, detail message and cause.
+ *
+ * @param traceId Unique identifier of this exception.
+ * @param code Full error code.
+ * @param message Detail message.
+ * @param cause Optional nested exception (can be {@code null}).
+ */
+ public TransactionInternalException(UUID traceId, int code, String
message, Throwable cause) {
+ super(traceId, code, message, cause);
+ }
+}
diff --git
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java
index 699a4ff803..c90d06e7d6 100644
---
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java
+++
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java
@@ -17,12 +17,16 @@
package org.apache.ignite.internal.tx.impl;
+import static
org.apache.ignite.internal.util.ExceptionUtils.copyExceptionWithCause;
+import static org.apache.ignite.internal.util.ExceptionUtils.sneakyThrow;
import static org.apache.ignite.internal.util.ExceptionUtils.withCause;
+import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_COMMIT_ERR;
import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_ROLLBACK_ERR;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import org.apache.ignite.internal.tx.InternalTransaction;
import org.apache.ignite.internal.tx.TxManager;
import org.apache.ignite.internal.tx.TxState;
@@ -87,7 +91,11 @@ public abstract class IgniteAbstractTransactionImpl
implements InternalTransacti
public void commit() throws TransactionException {
try {
commitAsync().get();
- } catch (Exception e) {
+ } catch (ExecutionException e) {
+ throw sneakyThrow(tryToCopyExceptionWithCause(e));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+
throw withCause(TransactionException::new, TX_COMMIT_ERR, e);
}
}
@@ -95,7 +103,7 @@ public abstract class IgniteAbstractTransactionImpl
implements InternalTransacti
/** {@inheritDoc} */
@Override
public CompletableFuture<Void> commitAsync() {
- return finish(true);
+ return
TransactionsExceptionMapperUtil.convertToPublicFuture(finish(true),
TX_COMMIT_ERR);
}
/** {@inheritDoc} */
@@ -103,7 +111,11 @@ public abstract class IgniteAbstractTransactionImpl
implements InternalTransacti
public void rollback() throws TransactionException {
try {
rollbackAsync().get();
- } catch (Exception e) {
+ } catch (ExecutionException e) {
+ throw sneakyThrow(tryToCopyExceptionWithCause(e));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+
throw withCause(TransactionException::new, TX_ROLLBACK_ERR, e);
}
}
@@ -111,7 +123,7 @@ public abstract class IgniteAbstractTransactionImpl
implements InternalTransacti
/** {@inheritDoc} */
@Override
public CompletableFuture<Void> rollbackAsync() {
- return finish(false);
+ return
TransactionsExceptionMapperUtil.convertToPublicFuture(finish(false),
TX_ROLLBACK_ERR);
}
/**
@@ -122,4 +134,15 @@ public abstract class IgniteAbstractTransactionImpl
implements InternalTransacti
* @return The future.
*/
protected abstract CompletableFuture<Void> finish(boolean commit);
+
+ // TODO: remove after IGNITE-22721 gets resolved.
+ private static Throwable tryToCopyExceptionWithCause(ExecutionException
exception) {
+ Throwable copy = copyExceptionWithCause(exception);
+
+ if (copy == null) {
+ return new TransactionException(INTERNAL_ERR, "Cannot make a
proper copy of " + exception.getCause().getClass(), exception);
+ }
+
+ return copy;
+ }
}
diff --git
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionsExceptionMapperUtil.java
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionsExceptionMapperUtil.java
new file mode 100644
index 0000000000..25c8d1d0db
--- /dev/null
+++
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionsExceptionMapperUtil.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.tx.impl;
+
+import static
org.apache.ignite.internal.lang.IgniteExceptionMapperUtil.mapToPublicException;
+import static
org.apache.ignite.internal.util.CompletableFutures.isCompletedSuccessfully;
+import static org.apache.ignite.internal.util.ExceptionUtils.unwrapCause;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import org.apache.ignite.lang.TraceableException;
+import org.apache.ignite.tx.Transaction;
+import org.apache.ignite.tx.TransactionException;
+
+/**
+ * Utils for mapping exceptions that is specific to the {@link Transaction}
context.
+ */
+class TransactionsExceptionMapperUtil {
+ /**
+ * Returns a new CompletableFuture that, when the given {@code origin}
future completes exceptionally, maps the origin's exception to a
+ * public Ignite exception if it is needed. The mapping is made in the
context of {@link Transaction} methods.
+ *
+ * @param origin The future to use to create a new stage.
+ * @param <T> Type os result.
+ * @return New CompletableFuture.
+ */
+ static <T> CompletableFuture<T> convertToPublicFuture(CompletableFuture<T>
origin, int defaultCode) {
+ if (isCompletedSuccessfully(origin)) {
+ // No need to translate exceptions.
+ return origin;
+ }
+
+ return origin
+ .handle((res, err) -> {
+ if (err != null) {
+ throw new
CompletionException(mapToPublicTransactionException(unwrapCause(err),
defaultCode));
+ }
+
+ return res;
+ });
+ }
+
+ private static Throwable mapToPublicTransactionException(Throwable origin,
int defaultCode) {
+ if (origin instanceof PrimaryReplicaExpiredException) {
+ PrimaryReplicaExpiredException err =
(PrimaryReplicaExpiredException) origin;
+
+ return new TransactionException(err.traceId(), err.code(),
err.getMessage(), err);
+ }
+ if (origin instanceof AssertionError) {
+ return new TransactionException(defaultCode, origin);
+ }
+ if (origin instanceof Error) {
+ return origin;
+ }
+
+ Throwable mapped = mapToPublicException(origin, ex -> new
TransactionException(defaultCode, ex));
+
+ if (mapped instanceof TransactionException) {
+ return mapped;
+ }
+
+ if (mapped instanceof TraceableException) {
+ TraceableException traceable = (TraceableException) mapped;
+
+ return new TransactionException(traceable.traceId(),
traceable.code(), mapped.getMessage(), mapped);
+ }
+
+ return new TransactionException(defaultCode, mapped);
+ }
+}
diff --git
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
index 099d2fbeca..11d25032e7 100644
---
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
+++
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
@@ -31,7 +31,6 @@ import static
org.apache.ignite.lang.ErrorGroups.Transactions.TX_PRIMARY_REPLICA
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -77,6 +76,7 @@ import org.apache.ignite.internal.replicator.ReplicaService;
import org.apache.ignite.internal.replicator.TablePartitionId;
import
org.apache.ignite.internal.replicator.exception.PrimaryReplicaMissException;
import org.apache.ignite.internal.testframework.IgniteAbstractTest;
+import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.internal.tx.configuration.TransactionConfiguration;
import org.apache.ignite.internal.tx.impl.HeapLockManager;
import org.apache.ignite.internal.tx.impl.PrimaryReplicaExpiredException;
@@ -87,7 +87,6 @@ import org.apache.ignite.internal.tx.impl.TxManagerImpl;
import org.apache.ignite.internal.tx.message.TxFinishReplicaRequest;
import org.apache.ignite.internal.tx.test.TestLocalRwTxCounter;
import org.apache.ignite.internal.tx.test.TestTransactionIds;
-import org.apache.ignite.internal.util.ExceptionUtils;
import org.apache.ignite.lang.ErrorGroups.Transactions;
import org.apache.ignite.network.ClusterNode;
import org.apache.ignite.network.NetworkAddress;
@@ -343,7 +342,7 @@ public class TxManagerTest extends IgniteAbstractTest {
TransactionException transactionException =
assertThrows(TransactionException.class, tx::commit);
- assertInstanceOf(MismatchingTransactionOutcomeException.class,
ExceptionUtils.unwrapCause(transactionException.getCause()));
+ assertTrue(IgniteTestUtils.hasCause(transactionException,
MismatchingTransactionOutcomeException.class, null));
tx.commitAsync().get(3, TimeUnit.SECONDS);
tx.rollbackAsync().get(3, TimeUnit.SECONDS);
@@ -371,7 +370,7 @@ public class TxManagerTest extends IgniteAbstractTest {
TransactionException transactionException =
assertThrows(TransactionException.class, tx::rollback);
- assertInstanceOf(MismatchingTransactionOutcomeException.class,
ExceptionUtils.unwrapCause(transactionException.getCause()));
+ assertTrue(IgniteTestUtils.hasCause(transactionException,
MismatchingTransactionOutcomeException.class, null));
tx.commitAsync().get(3, TimeUnit.SECONDS);
tx.rollbackAsync().get(3, TimeUnit.SECONDS);