Carry on stacktrace and suppressed exceptions if exception is not serializable

This commit is contained in:
Simon Willnauer 2015-06-30 14:43:42 +02:00
parent 6ee9a3d5f2
commit 0ac8c1bc55
5 changed files with 70 additions and 6 deletions

View File

@ -25,7 +25,10 @@ import java.io.IOException;
/** /**
* This exception can be used to wrap a given, not serializable exception * This exception can be used to wrap a given, not serializable exception
* to serialize via {@link StreamOutput#writeThrowable(Throwable)} * to serialize via {@link StreamOutput#writeThrowable(Throwable)}.
* This class will perserve the stacktrace as well as the suppressed exceptions of
* the throwable it was created with instead of it's own. The stacktrace has no indication
* of where this exception was created.
*/ */
public final class NotSerializableExceptionWrapper extends ElasticsearchException { public final class NotSerializableExceptionWrapper extends ElasticsearchException {
@ -34,6 +37,10 @@ public final class NotSerializableExceptionWrapper extends ElasticsearchExceptio
public NotSerializableExceptionWrapper(Throwable other) { public NotSerializableExceptionWrapper(Throwable other) {
super(other.getMessage(), other.getCause()); super(other.getMessage(), other.getCause());
this.name = ElasticsearchException.getExceptionName(other); this.name = ElasticsearchException.getExceptionName(other);
setStackTrace(other.getStackTrace());
for (Throwable otherSuppressed : other.getSuppressed()) {
addSuppressed(otherSuppressed);
}
} }
public NotSerializableExceptionWrapper(StreamInput in) throws IOException { public NotSerializableExceptionWrapper(StreamInput in) throws IOException {

View File

@ -19,6 +19,8 @@
package org.elasticsearch.common.io.stream; package org.elasticsearch.common.io.stream;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexFormatTooNewException; import org.apache.lucene.index.IndexFormatTooNewException;
import org.apache.lucene.index.IndexFormatTooOldException; import org.apache.lucene.index.IndexFormatTooOldException;

View File

@ -39,6 +39,8 @@ import org.elasticsearch.indices.IndexMissingException;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.SearchShardTarget;
import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
import org.elasticsearch.transport.RemoteTransportException; import org.elasticsearch.transport.RemoteTransportException;
import org.junit.Test; import org.junit.Test;
@ -293,9 +295,10 @@ public class ElasticsearchExceptionTests extends ElasticsearchTestCase {
assertEquals(e.getCause().getClass(), e.getCause().getClass()); assertEquals(e.getCause().getClass(), e.getCause().getClass());
assertArrayEquals(e.getStackTrace(), ex.getStackTrace()); assertArrayEquals(e.getStackTrace(), ex.getStackTrace());
assertTrue(e.getStackTrace().length > 1); assertTrue(e.getStackTrace().length > 1);
ElasticsearchAssertions.assertVersionSerializable(VersionUtils.randomVersion(getRandom()), t);
ElasticsearchAssertions.assertVersionSerializable(VersionUtils.randomVersion(getRandom()), ex);
ElasticsearchAssertions.assertVersionSerializable(VersionUtils.randomVersion(getRandom()), e);
} }
} }
} }

View File

