Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ public static Throwable getCause(@Nullable Throwable throwable, String[] mtdName
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);
Expand All @@ -300,6 +301,7 @@ public static List<Throwable> getSuppressedList(@Nullable Throwable t) {
return result;
}

// TODO: https://issues.apache.org/jira/browse/IGNITE-28026
do {
for (Throwable suppressed : t.getSuppressed()) {
result.add(suppressed);
Expand Down Expand Up @@ -464,13 +466,37 @@ private static boolean hasCauseOrSuppressedInternal(
* @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();
}

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.
*
Expand All @@ -484,6 +510,7 @@ public static Throwable unwrapRootCause(Throwable e) {
return e;
}

// TODO: https://issues.apache.org/jira/browse/IGNITE-28026
while (th != e) {
Throwable t = th;
th = t.getCause();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,12 @@ public static <T> T getFieldValue(@Nullable Object obj, String... fieldNames) {
* @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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
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;
Expand Down Expand Up @@ -458,27 +460,41 @@ private void doWaitForInitAsync(IgniteImpl instance) {

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 {
return new NodeStartException("Error during node start.", e);
}
}

@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!");
}
Expand Down Expand Up @@ -617,12 +633,61 @@ private static void sync(CompletableFuture<Void> future) {
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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)));
}
}