This is an automated email from the ASF dual-hosted git repository.
ptupitsyn 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 2c9fce5bdfa IGNITE-28000 Reduce verbosity of client stacktraces (#7854)
2c9fce5bdfa is described below
commit 2c9fce5bdfa68f809051aa5b4c8cb19850a3d805
Author: Tiago Marques Godinho <[email protected]>
AuthorDate: Tue Apr 21 17:11:42 2026 +0100
IGNITE-28000 Reduce verbosity of client stacktraces (#7854)
- Simplify exceptions returns by TcpClientChannel#readError
- Remove unnecessary calls to ensurePublicException
- Improve TraceableExceptionMapper
- Add copy method to some exceptions with fields.
- Update ViewUtils ensurePublicException implementation
- ClientTable now is responsible for ensuringPublicExceptions
- Refactor error handling in ClientSQL
- Reuse ViewUtils#sync in ClientCompute methods
- Update SQL Exception Mapper
- Do not wrap MarshallerException in MarshallerException in KeyValueViewImpl
- Update ThinClientTests error checks
- Update ItComputeTest error handling checks
- Update ConnectionTest error check
- Update error handling checks in tests
- Update ItThinClientTransactionTest
- Add CancelationException to ViewUtils#sync
---
.../org/apache/ignite/sql/SqlBatchException.java | 11 ++
.../exception/handler/SqlExceptionHandler.java | 20 +-
.../internal/client/ItThinClientComputeTest.java | 82 ++++-----
...tThinClientComputeTypeCheckMarshallingTest.java | 30 ++-
.../client/ItThinClientConnectionTest.java | 12 +-
.../client/ItThinClientTransactionsTest.java | 37 +++-
.../client/ClientExceptionMapperProvider.java | 63 +++++++
.../ClientRetriableTransactionException.java | 8 +-
.../ignite/internal/client/TcpClientChannel.java | 56 ++++--
.../internal/client/compute/ClientCompute.java | 10 +-
.../ignite/internal/client/sql/ClientSql.java | 48 +----
.../ignite/internal/client/table/ClientTable.java | 10 +-
.../tx/ClientTransactionKilledException.java | 4 +-
.../org/apache/ignite/client/ConnectionTest.java | 2 +-
.../ignite/internal/compute/ItComputeBaseTest.java | 97 ++++++----
.../internal/compute/ItComputeStandaloneTest.java | 5 +
.../internal/compute/ItComputeTestClient.java | 5 +
.../internal/compute/ItComputeTestEmbedded.java | 7 +-
.../org/apache/ignite/internal/util/ViewUtils.java | 66 +++++--
.../apache/ignite/internal/util/ViewUtilsTest.java | 11 +-
.../ignite/internal/IgniteExceptionTestUtils.java | 203 +++++++++++++++++++++
.../ignite/internal/TraceableExceptionMatcher.java | 11 +-
.../org/apache/ignite/internal/ssl/ItSslTest.java | 17 +-
.../sql/engine/ItPkOnlyTableCrossApiTest.java | 9 +-
.../ignite/internal/table/KeyValueViewImpl.java | 6 +-
.../ignite/internal/tx/ItRunInTransactionTest.java | 9 +-
26 files changed, 626 insertions(+), 213 deletions(-)
diff --git
a/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java
b/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java
index 13613b5c262..226666f541f 100644
--- a/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java
+++ b/modules/api/src/main/java/org/apache/ignite/sql/SqlBatchException.java
@@ -77,4 +77,15 @@ public class SqlBatchException extends SqlException {
public long[] updateCounters() {
return updCntrs;
}
+
+ /**
+ * Copy the exception.
+ *
+ * @param src Exception to copy.
+ * @return new copied exception.
+ */
+ @SuppressWarnings("PMD.UnusedPrivateMethod")
+ private static SqlBatchException copy(SqlBatchException src) {
+ return new SqlBatchException(src.traceId(), src.code(),
src.updateCounters(), src.getMessage(), src.getCause());
+ }
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/SqlExceptionHandler.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/SqlExceptionHandler.java
index 5419f91043d..78bbf6eaa8b 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/SqlExceptionHandler.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/SqlExceptionHandler.java
@@ -65,8 +65,8 @@ public class SqlExceptionHandler implements
ExceptionHandler<SQLException> {
return fromIgniteException("Client error", e);
}
- if (e.getCause() instanceof IgniteClientConnectionException) {
- IgniteClientConnectionException cause =
(IgniteClientConnectionException) e.getCause();
+ if (e instanceof IgniteClientConnectionException) {
+ IgniteClientConnectionException cause =
(IgniteClientConnectionException) e;
SSLHandshakeException sslHandshakeException = findCause(cause,
SSLHandshakeException.class);
if (sslHandshakeException != null) {
@@ -86,9 +86,23 @@ public class SqlExceptionHandler implements
ExceptionHandler<SQLException> {
private static ErrorUiComponent authnErrUiComponent(IgniteException e) {
InvalidCredentialsException invalidCredentialsException = findCause(e,
InvalidCredentialsException.class);
if (invalidCredentialsException != null) {
+ String msg = invalidCredentialsException.getMessage();
+
+ String details = msg;
+ if (msg != null) {
+ var headerIdx = msg.indexOf('\n');
+ if (headerIdx != -1) {
+ details = msg.substring(0, headerIdx);
+ int traceInfoIdx = details.indexOf(" TraceId:");
+ if (traceInfoIdx != -1) {
+ details = details.substring(0, traceInfoIdx);
+ }
+ }
+ }
+
return ErrorUiComponent.builder()
.header("Could not connect to node. Check authentication
configuration")
- .details(invalidCredentialsException.getMessage())
+ .details(details)
.verbose(extractCauseMessage(e.getMessage()))
.build();
}
diff --git
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTest.java
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTest.java
index 0677b1f18c5..53cc880bb73 100644
---
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTest.java
+++
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTest.java
@@ -23,7 +23,8 @@ import static org.apache.ignite.compute.JobStatus.COMPLETED;
import static org.apache.ignite.compute.JobStatus.EXECUTING;
import static org.apache.ignite.compute.JobStatus.FAILED;
import static org.apache.ignite.compute.JobStatus.QUEUED;
-import static
org.apache.ignite.internal.IgniteExceptionTestUtils.traceableException;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicException;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicExceptionWithHint;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrow;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrowFast;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.will;
@@ -49,7 +50,6 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.oneOf;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -93,6 +93,7 @@ import org.apache.ignite.compute.task.MapReduceTask;
import org.apache.ignite.compute.task.TaskExecution;
import org.apache.ignite.compute.task.TaskExecutionContext;
import org.apache.ignite.deployment.DeploymentUnit;
+import org.apache.ignite.internal.IgniteExceptionTestUtils.Cause;
import org.apache.ignite.internal.compute.JobTaskStatusMapper;
import org.apache.ignite.internal.runner.app.Jobs;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
@@ -438,14 +439,10 @@ public class ItThinClientComputeTest extends
ItAbstractThinClientTest {
submit(JobTarget.node(node(0)),
JobDescriptor.builder(Jobs.IgniteExceptionJob.class).build(), null)
);
- assertThat(cause.getMessage(), containsString("Custom job error"));
- assertEquals(Jobs.TRACE_ID, cause.traceId());
- assertEquals(COLUMN_NOT_FOUND_ERR, cause.code());
- assertInstanceOf(Jobs.CustomException.class, cause);
- assertNotNull(cause.getCause());
- String hint = cause.getCause().getMessage();
-
- assertEquals("To see the full stack trace, set
clientConnector.sendServerExceptionStackTraceToClient:true on the server",
hint);
+ assertThat(cause,
+ publicExceptionWithHint(Jobs.CustomException.class,
COLUMN_NOT_FOUND_ERR, "Custom job error")
+ .withTraceId(is(Jobs.TRACE_ID))
+ );
}
@Test
@@ -455,14 +452,10 @@ public class ItThinClientComputeTest extends
ItAbstractThinClientTest {
.execute(JobTarget.node(node(0)),
JobDescriptor.builder(Jobs.IgniteExceptionJob.class).build(), null)
);
- assertThat(cause.getMessage(), containsString("Custom job error"));
- assertEquals(Jobs.TRACE_ID, cause.traceId());
- assertEquals(COLUMN_NOT_FOUND_ERR, cause.code());
- assertInstanceOf(Jobs.CustomException.class, cause);
- assertNotNull(cause.getCause());
- String hint = cause.getCause().getMessage();
-
- assertEquals("To see the full stack trace, set
clientConnector.sendServerExceptionStackTraceToClient:true on the server",
hint);
+ assertThat(cause,
+ publicExceptionWithHint(Jobs.CustomException.class,
COLUMN_NOT_FOUND_ERR, "Custom job error")
+ .withTraceId(is(Jobs.TRACE_ID))
+ );
}
@ParameterizedTest
@@ -654,31 +647,31 @@ public class ItThinClientComputeTest extends
ItAbstractThinClientTest {
}
private static IgniteException
getExceptionInJobExecutionSync(Supplier<String> execution) {
- IgniteException ex = assertThrows(IgniteException.class,
execution::get);
-
- return (IgniteException) ex.getCause();
+ return assertThrows(IgniteException.class, execution::get);
}
private static void
assertComputeExceptionWithClassAndMessage(IgniteException cause) {
- String expectedMessage = "Job execution failed:
java.lang.ArithmeticException: math err";
- assertThat(cause, is(traceableException(ComputeException.class,
COMPUTE_JOB_FAILED_ERR, expectedMessage)));
-
- assertNotNull(cause.getCause());
- String hint = cause.getCause().getMessage();
-
- assertEquals("To see the full stack trace, set
clientConnector.sendServerExceptionStackTraceToClient:true on the server",
hint);
+ assertThat(cause,
+ publicExceptionWithHint(
+ ComputeException.class,
+ COMPUTE_JOB_FAILED_ERR,
+ "Job execution failed: java.lang.ArithmeticException:
math err"
+ )
+ );
}
private static void assertComputeExceptionWithStackTrace(IgniteException
cause) {
- String expectedMessage = "Job execution failed:
java.lang.ArithmeticException: math err";
- assertThat(cause, is(traceableException(ComputeException.class,
COMPUTE_JOB_FAILED_ERR, expectedMessage)));
-
- assertNotNull(cause.getCause());
-
- assertThat(cause.getCause().getMessage(), containsString(
- "Caused by: java.lang.ArithmeticException: math err" +
System.lineSeparator()
- + "\tat
org.apache.ignite.internal.client.ItThinClientComputeTest$"
- +
"ExceptionJob.executeAsync(ItThinClientComputeTest.java:")
+ assertThat(cause,
+ publicException(
+ ComputeException.class,
+ COMPUTE_JOB_FAILED_ERR,
+ "Job execution failed: java.lang.ArithmeticException:
math err",
+ List.of(
+ Cause.of(ArithmeticException.class, "math err"
+ System.lineSeparator()
+ + "\tat
org.apache.ignite.internal.client.ItThinClientComputeTest$"
+ +
"ExceptionJob.executeAsync(ItThinClientComputeTest.java:")
+ )
+ )
);
}
@@ -891,14 +884,13 @@ public class ItThinClientComputeTest extends
ItAbstractThinClientTest {
TaskDescriptor<I, String> taskDescriptor =
TaskDescriptor.builder(taskClass).build();
IgniteException cause =
getExceptionInTaskExecutionAsync(client.compute().submitMapReduce(taskDescriptor,
null));
- assertThat(cause.getMessage(), containsString("Custom job error"));
- assertEquals(Jobs.TRACE_ID, cause.traceId());
- assertEquals(COLUMN_NOT_FOUND_ERR, cause.code());
- assertInstanceOf(Jobs.CustomException.class, cause);
- assertNotNull(cause.getCause());
- String hint = cause.getCause().getMessage();
-
- assertEquals("To see the full stack trace, set
clientConnector.sendServerExceptionStackTraceToClient:true on the server",
hint);
+ assertThat(cause,
+ publicExceptionWithHint(
+ Jobs.CustomException.class,
+ COLUMN_NOT_FOUND_ERR,
+ "Custom job error"
+ ).withTraceId(is(Jobs.TRACE_ID))
+ );
}
}
diff --git
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTypeCheckMarshallingTest.java
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTypeCheckMarshallingTest.java
index 3463cc2e1d7..a6e1684485f 100644
---
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTypeCheckMarshallingTest.java
+++
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientComputeTypeCheckMarshallingTest.java
@@ -17,19 +17,20 @@
package org.apache.ignite.internal.client;
+import static java.util.Collections.emptyList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.apache.ignite.compute.JobStatus.COMPLETED;
import static org.apache.ignite.compute.JobStatus.FAILED;
-import static org.apache.ignite.internal.IgniteExceptionTestUtils.hasMessage;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicException;
import static
org.apache.ignite.internal.IgniteExceptionTestUtils.traceableException;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrow;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
import static
org.apache.ignite.internal.testframework.matchers.JobStateMatcher.jobStateWithStatus;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.ignite.compute.ComputeException;
import org.apache.ignite.compute.ComputeJob;
@@ -37,6 +38,7 @@ import org.apache.ignite.compute.JobDescriptor;
import org.apache.ignite.compute.JobExecution;
import org.apache.ignite.compute.JobExecutionContext;
import org.apache.ignite.compute.JobTarget;
+import org.apache.ignite.internal.IgniteExceptionTestUtils.Cause;
import org.apache.ignite.internal.runner.app.Jobs.ArgMarshallingJob;
import org.apache.ignite.internal.runner.app.Jobs.ResultMarshallingJob;
import org.apache.ignite.lang.ErrorGroups.Compute;
@@ -139,7 +141,7 @@ public class ItThinClientComputeTypeCheckMarshallingTest
extends ItAbstractThinC
assertResultFailsWithErr(
result, Compute.MARSHALLING_TYPE_MISMATCH_ERR,
"Exception in user-defined marshaller",
- hasMessage(containsString("java.lang.RuntimeException: User
defined error."))
+ List.of(Cause.of(RuntimeException.class, "User defined
error."))
);
}
@@ -205,19 +207,35 @@ public class ItThinClientComputeTypeCheckMarshallingTest
extends ItAbstractThinC
}
}
+ private static void assertResultFailsWithErr(
+ JobExecution<?> result,
+ int errCode,
+ String expectedMessage,
+ @Nullable Matcher<? extends Throwable> causeMatcher
+ ) {
+ assertThat(
+ result.resultAsync(),
+ willThrow(traceableException(ComputeException.class, errCode,
expectedMessage).withCause(causeMatcher))
+ );
+ }
+
private static void assertResultFailsWithErr(JobExecution<?> result, int
errCode, String expectedMessage) {
- assertResultFailsWithErr(result, errCode, expectedMessage, null);
+ assertResultFailsWithErr(result, errCode, expectedMessage,
emptyList());
}
private static void assertResultFailsWithErr(
JobExecution<?> result,
int errCode,
String expectedMessage,
- @Nullable Matcher<? extends Throwable> causeMatcher
+ List<Cause> causes
) {
assertThat(
result.resultAsync(),
- willThrow(traceableException(ComputeException.class, errCode,
expectedMessage).withCause(causeMatcher))
+ willThrow(
+ publicException(
+ ComputeException.class, errCode,
expectedMessage, causes
+ )
+ )
);
}
}
diff --git
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientConnectionTest.java
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientConnectionTest.java
index 99ffd6216fd..6cbfd0eec7f 100644
---
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientConnectionTest.java
+++
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientConnectionTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.client;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicExceptionWithHint;
import static
org.apache.ignite.internal.eventlog.api.IgniteEventType.CLIENT_CONNECTION_CLOSED;
import static
org.apache.ignite.internal.eventlog.api.IgniteEventType.CLIENT_CONNECTION_ESTABLISHED;
import static org.apache.ignite.lang.ErrorGroups.Table.TABLE_NOT_FOUND_ERR;
@@ -37,9 +38,11 @@ import java.util.stream.IntStream;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
import org.apache.ignite.internal.testframework.log4j2.EventLogInspector;
+import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.network.ClusterNode;
import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.sql.SqlException;
import org.apache.ignite.table.RecordView;
import org.apache.ignite.table.Table;
import org.apache.ignite.table.Tuple;
@@ -126,17 +129,14 @@ public class ItThinClientConnectionTest extends
ItAbstractThinClientTest {
@Test
void testExceptionHasHint() {
// Execute on all nodes to collect all types of exception.
- List<String> causes = IntStream.range(0,
client().configuration().addresses().length)
+ List<IgniteException> causes = IntStream.range(0,
client().configuration().addresses().length)
.mapToObj(i -> {
- IgniteException ex = assertThrows(IgniteException.class,
() -> client().sql().execute("select x from bad"));
-
- return
ex.getCause().getCause().getCause().getCause().getMessage();
+ return assertThrows(IgniteException.class, () ->
client().sql().execute("select x from bad"));
})
.collect(Collectors.toList());
assertThat(causes,
- hasItem(containsString("To see the full stack trace, "
- + "set
clientConnector.sendServerExceptionStackTraceToClient:true on the server")));
+ hasItem(publicExceptionWithHint(SqlException.class,
Sql.STMT_VALIDATION_ERR, "Object 'BAD' not found")));
}
@Test
diff --git
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientTransactionsTest.java
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientTransactionsTest.java
index b5176e9a517..7d18e4b3478 100644
---
a/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientTransactionsTest.java
+++
b/modules/client/src/integrationTest/java/org/apache/ignite/internal/client/ItThinClientTransactionsTest.java
@@ -20,12 +20,14 @@ package org.apache.ignite.internal.client;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicException;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicExceptionWithHint;
import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrowWithCauseOrSuppressed;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willSucceedFast;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.isA;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -70,6 +72,7 @@ import org.apache.ignite.internal.tx.Lock;
import org.apache.ignite.internal.tx.TxState;
import org.apache.ignite.internal.util.CollectionUtils;
import org.apache.ignite.lang.ErrorGroups;
+import org.apache.ignite.lang.ErrorGroups.Common;
import org.apache.ignite.lang.ErrorGroups.Transactions;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.network.ClusterNode;
@@ -322,11 +325,14 @@ public class ItThinClientTransactionsTest extends
ItAbstractThinClientTest {
};
var ex = assertThrows(IgniteException.class, () -> kvView().put(tx, 1,
"1"));
-
- String expected = "Unsupported transaction implementation: "
- + "'class
org.apache.ignite.internal.client.ItThinClientTransactionsTest";
-
- assertThat(ex.getMessage(), containsString(expected));
+ assertThat(ex,
+ publicException(
+ IgniteException.class,
+ Common.INTERNAL_ERR,
+ format("Unsupported transaction implementation: 'class
%s'", tx.getClass().getName()),
+ emptyList()
+ )
+ );
}
@Test
@@ -338,8 +344,14 @@ public class ItThinClientTransactionsTest extends
ItAbstractThinClientTest {
RecordView<Tuple> recordView =
client2.tables().tables().get(0).recordView();
var ex = assertThrows(IgniteException.class, () ->
recordView.upsert(tx, Tuple.create()));
-
- assertThat(ex.getMessage(), containsString("Transaction belongs to
a different client instance"));
+ assertThat(ex,
+ publicException(
+ IgniteException.class,
+ Common.INTERNAL_ERR,
+ "Transaction belongs to a different client
instance",
+ emptyList()
+ ).withCause(isA(IllegalArgumentException.class))
+ );
}
}
@@ -372,8 +384,13 @@ public class ItThinClientTransactionsTest extends
ItAbstractThinClientTest {
Transaction tx = client().transactions().begin(new
TransactionOptions().readOnly(true));
var ex = assertThrows(TransactionException.class, () -> kvView.put(tx,
1, "2"));
- assertThat(ex.getMessage(), containsString("Failed to enlist
read-write operation into read-only transaction"));
-
assertEquals(ErrorGroups.Transactions.TX_FAILED_READ_WRITE_OPERATION_ERR,
ex.code());
+ assertThat(ex,
+ publicExceptionWithHint(
+ TransactionException.class,
+
ErrorGroups.Transactions.TX_FAILED_READ_WRITE_OPERATION_ERR,
+ "Failed to enlist read-write operation into read-only
transaction"
+ )
+ );
}
@ParameterizedTest
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/ClientExceptionMapperProvider.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/ClientExceptionMapperProvider.java
new file mode 100644
index 00000000000..d1ddf42012c
--- /dev/null
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/ClientExceptionMapperProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.client;
+
+import static
org.apache.ignite.internal.util.ExceptionUtils.copyExceptionWithCause;
+
+import com.google.auto.service.AutoService;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.UnaryOperator;
+import org.apache.ignite.internal.client.tx.ClientTransactionKilledException;
+import org.apache.ignite.internal.lang.IgniteExceptionMapper;
+import org.apache.ignite.internal.lang.IgniteExceptionMappersProvider;
+import org.apache.ignite.internal.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteException;
+
+/** Client Module Exception mapper. */
+@AutoService(IgniteExceptionMappersProvider.class)
+public class ClientExceptionMapperProvider implements
IgniteExceptionMappersProvider {
+ private static final String RETRIABLE_TX_MESSAGE = "Retriable transaction
exception";
+
+ @Override
+ public Collection<IgniteExceptionMapper<?, ?>> mappers() {
+ return List.of(
+ mapException(
+ ClientRetriableTransactionException.class,
+ err -> new
ClientRetriableTransactionException(err.code(), RETRIABLE_TX_MESSAGE, null)
+ ),
+ mapException(
+ ClientTransactionKilledException.class,
+ err -> new
ClientTransactionKilledException(err.traceId(), err.code(),
RETRIABLE_TX_MESSAGE, err.txId(), null)
+ )
+ );
+ }
+
+ private static <T extends IgniteInternalException>
IgniteExceptionMapper<T, IgniteException> mapException(
+ Class<T> errType,
+ UnaryOperator<T> copyFunc
+ ) {
+ return IgniteExceptionMapper.unchecked(errType, err -> {
+ Throwable cause = err.getCause();
+ assert cause.getCause() == null : "Cause of client
RetriableTransactionExceptions should have no causes.";
+ // Retriable copy is actually a RetriableTransactionException. May
not be included in the future to present leaking internals.
+ Throwable retriableCopy = copyFunc.apply(err);
+ return copyExceptionWithCause(cause.getClass(), err.traceId(),
err.code(), err.getMessage(), retriableCopy);
+ });
+ }
+}
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/ClientRetriableTransactionException.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/ClientRetriableTransactionException.java
index dfea7601a62..75d7129c4b6 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/ClientRetriableTransactionException.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/ClientRetriableTransactionException.java
@@ -17,14 +17,14 @@
package org.apache.ignite.internal.client;
-import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.internal.lang.IgniteInternalException;
import org.apache.ignite.tx.RetriableTransactionException;
/**
* Wraps client exception cause for retry purposes, which is based on marker
interface RetriableTransactionException.
*/
-class ClientRetriableTransactionException extends IgniteException implements
RetriableTransactionException {
- public ClientRetriableTransactionException(int code, Throwable cause) {
- super(code, cause);
+public class ClientRetriableTransactionException extends
IgniteInternalException implements RetriableTransactionException {
+ public ClientRetriableTransactionException(int code, String msg, Throwable
cause) {
+ super(code, msg, cause);
}
}
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
index 6ec7fdac4c4..0a9322426a1 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
@@ -74,7 +74,6 @@ import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.properties.IgniteProductVersion;
import org.apache.ignite.internal.thread.PublicApiThreading;
import org.apache.ignite.internal.tostring.S;
-import org.apache.ignite.internal.util.ViewUtils;
import org.apache.ignite.lang.ErrorGroups.Table;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.TraceableException;
@@ -437,7 +436,7 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
return completedFuture(complete(payloadReader,
notificationFut, unpacker, opCode));
} catch (Throwable t) {
expectedException = true;
- throw sneakyThrow(ViewUtils.ensurePublicException(t));
+ throw sneakyThrow(t);
}
}
@@ -469,7 +468,7 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
metrics.requestsActiveDecrement();
- throw sneakyThrow(ViewUtils.ensurePublicException(t));
+ throw sneakyThrow(t);
}
}
@@ -485,11 +484,11 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
assert unpacker == null : "unpacker must be null if err is not
null";
try {
- asyncContinuationExecutor.execute(() ->
resFut.completeExceptionally(ViewUtils.ensurePublicException(err)));
+ asyncContinuationExecutor.execute(() ->
resFut.completeExceptionally(err));
} catch (Throwable execError) {
// Executor error, complete directly.
execError.addSuppressed(err);
-
resFut.completeExceptionally(ViewUtils.ensurePublicException(execError));
+ resFut.completeExceptionally(execError);
}
return;
@@ -502,14 +501,14 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
try {
resFut.complete(complete(payloadReader, notificationFut,
unpacker, opCode));
} catch (Throwable t) {
-
resFut.completeExceptionally(ViewUtils.ensurePublicException(t));
+ resFut.completeExceptionally(t);
}
});
} catch (Throwable execErr) {
unpacker.close();
// Executor error, complete directly.
-
resFut.completeExceptionally(ViewUtils.ensurePublicException(execErr));
+ resFut.completeExceptionally(execErr);
}
}
@@ -652,12 +651,22 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
var errClassName = unpacker.unpackString();
var errMsg = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
- boolean retriable = false;
+ @Nullable String causeStr = unpacker.tryUnpackNil() ? null :
unpacker.unpackString();
- IgniteException causeWithStackTrace = unpacker.tryUnpackNil() ? null :
new IgniteException(traceId, code, unpacker.unpackString());
+ String msg;
+ if (causeStr == null) {
+ msg = errMsg;
+ } else if (errMsg == null) {
+ msg = causeStr;
+ } else {
+ // Remove some duplication between errorMsg and cause.
+ int idx = causeStr.indexOf(errMsg);
+ msg = (idx == -1) ? errMsg + '\n' + causeStr :
causeStr.substring(idx);
+ }
int extSize = unpacker.tryUnpackNil() ? 0 : unpacker.unpackInt();
int expectedSchemaVersion = -1;
+ boolean retriable = false;
for (int i = 0; i < extSize; i++) {
String key = unpacker.unpackString();
@@ -667,14 +676,16 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
} else if (key.equals(ErrorExtensions.SQL_UPDATE_COUNTERS)) {
// Deprecated format, keep for compat with older servers.
return new SqlBatchException(traceId, code,
unpacker.unpackLongArray(),
- errMsg != null ? errMsg : "SQL batch execution error",
causeWithStackTrace);
+ msg != null ? msg : "SQL batch execution error", null);
} else if (key.equals(ErrorExtensions.SQL_UPDATE_COUNTERS_2)) {
return new SqlBatchException(traceId, code,
unpacker.unpackLongArrayAsBinary(),
- errMsg != null ? errMsg : "SQL batch execution error",
causeWithStackTrace);
+ msg != null ? msg : "SQL batch execution error", null);
} else if (key.equals(ErrorExtensions.DELAYED_ACK)) {
+ Throwable causeWithStackTrace = createException(errClassName,
traceId, code, msg, false);
return new ClientDelayedAckException(traceId, code, errMsg,
unpacker.unpackUuid(), causeWithStackTrace);
} else if (key.equals(ErrorExtensions.TX_KILL)) {
- return new ClientTransactionKilledException(traceId, code,
errMsg, unpacker.unpackUuid(), causeWithStackTrace);
+ Throwable causeWithStackTrace = createException(errClassName,
traceId, code, msg, false);
+ return new ClientTransactionKilledException(traceId, code,
msg, unpacker.unpackUuid(), causeWithStackTrace);
} else if (key.equals(ErrorExtensions.FLAGS)) {
EnumSet<ErrorFlags> flags =
ErrorFlags.unpack(unpacker.unpackInt());
retriable = flags.contains(ErrorFlags.RETRIABLE);
@@ -685,23 +696,32 @@ class TcpClientChannel implements ClientChannel,
ClientMessageHandler, ClientCon
}
if (code == Table.SCHEMA_VERSION_MISMATCH_ERR) {
+ Throwable causeWithStackTrace = createException(errClassName,
traceId, code, msg, false);
if (expectedSchemaVersion == -1) {
return new IgniteException(
traceId, PROTOCOL_ERR, "Expected schema version is not
specified in error extension map.", causeWithStackTrace);
}
- return new ClientSchemaVersionMismatchException(traceId, code,
errMsg, expectedSchemaVersion, causeWithStackTrace);
+ return new ClientSchemaVersionMismatchException(traceId, code,
msg, expectedSchemaVersion, null);
}
+ return createException(errClassName, traceId, code, msg, retriable);
+ }
+
+ private static Throwable createException(String errClassName, UUID
traceId, int code, String msg, boolean isRetriable) {
try {
Class<? extends Throwable> errCls = (Class<? extends Throwable>)
Class.forName(errClassName);
- return copyExceptionWithCause(errCls, traceId, code, errMsg,
- retriable ? new ClientRetriableTransactionException(code,
causeWithStackTrace) : causeWithStackTrace);
+ @Nullable Throwable ex = copyExceptionWithCause(errCls, traceId,
code, msg, null);
+ if (ex == null) {
+ ex = new IgniteException(traceId, code, msg);
+ }
+
+ return isRetriable
+ ? new ClientRetriableTransactionException(code, msg, ex)
+ : ex;
} catch (ClassNotFoundException ignored) {
- // Ignore: incompatible exception class. Fall back to generic
exception.
+ return new IgniteException(traceId, code, errClassName + ": " +
msg);
}
-
- return new IgniteException(traceId, code, errClassName + ": " +
errMsg, causeWithStackTrace);
}
/** {@inheritDoc} */
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/compute/ClientCompute.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/compute/ClientCompute.java
index fa258e857cf..4089c213ff2 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/compute/ClientCompute.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/compute/ClientCompute.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.client.compute;
import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static
org.apache.ignite.internal.client.TcpIgniteClient.unpackClusterNode;
+import static org.apache.ignite.internal.util.ViewUtils.sync;
import static org.apache.ignite.lang.ErrorGroups.Client.TABLE_ID_NOT_FOUND_ERR;
import java.util.ArrayList;
@@ -67,7 +68,6 @@ import
org.apache.ignite.internal.client.table.PartitionAwarenessProvider;
import org.apache.ignite.internal.compute.BroadcastJobExecutionImpl;
import org.apache.ignite.internal.compute.FailedExecution;
import org.apache.ignite.internal.util.ExceptionUtils;
-import org.apache.ignite.internal.util.ViewUtils;
import org.apache.ignite.lang.CancelHandleHelper;
import org.apache.ignite.lang.CancellationToken;
import org.apache.ignite.lang.IgniteException;
@@ -588,12 +588,4 @@ public class ClientCompute implements IgniteCompute {
ch.notificationFuture()
);
}
-
- private static <R> R sync(CompletableFuture<R> future) {
- try {
- return future.join();
- } catch (CompletionException e) {
- throw
ExceptionUtils.sneakyThrow(ViewUtils.ensurePublicException(e));
- }
- }
}
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
index 5fb3783f500..694191eb744 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
@@ -28,6 +28,8 @@ import static
org.apache.ignite.internal.client.proto.ProtocolBitmaskFeature.TX_
import static
org.apache.ignite.internal.client.proto.ProtocolBitmaskFeature.TX_PIGGYBACK;
import static org.apache.ignite.internal.util.ExceptionUtils.sneakyThrow;
import static org.apache.ignite.internal.util.ExceptionUtils.unwrapCause;
+import static org.apache.ignite.internal.util.ViewUtils.ensurePublicException;
+import static org.apache.ignite.internal.util.ViewUtils.sync;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
@@ -38,7 +40,6 @@ import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
@@ -61,7 +62,6 @@ import
org.apache.ignite.internal.marshaller.MarshallersProvider;
import org.apache.ignite.internal.sql.StatementBuilderImpl;
import org.apache.ignite.internal.sql.StatementImpl;
import org.apache.ignite.internal.sql.SyncResultSetAdapter;
-import org.apache.ignite.internal.util.ExceptionUtils;
import org.apache.ignite.lang.CancelHandleHelper;
import org.apache.ignite.lang.CancellationToken;
import org.apache.ignite.lang.ErrorGroups.Sql;
@@ -148,12 +148,7 @@ public class ClientSql implements IgniteSql {
@Nullable Object... arguments
) {
Objects.requireNonNull(query);
-
- try {
- return new SyncResultSetAdapter<>(executeAsync(transaction,
cancellationToken, query, arguments).join());
- } catch (CompletionException e) {
- throw sneakyThrow(ExceptionUtils.copyExceptionWithCause(e));
- }
+ return new SyncResultSetAdapter<>(sync(executeAsync(transaction,
cancellationToken, query, arguments)));
}
/** {@inheritDoc} */
@@ -165,12 +160,7 @@ public class ClientSql implements IgniteSql {
@Nullable Object... arguments
) {
Objects.requireNonNull(statement);
-
- try {
- return new SyncResultSetAdapter<>(executeAsync(transaction,
cancellationToken, statement, arguments).join());
- } catch (CompletionException e) {
- throw sneakyThrow(ExceptionUtils.copyExceptionWithCause(e));
- }
+ return new SyncResultSetAdapter<>(sync(executeAsync(transaction,
cancellationToken, statement, arguments)));
}
/** {@inheritDoc} */
@@ -183,12 +173,7 @@ public class ClientSql implements IgniteSql {
@Nullable Object... arguments
) {
Objects.requireNonNull(query);
-
- try {
- return new SyncResultSetAdapter<>(executeAsync(transaction,
mapper, cancellationToken, query, arguments).join());
- } catch (CompletionException e) {
- throw sneakyThrow(ExceptionUtils.copyExceptionWithCause(e));
- }
+ return new SyncResultSetAdapter<>(sync(executeAsync(transaction,
mapper, cancellationToken, query, arguments)));
}
/** {@inheritDoc} */
@@ -201,12 +186,7 @@ public class ClientSql implements IgniteSql {
@Nullable Object... arguments
) {
Objects.requireNonNull(statement);
-
- try {
- return new SyncResultSetAdapter<>(executeAsync(transaction,
mapper, cancellationToken, statement, arguments).join());
- } catch (CompletionException e) {
- throw sneakyThrow(ExceptionUtils.copyExceptionWithCause(e));
- }
+ return new SyncResultSetAdapter<>(sync(executeAsync(transaction,
mapper, cancellationToken, statement, arguments)));
}
/** {@inheritDoc} */
@@ -228,11 +208,7 @@ public class ClientSql implements IgniteSql {
Statement dmlStatement,
BatchedArguments batch
) {
- try {
- return executeBatchAsync(transaction, cancellationToken,
dmlStatement, batch).join();
- } catch (CompletionException e) {
- throw sneakyThrow(ExceptionUtils.copyExceptionWithCause(e));
- }
+ return sync(executeBatchAsync(transaction, cancellationToken,
dmlStatement, batch));
}
/** {@inheritDoc} */
@@ -245,12 +221,7 @@ public class ClientSql implements IgniteSql {
@Override
public void executeScript(@Nullable CancellationToken cancellationToken,
String query, @Nullable Object... arguments) {
Objects.requireNonNull(query);
-
- try {
- executeScriptAsync(cancellationToken, query, arguments).join();
- } catch (CompletionException e) {
- throw sneakyThrow(ExceptionUtils.copyExceptionWithCause(e));
- }
+ sync(executeScriptAsync(cancellationToken, query, arguments));
}
/** {@inheritDoc} */
@@ -693,7 +664,8 @@ public class ClientSql implements IgniteSql {
}
private static <T> T handleException(Throwable e) {
- Throwable ex = unwrapCause(e);
+ Throwable ex = ensurePublicException(unwrapCause(e));
+
if (ex instanceof TransactionException) {
var te = (TransactionException) ex;
throw new SqlException(te.traceId(), te.code(), te.getMessage(),
te);
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
index 3b90a75e62a..b4b944c13d5 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTable.java
@@ -66,6 +66,7 @@ import
org.apache.ignite.internal.marshaller.MarshallersProvider;
import org.apache.ignite.internal.marshaller.UnmappedColumnsException;
import org.apache.ignite.internal.tostring.IgniteToStringBuilder;
import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.ViewUtils;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.table.KeyValueView;
import org.apache.ignite.table.QualifiedName;
@@ -585,7 +586,14 @@ public class ClientTable implements Table {
return null;
});
- return fut;
+ return fut.handle((v, err) -> {
+ if (err == null) {
+ return v;
+ }
+
+ var cause = unwrapCause(err);
+ throw sneakyThrow(ViewUtils.ensurePublicException(cause));
+ });
}
private <T> @Nullable Object readSchemaAndReadData(
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactionKilledException.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactionKilledException.java
index a79bc88d2d8..f22b9e48edf 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactionKilledException.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactionKilledException.java
@@ -18,14 +18,14 @@
package org.apache.ignite.internal.client.tx;
import java.util.UUID;
+import org.apache.ignite.internal.lang.IgniteInternalException;
import org.apache.ignite.tx.RetriableTransactionException;
-import org.apache.ignite.tx.TransactionException;
import org.jetbrains.annotations.Nullable;
/**
* Reports a killed transaction.
*/
-public class ClientTransactionKilledException extends TransactionException
implements RetriableTransactionException {
+public class ClientTransactionKilledException extends IgniteInternalException
implements RetriableTransactionException {
/** Serial version uid. */
private static final long serialVersionUID = 0L;
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/ConnectionTest.java
b/modules/client/src/test/java/org/apache/ignite/client/ConnectionTest.java
index a3b3c15faad..d3ab2406a62 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ConnectionTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ConnectionTest.java
@@ -77,7 +77,7 @@ public class ConnectionTest extends AbstractClientTest {
var ex = assertThrows(IgniteClientConnectionException.class,
() -> testConnection("127.0.0.1:47500"));
- String errMsg = ex.getCause().getMessage();
+ String errMsg = ex.getMessage();
// It does not seem possible to verify that it's a 'Connection
refused' exception because with different
// user locales the message differs, so let's just check that the
message ends with the known suffix.
diff --git
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeBaseTest.java
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeBaseTest.java
index 02043f31c41..49e9691e90c 100644
---
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeBaseTest.java
+++
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeBaseTest.java
@@ -25,6 +25,7 @@ import static org.apache.ignite.compute.JobStatus.EXECUTING;
import static org.apache.ignite.compute.JobStatus.FAILED;
import static org.apache.ignite.compute.JobStatus.QUEUED;
import static org.apache.ignite.internal.IgniteExceptionTestUtils.hasMessage;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicException;
import static
org.apache.ignite.internal.IgniteExceptionTestUtils.traceableException;
import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
@@ -44,7 +45,6 @@ import static org.hamcrest.Matchers.both;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.in;
@@ -84,6 +84,7 @@ import org.apache.ignite.compute.task.TaskExecution;
import org.apache.ignite.deployment.DeploymentUnit;
import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
import org.apache.ignite.internal.ConfigOverride;
+import org.apache.ignite.internal.IgniteExceptionTestUtils.Cause;
import org.apache.ignite.internal.hlc.HybridTimestamp;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.lang.CancelHandle;
@@ -115,6 +116,8 @@ import org.junit.jupiter.params.provider.ValueSource;
public abstract class ItComputeBaseTest extends ClusterPerClassIntegrationTest
{
protected abstract List<DeploymentUnit> units();
+ protected abstract ClientType clientType();
+
protected String jobPackage() {
return "org.example.jobs.embedded";
}
@@ -310,7 +313,7 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
JobTarget.node(clusterNode(entryNode)),
failingJob(), null));
- assertThat(ex, is(computeJobFailedException("JobException", "Oops")));
+ assertThat(ex,
is(computeJobFailedException("org.example.jobs.embedded.JobException",
"Oops")));
}
@Test
@@ -323,7 +326,7 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
null
);
- assertThat(execution.resultAsync(),
willThrow(computeJobFailedException("JobException", "Oops")));
+ assertThat(execution.resultAsync(),
willThrow(computeJobFailedException("org.example.jobs.embedded.JobException",
"Oops")));
assertThat(execution.stateAsync(), willBe(jobStateWithStatus(FAILED)));
}
@@ -334,7 +337,7 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
JobTarget.anyNode(clusterNode(node(1)), clusterNode(node(2))),
failingJob(), null));
- assertThat(ex, is(computeJobFailedException("JobException", "Oops")));
+ assertThat(ex,
is(computeJobFailedException("org.example.jobs.embedded.JobException",
"Oops")));
}
@Test
@@ -358,7 +361,7 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
null
);
- assertThat(execution.resultAsync(),
willThrow(computeJobFailedException("JobException", "Oops")));
+ assertThat(execution.resultAsync(),
willThrow(computeJobFailedException("org.example.jobs.embedded.JobException",
"Oops")));
assertThat(execution.stateAsync(), willBe(jobStateWithStatus(FAILED)));
}
@@ -412,12 +415,13 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
Collection<JobExecution<String>> executions =
broadcastExecution.executions();
assertThat(executions, hasSize(3));
for (JobExecution<String> execution : executions) {
- assertThat(execution.resultAsync(),
willThrow(computeJobFailedException("JobException", "Oops")));
+ assertThat(execution.resultAsync(),
willThrow(computeJobFailedException("org.example.jobs.embedded.JobException",
"Oops")));
assertThat(execution.stateAsync(),
willBe(jobStateWithStatus(FAILED)));
}
- assertThat(broadcastExecution.resultsAsync(),
willThrow(computeJobFailedException("JobException", "Oops")));
+ assertThat(broadcastExecution.resultsAsync(),
+
willThrow(computeJobFailedException("org.example.jobs.embedded.JobException",
"Oops")));
}
@Test
@@ -669,17 +673,8 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
assertThat(cancelHandle.cancelAsync(), willCompleteSuccessfully());
CompletionException completionException =
assertThrows(CompletionException.class, () -> execution.resultAsync().join());
-
- // Unwrap CompletionException, ComputeException should be the cause
thrown from the API
- assertThat(completionException.getCause(),
instanceOf(ComputeException.class));
- ComputeException computeException = (ComputeException)
completionException.getCause();
-
- // ComputeException should be caused by the RuntimeException thrown
from the SleepJob
- assertThat(computeException.getCause(),
instanceOf(RuntimeException.class));
- RuntimeException runtimeException = (RuntimeException)
computeException.getCause();
-
- // RuntimeException is thrown when SleepJob catches the
InterruptedException
- assertThat(runtimeException.toString(),
containsString(InterruptedException.class.getName()));
+ assertThat((Exception) completionException.getCause(),
+
computeJobFailedException(InterruptedException.class.getName(), "sleep
interrupted"));
await().until(execution::stateAsync,
willBe(jobStateWithStatus(FAILED)));
}
@@ -1061,29 +1056,63 @@ public abstract class ItComputeBaseTest extends
ClusterPerClassIntegrationTest {
.units(units()).build();
}
- static Matcher<Exception> computeJobFailedException(String causeClass,
String causeMsgSubstring) {
- return traceableException(ComputeException.class)
- .withCode(is(COMPUTE_JOB_FAILED_ERR))
- .withMessage(both(containsString("Job execution failed:"))
- .and(containsString(causeClass)))
- .withCause(hasMessage(containsString(causeMsgSubstring)));
+ Matcher<Exception> computeJobFailedException(String causeClass, String
causeMsgSubstring) {
+ return computeJobFailedException(clientType(), causeClass,
causeMsgSubstring);
+ }
+
+ static Matcher<Exception> computeJobFailedException(ClientType clientType,
String causeClass, String causeMsgSubstring) {
+ var msgMatcher = both(containsString("Job execution
failed:")).and(containsString(causeClass));
+ switch (clientType) {
+ case JAVA:
+ return publicException(
+ ComputeException.class,
+ COMPUTE_JOB_FAILED_ERR,
+ "",
+ List.of(new Cause(causeClass, causeMsgSubstring))
+ )
+ .withMessage(msgMatcher);
+ case EMBEDDED:
+ return traceableException(ComputeException.class)
+ .withCode(is(COMPUTE_JOB_FAILED_ERR))
+ .withMessage(msgMatcher)
+
.withCause(hasMessage(containsString(causeMsgSubstring)));
+ default:
+ throw new IllegalArgumentException("invalid clientType");
+ }
+ }
+
+ Matcher<Exception> computeJobCancelledException() {
+ return computeJobCancelledException(clientType());
}
- private static Matcher<Exception> computeJobCancelledException() {
- return traceableException(ComputeException.class)
- .withCode(is(COMPUTE_JOB_CANCELLED_ERR))
- .withMessage(containsString("Job execution cancelled"))
- .withCause(
- // Thin client exception transfers the class name in a
message of the cause,
- // embedded exception are instances in the cause chain
-
either(hasMessage(containsString(CancellationException.class.getName())))
- .or(instanceOf(CancellationException.class))
+ private static Matcher<Exception> computeJobCancelledException(ClientType
clientType) {
+ switch (clientType) {
+ case JAVA:
+ return publicException(
+ ComputeException.class,
+ COMPUTE_JOB_CANCELLED_ERR,
+ "Job execution cancelled",
+ List.of(Cause.of(CancellationException.class))
);
+ case EMBEDDED:
+ return traceableException(ComputeException.class)
+ .withCode(is(COMPUTE_JOB_CANCELLED_ERR))
+ .withMessage(containsString("Job execution cancelled"))
+ .withCause(instanceOf(CancellationException.class));
+ default:
+ throw new IllegalArgumentException("invalid clientType");
+ }
+ }
+
+ /** ClientType. */
+ public enum ClientType {
+ JAVA,
+ EMBEDDED,
}
private static Matcher<Exception> sqlCancelledException() {
return traceableException(SqlException.class)
.withCode(is(EXECUTION_CANCELLED_ERR))
- .withMessage(is("The query was cancelled while executing."));
+ .withMessage(containsString("The query was cancelled while
executing."));
}
}
diff --git
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeStandaloneTest.java
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeStandaloneTest.java
index ea87573194c..2780da3be3a 100644
---
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeStandaloneTest.java
+++
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeStandaloneTest.java
@@ -111,6 +111,11 @@ class ItComputeStandaloneTest extends ItComputeBaseTest {
return units;
}
+ @Override
+ protected ClientType clientType() {
+ return ClientType.EMBEDDED;
+ }
+
@Disabled("https://issues.apache.org/jira/browse/IGNITE-26546")
@Override
void executesFailingJobLocally() {
diff --git
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestClient.java
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestClient.java
index 1502718c99f..69c78bda59a 100644
---
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestClient.java
+++
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestClient.java
@@ -31,6 +31,11 @@ import org.junit.jupiter.api.AfterEach;
public class ItComputeTestClient extends ItComputeTestEmbedded {
private final Clients clients = new Clients();
+ @Override
+ protected ClientType clientType() {
+ return ClientType.JAVA;
+ }
+
@AfterEach
void stopClient() {
clients.cleanup();
diff --git
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestEmbedded.java
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestEmbedded.java
index 1f856afd549..151a0114529 100644
---
a/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestEmbedded.java
+++
b/modules/compute/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTestEmbedded.java
@@ -76,6 +76,11 @@ class ItComputeTestEmbedded extends ItComputeBaseTest {
return List.of();
}
+ @Override
+ protected ClientType clientType() {
+ return ClientType.EMBEDDED;
+ }
+
@SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
@Test
void changeJobPriorityLocally() {
@@ -232,7 +237,7 @@ class ItComputeTestEmbedded extends ItComputeBaseTest {
JobDescriptor.builder(CustomFailingJob.class).units(units()).build(),
null));
- assertThat(ex,
is(computeJobFailedException(throwable.getClass().getName(),
throwable.getMessage())));
+ assertThat(ex, is(computeJobFailedException(ClientType.EMBEDDED,
throwable.getClass().getName(), throwable.getMessage())));
}
@ParameterizedTest
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 16623becb5b..3b029ffc8ed 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
@@ -21,10 +21,18 @@ import static
org.apache.ignite.internal.util.ExceptionUtils.sneakyThrow;
import static org.apache.ignite.internal.util.ExceptionUtils.unwrapCause;
import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.util.Collection;
+import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
import org.apache.ignite.internal.lang.IgniteExceptionMapperUtil;
import org.apache.ignite.lang.IgniteCheckedException;
import org.apache.ignite.lang.IgniteException;
@@ -34,6 +42,8 @@ import org.apache.ignite.lang.TraceableException;
* Table view utilities.
*/
public final class ViewUtils {
+ private static final Map<Class<?>, Optional<MethodHandle>>
COPY_FACTORY_METHOD_CACHE = new ConcurrentHashMap<>();
+
/**
* Waits for async operation completion.
*
@@ -48,7 +58,7 @@ public final class ViewUtils {
Thread.currentThread().interrupt(); // Restore interrupt flag.
throw sneakyThrow(ensurePublicException(e));
- } catch (ExecutionException e) {
+ } catch (ExecutionException | CancellationException e) {
Throwable cause = unwrapCause(e);
throw sneakyThrow(ensurePublicException(cause));
@@ -56,8 +66,13 @@ public final class ViewUtils {
}
/**
- * Wraps an exception in an IgniteException, extracting trace identifier
and error code when the specified exception or one of its
- * causes is an IgniteException itself.
+ * Ensures the provided exception complies with our public API.
+ * <ol>
+ * <li>Errors caused by {@link IgniteException} and {@link
IgniteCheckedException} are treated as server-side exceptions.
+ * Their stack trace is rebased to the current stack trace.</li>
+ * <li>Other errors are mapped using {@link
IgniteExceptionMapperUtil#mapToPublicException(Throwable, Function)},
+ * are treated as client-side errors, and their stack trace is not
rebased.</li>
+ * </ol>
*
* @param e Internal exception.
* @return Public exception.
@@ -65,19 +80,12 @@ public final class ViewUtils {
public static Throwable ensurePublicException(Throwable e) {
Objects.requireNonNull(e);
- e = unwrapCause(e);
-
- if (e instanceof IgniteException) {
- return copyExceptionWithCauseIfPossible((IgniteException) e);
- }
-
- if (e instanceof IgniteCheckedException) {
- return copyExceptionWithCauseIfPossible((IgniteCheckedException)
e);
+ // Copy should rebase the stacktrace.
+ if (e instanceof IgniteException || e instanceof
IgniteCheckedException) {
+ return copyExceptionWithCauseIfPossible((Throwable &
TraceableException) e);
}
- var e0 = IgniteExceptionMapperUtil.mapToPublicException(e);
-
- return new IgniteException(INTERNAL_ERR, e0.getMessage(), e0);
+ return IgniteExceptionMapperUtil.mapToPublicException(e, ex -> new
IgniteException(INTERNAL_ERR, ex.getMessage(), ex));
}
/**
@@ -88,7 +96,20 @@ public final class ViewUtils {
*/
// 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);
+ // Copy exception with cause does not respect custom exception fields.
+ // TODO: IGNITE-28422 We should just create this during compile time
and call it a day
+ Optional<MethodHandle> copyMethodHandleOpt =
COPY_FACTORY_METHOD_CACHE.computeIfAbsent(e.getClass(),
+ ViewUtils::createMethodHandleForCopy);
+
+ if (copyMethodHandleOpt.isPresent()) {
+ try {
+ return (T) copyMethodHandleOpt.get().invoke(e);
+ } catch (Throwable ignored) {
+ // Intentionally left blank.
+ }
+ }
+
+ Throwable copy = ExceptionUtils.copyExceptionWithCause(e.getClass(),
e.traceId(), e.code(), e.getMessage(), e.getCause());
if (copy != null) {
return copy;
}
@@ -97,6 +118,21 @@ public final class ViewUtils {
+ e.getClass().getName(), e);
}
+ private static Optional<MethodHandle> createMethodHandleForCopy(Class<?>
type) {
+ try {
+ MethodHandles.Lookup privateLookup =
MethodHandles.privateLookupIn(type, MethodHandles.lookup());
+ MethodHandle mhandle = privateLookup.findStatic(
+ type,
+ "copy",
+ MethodType.methodType(type, type) // adjust signature
+ );
+
+ return Optional.of(mhandle);
+ } catch (IllegalAccessException | NoSuchMethodException ignored) {
+ return Optional.empty();
+ }
+ }
+
/**
* Checks that given keys collection isn't null and there is no a
null-value key.
*
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/util/ViewUtilsTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/util/ViewUtilsTest.java
index 9a5d89ae976..1bbc21e9a6c 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/util/ViewUtilsTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/util/ViewUtilsTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -47,7 +48,7 @@ public class ViewUtilsTest {
assertEquals(ex.getMessage(), resEx.getMessage());
assertThat(Arrays.asList(ex.getStackTrace()),
hasToString(containsString("throwIgniteException")));
assertThat(Arrays.asList(resEx.getStackTrace()),
hasToString(containsString("checkableTestMethod")));
- assertSame(ex.getClass(), resEx.getCause().getClass());
+ assertNull(resEx.getCause());
}
@Test
@@ -61,7 +62,7 @@ public class ViewUtilsTest {
assertEquals(ex.getMessage(), resEx.getMessage());
assertThat(Arrays.asList(ex.getStackTrace()),
hasToString(containsString("throwIgniteCheckedException")));
assertThat(Arrays.asList(resEx.getStackTrace()),
hasToString(containsString("checkableTestMethod")));
- assertSame(ex.getClass(), resEx.getCause().getClass());
+ assertNull(resEx.getCause());
}
@Test
@@ -75,8 +76,7 @@ public class ViewUtilsTest {
assertEquals(ex.getMessage(), resEx.getMessage());
assertThat(Arrays.asList(ex.getStackTrace()),
hasToString(containsString("throwRuntimeException")));
assertThat(Arrays.asList(resEx.getStackTrace()),
hasToString(containsString("checkableTestMethod")));
- assertSame(IgniteException.class, resEx.getCause().getClass());
- assertSame(ex.getClass(), resEx.getCause().getCause().getClass());
+ assertSame(ex, resEx.getCause());
}
@Test
@@ -89,8 +89,7 @@ public class ViewUtilsTest {
assertThat(resEx.getMessage(), containsString("Public Ignite
exception-derived class does not have required constructor"));
assertThat(Arrays.asList(ex.getStackTrace()),
hasToString(containsString("throwInvalidIgniteException")));
assertThat(Arrays.asList(resEx.getStackTrace()),
hasToString(containsString("checkableTestMethod")));
- assertSame(InvalidIgniteException.class, resEx.getCause().getClass());
- assertSame(ex.getClass(), resEx.getCause().getClass());
+ assertSame(ex, resEx.getCause());
}
/**
diff --git
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/IgniteExceptionTestUtils.java
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/IgniteExceptionTestUtils.java
index 9d3b6d09d93..dd20af60887 100644
---
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/IgniteExceptionTestUtils.java
+++
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/IgniteExceptionTestUtils.java
@@ -17,14 +17,31 @@
package org.apache.ignite.internal;
+import static org.apache.ignite.internal.util.ExceptionUtils.unwrapCause;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.util.ExceptionUtils;
import org.apache.ignite.lang.IgniteCheckedException;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.TraceableException;
+import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.table.KeyValueView;
+import org.apache.ignite.tx.RetriableTransactionException;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
+import org.jetbrains.annotations.Nullable;
/**
* Test utils for checking public exceptions.
@@ -67,6 +84,69 @@ public class IgniteExceptionTestUtils {
return traceableException(IgniteException.class, expectedCode,
containMessage);
}
+ /**
+ * Creates a matcher for public exceptions with stacktrace sent from the
server.
+ *
+ * @param expectedClass expected exception type.
+ * @param expectedCode expected code.
+ * @param containMessage message that exception should contain.
+ * @param causes Expected causes to be in the server sent stacktrace.
+ * @return message that exception should contain.
+ */
+ public static TraceableExceptionMatcher publicException(
+ Class<? extends TraceableException> expectedClass,
+ int expectedCode,
+ String containMessage,
+ List<Cause> causes
+ ) {
+ var ret = traceableException(expectedClass)
+ .withCode(is(expectedCode))
+ .withMessage(containsString(containMessage))
+ .withCause(
+ // Checks if is either null or a RetriableException
with no cause and same code and trace.
+ anyOf(
+ nullValue(Throwable.class),
+ allOf(
+
instanceOf(RetriableTransactionException.class),
+
traceableException(TraceableException.class)
+ .withCode(is(expectedCode))
+
.withCause(nullValue(Throwable.class))
+ )
+ )
+ );
+
+ for (var cause : causes) {
+ if (cause.message() != null) {
+ ret = ret.withMessage(containsString(String.format("Caused by:
%s: %s", cause.className(), cause.message())));
+ } else {
+ ret = ret.withMessage(containsString(String.format("Caused by:
%s", cause.className())));
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Creates an exception matcher with stacktrace not sent from the server.
+ *
+ * @param expectedClass expected exception type.
+ * @param expectedCode expected code.
+ * @param containMessage message that exception should contain.
+ * @return message that exception should contain.
+ */
+ public static TraceableExceptionMatcher publicExceptionWithHint(
+ Class<? extends TraceableException> expectedClass,
+ int expectedCode,
+ String containMessage
+ ) {
+ return traceableException(expectedClass)
+ .withCode(is(expectedCode))
+ .withMessage(containsString(containMessage))
+ .withMessage(containsString("To see the full stack trace, "
+ + "set
clientConnector.sendServerExceptionStackTraceToClient:true on the server"))
+ .withCause(nullValue(Throwable.class));
+ }
+
/**
* Creates a matcher that matches a public checked exception with expected
code and message.
*
@@ -90,4 +170,127 @@ public class IgniteExceptionTestUtils {
}
};
}
+
+ /**
+ * Creates a matcher that checks if a given exception complies with our
public guidelines.
+ *
+ * @return Matcher.
+ */
+ public static Matcher<Exception> anyPublicException() {
+ return allOf(
+ anyOf(instanceOf(IgniteException.class),
instanceOf(IgniteCheckedException.class)),
+ traceableException(TraceableException.class)
+ .withCause(
+ // Checks if is either null or a
RetriableException with no cause and same code and trace.
+ anyOf(
+ nullValue(Throwable.class),
+ allOf(
+
instanceOf(RetriableTransactionException.class),
+
traceableException(TraceableException.class)
+
.withCause(nullValue(Throwable.class))
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Wraps a KeyValueView with a proxy that checks that all exceptions
thrown by it comply with the public guidelines.
+ *
+ * @param view View.
+ * @param <K> KeyType.
+ * @param <V> ValueType.
+ * @return Proxy around the view.
+ */
+ public static <K, V> KeyValueView<K, V>
withPublicExceptionAssertions(KeyValueView<K, V> view) {
+ return (KeyValueView<K, V>) Proxy.newProxyInstance(
+ KeyValueView.class.getClassLoader(),
+ new Class<?>[]{KeyValueView.class},
+ new PublicExceptionCheckInvocationHandler<>(view)
+ );
+ }
+
+ /**
+ * Wraps a IgniteSql with a proxy that checks that all exceptions thrown
by it comply with the public guidelines.
+ *
+ * @param view IgniteSql instance..
+ * @return Proxy around the IgniteSql.
+ */
+ public static IgniteSql withPublicExceptionAssertions(IgniteSql view) {
+ return (IgniteSql) Proxy.newProxyInstance(
+ IgniteSql.class.getClassLoader(),
+ new Class<?>[]{IgniteSql.class},
+ new PublicExceptionCheckInvocationHandler<>(view)
+ );
+ }
+
+ private static class PublicExceptionCheckInvocationHandler<T> implements
InvocationHandler {
+ private final T target;
+
+ PublicExceptionCheckInvocationHandler(T target) {
+ this.target = target;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
+ boolean isAsync =
CompletableFuture.class.isAssignableFrom(method.getReturnType());
+
+ if (isAsync) {
+ try {
+ CompletableFuture<?> future = (CompletableFuture<?>)
method.invoke(target, args);
+ return future.handle((r, e) -> {
+ if (e != null) {
+ Exception ex = (Exception) unwrapCause(e);
+ assertThat(ex, anyPublicException());
+ ExceptionUtils.sneakyThrow(e);
+ }
+
+ return r;
+ });
+ } catch (InvocationTargetException e) {
+ return CompletableFuture.failedFuture(e);
+ }
+ } else {
+ try {
+ return method.invoke(target, args);
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (Exception e) {
+ assertThat(e, anyPublicException());
+ throw e;
+ }
+ }
+ }
+ }
+
+ /** Cause. */
+ public static class Cause {
+ private final String className;
+
+ // May be null, indicates no matcher will be used.
+ @Nullable
+ private final String message;
+
+ public Cause(String className, @Nullable String message) {
+ this.className = className;
+ this.message = message;
+ }
+
+ public String className() {
+ return className;
+ }
+
+ @Nullable
+ public String message() {
+ return message;
+ }
+
+ public static Cause of(Class<?> klass) {
+ return new Cause(klass.getName(), null);
+ }
+
+ public static Cause of(Class<?> klass, String message) {
+ return new Cause(klass.getName(), message);
+ }
+ }
}
diff --git
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/TraceableExceptionMatcher.java
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/TraceableExceptionMatcher.java
index 602db608294..4d600c1ca04 100644
---
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/TraceableExceptionMatcher.java
+++
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/TraceableExceptionMatcher.java
@@ -39,7 +39,7 @@ public class TraceableExceptionMatcher extends
TypeSafeMatcher<Exception> {
private Matcher<Integer> codeMatcher;
- private final Matcher<UUID> traceIdMatcher = is(notNullValue(UUID.class));
+ private Matcher<UUID> traceIdMatcher = is(notNullValue(UUID.class));
private Matcher<String> messageMatcher;
@@ -83,6 +83,11 @@ public class TraceableExceptionMatcher extends
TypeSafeMatcher<Exception> {
return this;
}
+ public TraceableExceptionMatcher withTraceId(Matcher<UUID> traceIdMatcher)
{
+ this.traceIdMatcher = traceIdMatcher;
+ return this;
+ }
+
@Override
protected boolean matchesSafely(Exception item) {
Throwable throwable = ExceptionUtils.unwrapCause(item);
@@ -99,6 +104,10 @@ public class TraceableExceptionMatcher extends
TypeSafeMatcher<Exception> {
}
private boolean matchesWithCause(Throwable e) {
+ if (e == null) {
+ return causeMatcher.matches(e);
+ }
+
for (Throwable current = e; current != null; current =
current.getCause()) {
if (causeMatcher.matches(current) ||
Arrays.stream(current.getSuppressed()).anyMatch(this::matchesWithCause)) {
return true;
diff --git
a/modules/security/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
b/modules/security/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
index a399cdfb838..1e5107c638e 100644
---
a/modules/security/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
+++
b/modules/security/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
@@ -17,12 +17,14 @@
package org.apache.ignite.internal.ssl;
+import static java.util.Collections.emptyList;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.escapeWindowsPath;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.getResourcePath;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrow;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willTimeoutIn;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static
org.apache.ignite.jdbc.util.JdbcTestUtils.assertThrowsSqlException;
+import static
org.apache.ignite.lang.ErrorGroups.Client.CLIENT_SSL_CONFIGURATION_ERR;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
@@ -46,6 +48,7 @@ import org.apache.ignite.internal.Cluster;
import org.apache.ignite.internal.Cluster.ServerRegistration;
import org.apache.ignite.internal.ClusterConfiguration;
import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
+import org.apache.ignite.internal.IgniteExceptionTestUtils;
import org.apache.ignite.internal.testframework.TestIgnitionManager;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
@@ -239,11 +242,15 @@ public class ItSslTest {
}
});
- assertThat(ex.getMessage(), is("Client SSL configuration error:
keystore password was incorrect"));
- // Exceptions thrown from the synchronous build method are copied
to include the sync method
- assertThat(ex.getCause(),
isA(IgniteClientConnectionException.class));
- assertThat(ex.getCause().getCause(), isA(IOException.class));
- assertThat(ex.getCause().getCause().getMessage(), is("keystore
password was incorrect"));
+ assertThat(ex,
+ IgniteExceptionTestUtils.publicException(
+ IgniteClientConnectionException.class,
+ CLIENT_SSL_CONFIGURATION_ERR,
+ "Client SSL configuration error: keystore password was
incorrect",
+ emptyList()
+ ).withCause(isA(IOException.class))
+ );
+ assertThat(ex.getCause().getMessage(), is("keystore password was
incorrect"));
}
@Test
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItPkOnlyTableCrossApiTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItPkOnlyTableCrossApiTest.java
index 3a4032aaa2d..fa76fca0be8 100644
---
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItPkOnlyTableCrossApiTest.java
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItPkOnlyTableCrossApiTest.java
@@ -17,12 +17,14 @@
package org.apache.ignite.internal.sql.engine;
+import static java.util.Collections.emptyList;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.publicException;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
+import static org.apache.ignite.lang.ErrorGroups.Marshalling.COMMON_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.CONSTRAINT_VIOLATION_ERR;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.any;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -157,7 +159,8 @@ public class ItPkOnlyTableCrossApiTest extends
BaseSqlIntegrationTest {
rwTx -> {
IgniteException ex = assertThrows(IgniteException.class,
() -> tab.keyValueView(KeyObject.class,
Integer.class).put(rwTx, key, 1));
- assertThat(ex.getCause().getCause(),
is(instanceOf(MarshallerException.class)));
+
+ assertThat(ex, publicException(MarshallerException.class,
COMMON_ERR, "", emptyList()).withMessage(any(String.class)));
kvView.put(rwTx, key, null);
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
index 6f5a91efc93..41427f90eeb 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/KeyValueViewImpl.java
@@ -611,7 +611,11 @@ public class KeyValueViewImpl<K, V> extends
AbstractTableView<Entry<K, V>> imple
marsh = marshallerFactory.apply(registry.schema(schemaVersion));
this.marsh = marsh;
} catch (Exception ex) {
- throw new MarshallerException(ex.getMessage(), ex);
+ if (ex instanceof MarshallerException) {
+ throw ex;
+ } else {
+ throw new MarshallerException(ex.getMessage(), ex);
+ }
}
return marsh;
diff --git
a/modules/transactions/src/integrationTest/java/org/apache/ignite/internal/tx/ItRunInTransactionTest.java
b/modules/transactions/src/integrationTest/java/org/apache/ignite/internal/tx/ItRunInTransactionTest.java
index 271382da0e5..bcc9148f453 100644
---
a/modules/transactions/src/integrationTest/java/org/apache/ignite/internal/tx/ItRunInTransactionTest.java
+++
b/modules/transactions/src/integrationTest/java/org/apache/ignite/internal/tx/ItRunInTransactionTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.tx;
import static java.lang.String.format;
+import static
org.apache.ignite.internal.IgniteExceptionTestUtils.withPublicExceptionAssertions;
import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrowWithCauseOrSuppressed;
import static
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
@@ -230,24 +231,24 @@ public class ItRunInTransactionTest extends
ClusterPerTestIntegrationTest {
}
private static CompletableFuture<Void> putSqlAsync(Ignite client,
Transaction tx, Tuple key) {
- return client.sql()
+ return withPublicExceptionAssertions(client.sql())
.executeAsync(tx, format("INSERT INTO %s (%s, %s) VALUES (?,
?)", TABLE_NAME, COLUMN_KEY, COLUMN_VAL), key.intValue(0),
key.intValue(0) + "").thenApply(r -> null);
}
private static Void putKv(Ignite client, Transaction tx, Tuple key) {
- client.tables().tables().get(0).keyValueView().put(tx, key,
val(key.intValue(0) + ""));
+
withPublicExceptionAssertions(client.tables().tables().get(0).keyValueView()).put(tx,
key, val(key.intValue(0) + ""));
return null;
}
private static Void putSql(Ignite client, @Nullable Transaction tx, Tuple
key) {
- client.sql()
+ withPublicExceptionAssertions(client.sql())
.execute(tx, format("INSERT INTO %s (%s, %s) VALUES (?, ?)",
TABLE_NAME, COLUMN_KEY, COLUMN_VAL), key.intValue(0),
key.intValue(0) + "");
return null;
}
private static CompletableFuture<Void> putKvAsync(Ignite client,
Transaction tx, Tuple key) {
- return client.tables().tables().get(0).keyValueView().putAsync(tx,
key, val(key.intValue(0) + ""));
+ return
withPublicExceptionAssertions(client.tables().tables().get(0).keyValueView()).putAsync(tx,
key, val(key.intValue(0) + ""));
}
}