@ -18,14 +18,16 @@
*/ */
package org.elasticsearch; package org.elasticsearch;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.codehaus.groovy.runtime.typehandling.GroovyCastException;
import org.elasticsearch.action.FailedNodeException; import org.elasticsearch.action.FailedNodeException;
import org.elasticsearch.action.RoutingMissingException; import org.elasticsearch.action.RoutingMissingException;
import org.elasticsearch.action.TimestampParsingException; import org.elasticsearch.action.TimestampParsingException;
import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.metadata.SnapshotId;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
@ -33,7 +35,6 @@ import org.elasticsearch.cluster.routing.*;
import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.breaker.CircuitBreakingException;
import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.io.stream.*; import org.elasticsearch.common.io.stream.*;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.xcontent.*; import org.elasticsearch.common.xcontent.*;
@ -63,6 +64,8 @@ import org.elasticsearch.search.warmer.IndexWarmerMissingException;
import org.elasticsearch.snapshots.SnapshotException; import org.elasticsearch.snapshots.SnapshotException;
import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.TestSearchContext; import org.elasticsearch.test.TestSearchContext;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
import org.elasticsearch.transport.ActionNotFoundTransportException; import org.elasticsearch.transport.ActionNotFoundTransportException;
import org.elasticsearch.transport.ActionTransportException; import org.elasticsearch.transport.ActionTransportException;
import org.elasticsearch.transport.ConnectTransportException; import org.elasticsearch.transport.ConnectTransportException;
@ -179,7 +182,8 @@ public class ExceptionSerializationTests extends ElasticsearchTestCase {
} }
} }
private <T extends ElasticsearchException> T serialize(T exception) throws IOException { private <T extends Throwable> T serialize(T exception) throws IOException {
ElasticsearchAssertions.assertVersionSerializable(VersionUtils.randomVersion(random()), exception);
BytesStreamOutput out = new BytesStreamOutput(); BytesStreamOutput out = new BytesStreamOutput();
out.writeThrowable(exception); out.writeThrowable(exception);
StreamInput in = StreamInput.wrap(out.bytes()); StreamInput in = StreamInput.wrap(out.bytes());
@ -552,5 +556,26 @@ public class ExceptionSerializationTests extends ElasticsearchTestCase {
assertEquals("{\"type\":\"null_pointer_exception\",\"reason\":null}", toXContent(ex)); assertEquals("{\"type\":\"null_pointer_exception\",\"reason\":null}", toXContent(ex));
ex = serialize(new NotSerializableExceptionWrapper(new IllegalArgumentException("nono!"))); ex = serialize(new NotSerializableExceptionWrapper(new IllegalArgumentException("nono!")));
assertEquals("{\"type\":\"illegal_argument_exception\",\"reason\":\"nono!\"}", toXContent(ex)); assertEquals("{\"type\":\"illegal_argument_exception\",\"reason\":\"nono!\"}", toXContent(ex));
Throwable[] unknowns = new Throwable[] {
new JsonParseException("foobar", new JsonLocation(new Object(), 1,2,3,4)),
new GroovyCastException("boom boom boom"),
new IOException("booom")
};
for (Throwable t : unknowns) {
if (randomBoolean()) {
t.addSuppressed(new IOException("suppressed"));
t.addSuppressed(new NullPointerException());
}
Throwable deserialized = serialize(t);
assertTrue(deserialized instanceof NotSerializableExceptionWrapper);
assertArrayEquals(t.getStackTrace(), deserialized.getStackTrace());
assertEquals(t.getSuppressed().length, deserialized.getSuppressed().length);
if (t.getSuppressed().length > 0) {
assertTrue(deserialized.getSuppressed()[0] instanceof NotSerializableExceptionWrapper);
assertArrayEquals(t.getSuppressed()[0].getStackTrace(), deserialized.getSuppressed()[0].getStackTrace());
assertTrue(deserialized.getSuppressed()[1] instanceof NullPointerException);
}
}
} }
} }

View File

@ -60,6 +60,7 @@ import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
@ -650,6 +651,32 @@ public class ElasticsearchAssertions {
} }
public static void assertVersionSerializable(Version version, final Throwable t) {
ElasticsearchAssertions.assertVersionSerializable(version, new ThrowableWrapper(t));
}
private static final class ThrowableWrapper implements Streamable {
Throwable throwable;
public ThrowableWrapper(Throwable t) {
throwable = t;
}
public ThrowableWrapper() {
throwable = null;
}
@Override
public void readFrom(StreamInput in) throws IOException {
throwable = in.readThrowable();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeThrowable(throwable);
}
}
private static Streamable tryCreateNewInstance(Streamable streamable) throws NoSuchMethodException, InstantiationException, private static Streamable tryCreateNewInstance(Streamable streamable) throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException { IllegalAccessException, InvocationTargetException {
try { try {