Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add caller stacktrace to rethrown RuntimeException #17249

Merged
merged 2 commits into from Jul 23, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -24,4 +24,8 @@ public class HazelcastClientOfflineException extends IllegalStateException {
public HazelcastClientOfflineException() {
super("No connection found to cluster");
}

public HazelcastClientOfflineException(Throwable cause) {
super(cause);
}
}
Expand Up @@ -19,10 +19,14 @@
import com.hazelcast.core.HazelcastException;
import com.hazelcast.instance.impl.OutOfMemoryErrorDispatcher;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.impl.operationservice.WrappableException;

import javax.annotation.Nonnull;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
Expand All @@ -33,6 +37,14 @@
*/
public final class ExceptionUtil {

private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup();
// new Throwable(String message, Throwable cause)
private static final MethodType MT_INIT_STRING_THROWABLE = MethodType.methodType(void.class, String.class, Throwable.class);
// new Throwable(Throwable cause)
private static final MethodType MT_INIT_THROWABLE = MethodType.methodType(void.class, Throwable.class);
// new Throwable(String message)
private static final MethodType MT_INIT_STRING = MethodType.methodType(void.class, String.class);

private static final BiFunction<Throwable, String, HazelcastException> HAZELCAST_EXCEPTION_WRAPPER = (throwable, message) -> {
if (message != null) {
return new HazelcastException(message, throwable);
Expand Down Expand Up @@ -99,7 +111,7 @@ public static <T extends Throwable> Throwable peel(final Throwable t, Class<T> a
public static <T, W extends Throwable> Throwable peel(final Throwable t, Class<T> allowedType,
String message, BiFunction<Throwable, String, W> exceptionWrapper) {
if (t instanceof RuntimeException) {
return t;
return wrapException(t, message, exceptionWrapper);
}

if (t instanceof ExecutionException || t instanceof InvocationTargetException) {
Expand All @@ -118,7 +130,20 @@ public static <T, W extends Throwable> Throwable peel(final Throwable t, Class<T
return exceptionWrapper.apply(t, message);
}

public static RuntimeException rethrow(Throwable t) {
public static <W extends Throwable> Throwable wrapException(Throwable t, String message,
BiFunction<Throwable, String, W> exceptionWrapper) {
if (t instanceof WrappableException) {
return ((WrappableException) t).wrap();
}
Throwable wrapped = tryWrapInSameClass(t);
return wrapped == null ? exceptionWrapper.apply(t, message) : wrapped;
}

public static RuntimeException wrapException(RuntimeException t) {
return (RuntimeException) wrapException(t, null, HAZELCAST_EXCEPTION_WRAPPER);
}

public static RuntimeException rethrow(final Throwable t) {
rethrowIfError(t);
throw peel(t);
}
Expand Down Expand Up @@ -151,10 +176,15 @@ public static void rethrowIfError(final Throwable t) {
if (t instanceof OutOfMemoryError) {
OutOfMemoryErrorDispatcher.onOutOfMemory((OutOfMemoryError) t);
}
throw (Error) t;
throw wrapError((Error) t);
}
}

public static Error wrapError(Error cause) {
Error result = tryWrapInSameClass(cause);
return result == null ? cause : result;
}

public static RuntimeException rethrowAllowInterrupted(final Throwable t) throws InterruptedException {
return rethrow(t, InterruptedException.class);
}
Expand Down Expand Up @@ -194,4 +224,27 @@ public static <T extends Throwable> RuntimeException sneakyThrow(@Nonnull Throwa
}
};
}

public static <T extends Throwable> T tryWrapInSameClass(T cause) {
Class<? extends Throwable> exceptionClass = cause.getClass();
MethodHandle constructor;
try {
constructor = LOOKUP.findConstructor(exceptionClass, MT_INIT_STRING_THROWABLE);
return (T) constructor.invokeWithArguments(cause.getMessage(), cause);
} catch (Throwable ignored) {
}
try {
constructor = LOOKUP.findConstructor(exceptionClass, MT_INIT_THROWABLE);
return (T) constructor.invokeWithArguments(cause);
} catch (Throwable ignored) {
}
try {
constructor = LOOKUP.findConstructor(exceptionClass, MT_INIT_STRING);
T result = (T) constructor.invokeWithArguments(cause.getMessage());
result.initCause(cause);
return result;
} catch (Throwable ignored) {
}
return null;
}
}
Expand Up @@ -17,13 +17,15 @@
package com.hazelcast.ringbuffer;

import com.hazelcast.spi.exception.SilentException;
import com.hazelcast.spi.impl.operationservice.WrappableException;

/**
* An {@link RuntimeException} that is thrown when accessing an item in the {@link Ringbuffer} using a sequence that is smaller
* than the current head sequence and that the ringbuffer store is disabled. This means that the item isn't available in the
* ringbuffer and it cannot be loaded from the store either, thus being completely unavailable.
*/
public class StaleSequenceException extends RuntimeException implements SilentException {
public class StaleSequenceException extends RuntimeException
implements SilentException, WrappableException<StaleSequenceException> {

private final long headSeq;

Expand All @@ -47,4 +49,11 @@ public StaleSequenceException(String message, long headSeq) {
public long getHeadSeq() {
return headSeq;
}

@Override
public StaleSequenceException wrap() {
StaleSequenceException staleSequenceException = new StaleSequenceException(getMessage(), headSeq);
staleSequenceException.initCause(this);
return staleSequenceException;
}
}
Expand Up @@ -22,15 +22,10 @@
import com.hazelcast.instance.impl.OutOfMemoryErrorDispatcher;
import com.hazelcast.internal.util.executor.UnblockableThread;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.impl.operationservice.WrappableException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
Expand All @@ -51,6 +46,8 @@

import static com.hazelcast.internal.util.ConcurrencyUtil.DEFAULT_ASYNC_EXECUTOR;
import static com.hazelcast.internal.util.ExceptionUtil.sneakyThrow;
import static com.hazelcast.internal.util.ExceptionUtil.wrapError;
import static com.hazelcast.internal.util.ExceptionUtil.wrapException;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
import static java.util.concurrent.locks.LockSupport.park;
Expand All @@ -59,6 +56,7 @@

/**
* Custom implementation of {@link java.util.concurrent.CompletableFuture}.
*
* @param <V>
*/
@SuppressFBWarnings(value = "DLS_DEAD_STORE_OF_CLASS_LITERAL", justification = "Recommended way to prevent classloading bug")
Expand All @@ -71,13 +69,6 @@ public String toString() {
return "UNRESOLVED";
}
};
private static final Lookup LOOKUP = MethodHandles.publicLookup();
// new Throwable(String message, Throwable cause)
private static final MethodType MT_INIT_STRING_THROWABLE = MethodType.methodType(void.class, String.class, Throwable.class);
// new Throwable(Throwable cause)
private static final MethodType MT_INIT_THROWABLE = MethodType.methodType(void.class, Throwable.class);
// new Throwable(String message)
private static final MethodType MT_INIT_STRING = MethodType.methodType(void.class, String.class);

private static final AtomicReferenceFieldUpdater<AbstractInvocationFuture, Object> STATE_UPDATER =
newUpdater(AbstractInvocationFuture.class, Object.class, "state");
Expand Down Expand Up @@ -1912,7 +1903,7 @@ static Throwable wrapOrPeel(Throwable cause) {
return cause;
}
if (cause instanceof RuntimeException) {
return wrapRuntimeException((RuntimeException) cause);
return wrapException((RuntimeException) cause);
}
if ((cause instanceof ExecutionException || cause instanceof InvocationTargetException)
&& cause.getCause() != null) {
Expand All @@ -1927,39 +1918,4 @@ static Throwable wrapOrPeel(Throwable cause) {
return new HazelcastException(cause);
}

private static RuntimeException wrapRuntimeException(RuntimeException cause) {
if (cause instanceof WrappableException) {
return ((WrappableException) cause).wrap();
}
RuntimeException wrapped = tryWrapInSameClass(cause);
return wrapped == null ? new HazelcastException(cause) : wrapped;
}

private static Error wrapError(Error cause) {
Error result = tryWrapInSameClass(cause);
return result == null ? cause : result;
}

private static <T extends Throwable> T tryWrapInSameClass(T cause) {
Class<? extends Throwable> exceptionClass = cause.getClass();
MethodHandle constructor;
try {
constructor = LOOKUP.findConstructor(exceptionClass, MT_INIT_STRING_THROWABLE);
return (T) constructor.invokeWithArguments(cause.getMessage(), cause);
} catch (Throwable ignored) {
}
try {
constructor = LOOKUP.findConstructor(exceptionClass, MT_INIT_THROWABLE);
return (T) constructor.invokeWithArguments(cause);
} catch (Throwable ignored) {
}
try {
constructor = LOOKUP.findConstructor(exceptionClass, MT_INIT_STRING);
T result = (T) constructor.invokeWithArguments(cause.getMessage());
result.initCause(cause);
return result;
} catch (Throwable ignored) {
}
return null;
}
}
Expand Up @@ -22,13 +22,13 @@
import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.internal.util.FilteringClassLoader;
import com.hazelcast.map.IMap;
import com.hazelcast.nio.serialization.HazelcastSerializationException;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.ParallelJVMTest;
import com.hazelcast.test.annotation.QuickTest;
import com.hazelcast.internal.util.FilteringClassLoader;
import org.junit.After;
import org.junit.Test;
import org.junit.experimental.categories.Category;
Expand All @@ -38,8 +38,6 @@
import java.io.FileNotFoundException;

import static java.util.Collections.singletonList;
import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertEquals;

@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelJVMTest.class})
Expand All @@ -52,7 +50,7 @@ public void tearDown() throws Exception {
factory.terminateAll();
}

@Test
@Test(expected = HazelcastSerializationException.class)
public void testUserCodeDeploymentIsDisabledByDefaultOnClient() {
// this test also validate the EP is filtered locally and has to be loaded from the other member
ClientConfig clientConfig = new ClientConfig();
Expand All @@ -64,12 +62,7 @@ public void testUserCodeDeploymentIsDisabledByDefaultOnClient() {
HazelcastInstance client = factory.newHazelcastClient(clientConfig);

IMap<Integer, Integer> map = client.getMap(randomName());
try {
map.executeOnEntries(incrementingEntryProcessor);
fail();
} catch (HazelcastSerializationException e) {
assertEquals(ClassNotFoundException.class, e.getCause().getClass());
}
map.executeOnEntries(incrementingEntryProcessor);
}

private Config createNodeConfig() {
Expand Down
Expand Up @@ -54,14 +54,16 @@ public void testToString() {
public void testPeel_whenThrowableIsRuntimeException_thenReturnOriginal() {
RuntimeException result = ExceptionUtil.peel(throwable);

assertEquals(throwable, result);
assertEquals(throwable.getMessage(), result.getMessage());
assertEquals(throwable.getClass(), result.getClass());
}

@Test
public void testPeel_whenThrowableIsExecutionException_thenReturnCause() {
RuntimeException result = ExceptionUtil.peel(new ExecutionException(throwable));

assertEquals(throwable, result);
assertEquals(throwable.getMessage(), result.getMessage());
assertEquals(throwable.getClass(), result.getClass());
}

@Test
Expand Down
Expand Up @@ -55,7 +55,6 @@
import static com.hazelcast.transaction.TransactionOptions.TransactionType.ONE_PHASE;
import static com.hazelcast.transaction.TransactionOptions.TransactionType.TWO_PHASE;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.core.Is.isA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

Expand All @@ -69,7 +68,6 @@ public class TransactionsWithWriteBehind_whenNoCoalescingQueueIsFullTest extends
@Test
public void prepare_step_throws_reached_max_size_exception_when_two_phase() {
expectedException.expect(TransactionException.class);
expectedException.expectCause(isA(ReachedMaxSizeException.class));

String mapName = "map";
long maxWbqCapacity = 100;
Expand Down
Expand Up @@ -89,7 +89,7 @@ public void get_throwsGivenException_whenUncheckedExceptionSet() throws Exceptio
try {
future.get();
} catch (Exception e) {
assertSame(error, e);
assertSame(error.getClass(), e.getClass());
}
}

Expand Down