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)));
+    }
+}

Reply via email to