This is an automated email from the ASF dual-hosted git repository.
ashapkin 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 81b6836576c IGNITE-26345 Wrap ExceptionInInitializerError that happens
in contructor of IgniteImpl when launched without JVM options "--add-opens=..."
(#7606)
81b6836576c is described below
commit 81b6836576c1577df2f6b1fd2d4ae2bb41ebd4f3
Author: Mikhail Efremov <[email protected]>
AuthorDate: Tue Mar 3 00:48:49 2026 +0600
IGNITE-26345 Wrap ExceptionInInitializerError that happens in contructor of
IgniteImpl when launched without JVM options "--add-opens=..." (#7606)
---
.../ignite/internal/util/ExceptionUtils.java | 27 ++++
.../internal/testframework/IgniteTestUtils.java | 6 +-
.../ignite/internal/app/IgniteServerImpl.java | 81 +++++++++-
.../ignite/internal/app/IgniteServerStartTest.java | 174 +++++++++++++++++++++
4 files changed, 277 insertions(+), 11 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/util/ExceptionUtils.java
b/modules/core/src/main/java/org/apache/ignite/internal/util/ExceptionUtils.java
index 07c61e925f1..5146d87e5a9 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/util/ExceptionUtils.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/util/ExceptionUtils.java
@@ -279,6 +279,7 @@ public final class ExceptionUtils {
public static List<Throwable> getThrowableList(Throwable throwable) {
List<Throwable> list = new ArrayList<>();
+ // TODO: https://issues.apache.org/jira/browse/IGNITE-28026
while (throwable != null && !list.contains(throwable)) {
list.add(throwable);
throwable = getCause(throwable);
@@ -300,6 +301,7 @@ public final class ExceptionUtils {
return result;
}
+ // TODO: https://issues.apache.org/jira/browse/IGNITE-28026
do {
for (Throwable suppressed : t.getSuppressed()) {
result.add(suppressed);
@@ -464,6 +466,7 @@ public final class ExceptionUtils {
* @return Unwrapped throwable.
*/
public static Throwable unwrapCause(Throwable e) {
+ // TODO: https://issues.apache.org/jira/browse/IGNITE-28026
while ((e instanceof CompletionException || e instanceof
ExecutionException) && e.getCause() != null) {
e = e.getCause();
}
@@ -471,6 +474,29 @@ public final class ExceptionUtils {
return e;
}
+ /**
+ * Unwraps exception cause until the given cause type from the given
wrapper exception. If there is no any cause of the expected type
+ * then {@code null} will be returned.
+ *
+ * @param e The exception to unwrap.
+ * @param causeType Expected type of a cause to look up.
+ * @return The desired cause of the exception or {@code null} if it wasn't
found.
+ */
+ public static @Nullable <T extends Throwable> T unwrapCause(Throwable e,
Class<T> causeType) {
+ Throwable cause = e;
+
+ // TODO: https://issues.apache.org/jira/browse/IGNITE-28026
+ while (!causeType.isAssignableFrom(cause.getClass()) &&
cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+
+ if (!causeType.isInstance(cause)) {
+ return null;
+ }
+
+ return (T) cause;
+ }
+
/**
* Unwraps the root cause of the given exception.
*
@@ -484,6 +510,7 @@ public final class ExceptionUtils {
return e;
}
+ // TODO: https://issues.apache.org/jira/browse/IGNITE-28026
while (th != e) {
Throwable t = th;
th = t.getCause();
diff --git
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
index 9a26e685fee..e0ba820cbd4 100644
---
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
+++
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/IgniteTestUtils.java
@@ -274,12 +274,12 @@ public final class IgniteTestUtils {
* @param errorMessageFragment Fragment of the error text in the expected
exception, {@code null} if not to be checked.
* @return Thrown throwable.
*/
- public static Throwable assertThrows(
- Class<? extends Throwable> cls,
+ public static <T extends Throwable> T assertThrows(
+ Class<T> cls,
Executable run,
@Nullable String errorMessageFragment
) {
- Throwable throwable = Assertions.assertThrows(cls, run);
+ T throwable = Assertions.assertThrows(cls, run);
if (errorMessageFragment != null) {
assertThat(throwable.getMessage(),
containsString(errorMessageFragment));
diff --git
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java
index 193839cf5d2..3d17644174c 100644
---
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java
+++
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteServerImpl.java
@@ -22,7 +22,9 @@ import static java.util.Objects.requireNonNull;
import static java.util.concurrent.CompletableFuture.failedFuture;
import static
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
import static
org.apache.ignite.internal.util.ExceptionUtils.copyExceptionWithCause;
+import static org.apache.ignite.internal.util.ExceptionUtils.hasCause;
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.io.IOException;
@@ -458,20 +460,15 @@ public class IgniteServerImpl implements IgniteServer {
return null;
} else {
- throw handleStartException(e);
+ throw handleClusterStartException(e);
}
});
} catch (Exception e) {
- throw handleStartException(e);
+ throw handleClusterStartException(e);
}
}
- @Override
- public void start() {
- sync(startAsync());
- }
-
- private static IgniteException handleStartException(Throwable e) {
+ private static IgniteException handleClusterStartException(Throwable e) {
if (e instanceof IgniteException) {
return (IgniteException) e;
} else {
@@ -479,6 +476,25 @@ public class IgniteServerImpl implements IgniteServer {
}
}
+ @Override
+ public void start() {
+ CompletableFuture<Void> startFuture =
startAsync().handle(IgniteServerImpl::handleNodeStartException);
+
+ sync(startFuture);
+ }
+
+ private static Void handleNodeStartException(Void v, Throwable e) {
+ if (e == null) {
+ return v;
+ }
+
+ throwIfError(e);
+
+ sneakyThrow(e);
+
+ return v;
+ }
+
private static void ackSuccessStart() {
LOG.info("Apache Ignite started successfully!");
}
@@ -617,12 +633,61 @@ public class IgniteServerImpl implements IgniteServer {
try {
future.get();
} catch (ExecutionException e) {
+ if (hasCause(e, NodeStartException.class)) {
+ sneakyThrow(e.getCause());
+ }
+
throw sneakyThrow(tryToCopyExceptionWithCause(e));
} catch (InterruptedException e) {
throw sneakyThrow(e);
}
}
+ private static void throwIfError(Throwable exception) {
+ Error error = unwrapCause(exception, Error.class);
+
+ if (error == null) {
+ return;
+ }
+
+ throwIfExceptionInInitializerError(error);
+
+ throw new NodeStartException(
+ "Error occurred during node start, make sure that classpath
and JVM execution arguments are correct.",
+ error
+ );
+ }
+
+ private static void throwIfExceptionInInitializerError(Error error) {
+ ExceptionInInitializerError initializerError = unwrapCause(error,
ExceptionInInitializerError.class);
+
+ if (initializerError == null) {
+ return;
+ }
+
+ Throwable initializerErrorCause = initializerError.getCause();
+
+ if (initializerErrorCause == null) {
+ throw new NodeStartException(
+ "Error during static components initialization with
unknown cause, "
+ + "make sure that classpath and JVM execution
arguments are correct.",
+ initializerError
+ );
+ }
+
+ if (initializerErrorCause instanceof IllegalAccessException) {
+ throw new NodeStartException(
+ "Error during static components initialization due to
illegal code access, check --add-opens JVM execution arguments.",
+ initializerErrorCause
+ );
+ }
+
+ throw new NodeStartException(
+ "Error during static components initialization, make sure that
classpath and JVM execution arguments are correct.",
+ initializerErrorCause
+ );
+ }
+
// TODO: remove after IGNITE-22721 gets resolved.
private static Throwable tryToCopyExceptionWithCause(ExecutionException
exception) {
Throwable copy = copyExceptionWithCause(exception);
diff --git
a/modules/runner/src/test/java/org/apache/ignite/internal/app/IgniteServerStartTest.java
b/modules/runner/src/test/java/org/apache/ignite/internal/app/IgniteServerStartTest.java
new file mode 100644
index 00000000000..0f2191ddba5
--- /dev/null
+++
b/modules/runner/src/test/java/org/apache/ignite/internal/app/IgniteServerStartTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.app;
+
+import static java.util.concurrent.CompletableFuture.failedFuture;
+import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrows;
+import static
org.apache.ignite.internal.testframework.TestIgnitionManager.DEFAULT_CONFIG_NAME;
+import static
org.apache.ignite.internal.testframework.TestIgnitionManager.writeConfigurationFile;
+import static org.apache.ignite.internal.util.Constants.MiB;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalToObject;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.ignite.IgniteServer;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.lang.NodeStartException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Test for {@link IgniteServer} starting and errors handling during the
process.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+public class IgniteServerStartTest extends BaseIgniteAbstractTest {
+ private static final int IGNITE_SERVER_PORT = 3344;
+
+ private static final String IGNITE_SERVER_NAME = "test-node-" +
IGNITE_SERVER_PORT;
+
+ private static final String IGNITE_SERVER_CONFIGURATION = "ignite {\n"
+ + " network: {\n"
+ + " port: " + IGNITE_SERVER_PORT + ",\n"
+ + " nodeFinder.netClusterNodes: [ \"localhost:" +
IGNITE_SERVER_PORT + "\" ]\n"
+ + " },\n"
+ + " clientConnector.port: 10800,\n"
+ + " rest.port: 10300,\n"
+ + " failureHandler.dumpThreadsOnFailure: false,\n"
+ + " storage.profiles.default {engine: aipersist, sizeBytes: " +
256 * MiB + "}\n"
+ + "}";
+
+ @WorkDirectory
+ private static Path workDir;
+
+ private IgniteServer server;
+
+ @BeforeEach
+ public void createIgniteServerMock() throws IOException {
+ server = spy(createIgniteServer());
+ }
+
+ private static IgniteServer createIgniteServer() throws IOException {
+ Files.createDirectories(workDir);
+ Path configPath = workDir.resolve(DEFAULT_CONFIG_NAME);
+
+ writeConfigurationFile(IGNITE_SERVER_CONFIGURATION, configPath);
+
+ return IgniteServer
+ .builder(IGNITE_SERVER_NAME, configPath, workDir)
+ .build();
+ }
+
+ @AfterEach
+ public void shutdownIgniteServerMock() {
+ if (server == null) {
+ return;
+ }
+
+ reset(server);
+
+ server.shutdown();
+ }
+
+ @Test
+ void igniteServerStartTest() {
+ Assertions.assertDoesNotThrow(() -> server.start());
+ }
+
+ @Test
+ void errorDuringIgniteServerStartTest() {
+ Error error = new Error("Test error.");
+
+ when(server.startAsync()).thenReturn(failedFuture(error));
+
+ NodeStartException exception = assertThrows(
+ NodeStartException.class,
+ server::start,
+ "Error occurred during node start, make sure that classpath
and JVM execution arguments are correct."
+ );
+
+ Throwable cause = exception.getCause();
+ assertThat(cause, is(notNullValue()));
+ assertThat(cause, is(equalToObject(error)));
+ }
+
+ @Test
+ void initializerErrorWithoutCauseDuringIgniteServerStartTest() {
+ ExceptionInInitializerError initializerError = new
ExceptionInInitializerError("Test initializer error.");
+
+ when(server.startAsync()).thenReturn(failedFuture(initializerError));
+
+ NodeStartException exception = assertThrows(
+ NodeStartException.class,
+ server::start,
+ "Error during static components initialization with unknown
cause, "
+ + "make sure that classpath and JVM execution
arguments are correct."
+ );
+
+ Throwable cause = exception.getCause();
+ assertThat(cause, is(notNullValue()));
+ assertThat(cause, is(equalToObject(initializerError)));
+ }
+
+ @Test
+ void initializerErrorWithIllegalAccessCauseDuringIgniteServerStartTest() {
+ IllegalAccessException illegalAccessCause = new
IllegalAccessException("Test illegal access exception.");
+ ExceptionInInitializerError initializerError = new
ExceptionInInitializerError(illegalAccessCause);
+
+ when(server.startAsync()).thenReturn(failedFuture(initializerError));
+
+ NodeStartException exception = assertThrows(
+ NodeStartException.class,
+ server::start,
+ "Error during static components initialization due to illegal
code access, check --add-opens JVM execution arguments."
+ );
+
+ Throwable cause = exception.getCause();
+ assertThat(cause, is(notNullValue()));
+ assertThat(cause, is(equalToObject(illegalAccessCause)));
+ }
+
+ @Test
+ void initializerErrorWithCommonCauseDuringIgniteServerStartTest() {
+ Throwable commonCause = new Throwable("Test common exception.");
+ ExceptionInInitializerError initializerError = new
ExceptionInInitializerError(commonCause);
+
+ when(server.startAsync()).thenReturn(failedFuture(initializerError));
+
+ NodeStartException exception = assertThrows(
+ NodeStartException.class,
+ server::start,
+ "Error during static components initialization, make sure that
classpath and JVM execution arguments are correct."
+ );
+
+ Throwable cause = exception.getCause();
+ assertThat(cause, is(notNullValue()));
+ assertThat(cause, is(equalToObject(commonCause)));
+ }
+}