Skip to content

Commit 9c357df

Browse files
author
Tim van Heugten
committed
Introduce NotWrappedByHystrix to mark Exceptions that should not be wrapped in HystrixRunetimeException
1 parent 7598e9a commit 9c357df

File tree

10 files changed

+214
-14
lines changed

10 files changed

+214
-14
lines changed

hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.netflix.hystrix.HystrixCircuitBreaker.NoOpCircuitBreaker;
1919
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
20+
import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix;
2021
import com.netflix.hystrix.exception.HystrixBadRequestException;
2122
import com.netflix.hystrix.exception.HystrixRuntimeException;
2223
import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType;
@@ -749,12 +750,15 @@ private Observable<R> getFallbackOrThrowException(final AbstractCommand<R> _cmd,
749750
// do this before executing fallback so it can be queried from within getFallback (see See https://github.com/Netflix/Hystrix/pull/144)
750751
executionResult = executionResult.addEvent((int) latency, eventType);
751752

752-
if (isUnrecoverable(originalException)) {
753-
Exception e = originalException;
754-
logger.error("Unrecoverable Error for HystrixCommand so will throw HystrixRuntimeException and not apply fallback. ", e);
753+
if (shouldNotBeWrapped(originalException)){
754+
/* executionHook for all errors */
755+
Exception e = wrapWithOnErrorHook(failureType, originalException);
756+
return Observable.error(e);
757+
} else if (isUnrecoverable(originalException)) {
758+
logger.error("Unrecoverable Error for HystrixCommand so will throw HystrixRuntimeException and not apply fallback. ", originalException);
755759

756760
/* executionHook for all errors */
757-
e = wrapWithOnErrorHook(failureType, e);
761+
Exception e = wrapWithOnErrorHook(failureType, originalException);
758762
return Observable.error(new HystrixRuntimeException(failureType, this.getClass(), getLogMessagePrefix() + " " + message + " and encountered unrecoverable error.", e, null));
759763
} else {
760764
if (isRecoverableError(originalException)) {
@@ -1038,6 +1042,10 @@ private Observable<R> handleFallbackDisabledByEmittingError(Exception underlying
10381042
return Observable.error(new HystrixRuntimeException(failureType, this.getClass(), getLogMessagePrefix() + " " + message + " and fallback disabled.", wrapped, null));
10391043
}
10401044

1045+
protected boolean shouldNotBeWrapped(Throwable underlying) {
1046+
return underlying instanceof ExceptionNotWrappedByHystrix;
1047+
}
1048+
10411049
/**
10421050
* Returns true iff the t was caused by a java.lang.Error that is unrecoverable. Note: not all java.lang.Errors are unrecoverable.
10431051
* @see <a href="https://github.com/Netflix/Hystrix/issues/713"></a> for more context
@@ -1532,12 +1540,13 @@ private R wrapWithOnEmitHook(R r) {
15321540
/**
15331541
* Take an Exception and determine whether to throw it, its cause or a new HystrixRuntimeException.
15341542
* <p>
1535-
* This will only throw an HystrixRuntimeException, HystrixBadRequestException or IllegalStateException
1543+
* This will only throw an HystrixRuntimeException, HystrixBadRequestException, IllegalStateException
1544+
* or any exception that implements ExceptionNotWrappedByHystrix.
15361545
*
15371546
* @param e initial exception
15381547
* @return HystrixRuntimeException, HystrixBadRequestException or IllegalStateException
15391548
*/
1540-
protected RuntimeException decomposeException(Exception e) {
1549+
protected Throwable decomposeException(Exception e) {
15411550
if (e instanceof IllegalStateException) {
15421551
return (IllegalStateException) e;
15431552
}
@@ -1554,6 +1563,12 @@ protected RuntimeException decomposeException(Exception e) {
15541563
if (e.getCause() instanceof HystrixRuntimeException) {
15551564
return (HystrixRuntimeException) e.getCause();
15561565
}
1566+
if (shouldNotBeWrapped(e)) {
1567+
return e;
1568+
}
1569+
if (shouldNotBeWrapped(e.getCause())) {
1570+
return e.getCause();
1571+
}
15571572
// we don't know what kind of exception this is so create a generic message and throw a new HystrixRuntimeException
15581573
String message = getLogMessagePrefix() + " failed while executing.";
15591574
logger.debug(message, e); // debug only since we're throwing the exception and someone higher will do something with it

hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.concurrent.atomic.AtomicBoolean;
2424
import java.util.concurrent.atomic.AtomicReference;
2525

26+
import com.netflix.hystrix.util.Exceptions;
2627
import rx.Observable;
2728
import rx.functions.Action0;
2829

@@ -342,7 +343,7 @@ public R execute() {
342343
try {
343344
return queue().get();
344345
} catch (Exception e) {
345-
throw decomposeException(e);
346+
throw Exceptions.sneakyThrow(decomposeException(e));
346347
}
347348
}
348349

@@ -435,11 +436,11 @@ public R get(long timeout, TimeUnit unit) throws InterruptedException, Execution
435436
f.get();
436437
return f;
437438
} catch (Exception e) {
438-
RuntimeException re = decomposeException(e);
439-
if (re instanceof HystrixBadRequestException) {
439+
Throwable t = decomposeException(e);
440+
if (t instanceof HystrixBadRequestException) {
440441
return f;
441-
} else if (re instanceof HystrixRuntimeException) {
442-
HystrixRuntimeException hre = (HystrixRuntimeException) re;
442+
} else if (t instanceof HystrixRuntimeException) {
443+
HystrixRuntimeException hre = (HystrixRuntimeException) t;
443444
switch (hre.getFailureType()) {
444445
case COMMAND_EXCEPTION:
445446
case TIMEOUT:
@@ -450,7 +451,7 @@ public R get(long timeout, TimeUnit unit) throws InterruptedException, Execution
450451
throw hre;
451452
}
452453
} else {
453-
throw re;
454+
throw Exceptions.sneakyThrow(t);
454455
}
455456
}
456457
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.netflix.hystrix.exception;
2+
3+
/**
4+
* Exceptions can implement this interface to prevent Hystrix from wrapping detected exceptions in a HystrixRuntimeException
5+
*/
6+
public interface ExceptionNotWrappedByHystrix {
7+
8+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.netflix.hystrix.util;
2+
3+
import java.util.LinkedList;
4+
import java.util.List;
5+
6+
public class Exceptions {
7+
private Exceptions() {
8+
}
9+
10+
/**
11+
* Throws the argument, return-type is RuntimeException so the caller can use a throw statement break out of the method
12+
*/
13+
public static RuntimeException sneakyThrow(Throwable t) {
14+
return Exceptions.<RuntimeException>doThrow(t);
15+
}
16+
17+
private static <T extends Throwable> T doThrow(Throwable ex) throws T {
18+
throw (T) ex;
19+
}
20+
}

hystrix-core/src/test/java/com/netflix/hystrix/AbstractTestHystrixCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
public interface AbstractTestHystrixCommand<R> extends HystrixObservable<R>, InspectableBuilder {
2121

2222
enum ExecutionResult {
23-
SUCCESS, FAILURE, ASYNC_FAILURE, HYSTRIX_FAILURE, ASYNC_HYSTRIX_FAILURE, RECOVERABLE_ERROR, ASYNC_RECOVERABLE_ERROR, UNRECOVERABLE_ERROR, ASYNC_UNRECOVERABLE_ERROR, BAD_REQUEST, ASYNC_BAD_REQUEST, MULTIPLE_EMITS_THEN_SUCCESS, MULTIPLE_EMITS_THEN_FAILURE, NO_EMITS_THEN_SUCCESS
23+
SUCCESS, FAILURE, ASYNC_FAILURE, HYSTRIX_FAILURE, NOT_WRAPPED_FAILURE, ASYNC_HYSTRIX_FAILURE, RECOVERABLE_ERROR, ASYNC_RECOVERABLE_ERROR, UNRECOVERABLE_ERROR, ASYNC_UNRECOVERABLE_ERROR, BAD_REQUEST, ASYNC_BAD_REQUEST, MULTIPLE_EMITS_THEN_SUCCESS, MULTIPLE_EMITS_THEN_FAILURE, NO_EMITS_THEN_SUCCESS
2424
}
2525

2626
enum FallbackResult {

hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,31 @@ public void testExecutionFailureWithNoFallback() {
171171
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
172172
assertSaneHystrixRequestLog(1);
173173
}
174+
175+
/**
176+
* Test a command execution that throws an exception that should not be wrapped.
177+
*/
178+
@Test
179+
public void testNotWrappedExceptionWithNoFallback() {
180+
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
181+
try {
182+
command.execute();
183+
fail("we shouldn't get here");
184+
} catch (HystrixRuntimeException e) {
185+
e.printStackTrace();
186+
fail("we shouldn't get a HystrixRuntimeException");
187+
} catch (RuntimeException e) {
188+
assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException);
189+
}
190+
191+
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
192+
assertTrue(command.isFailedExecution());
193+
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
194+
assertNotNull(command.getExecutionException());
195+
assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestRuntimeException);
196+
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
197+
assertSaneHystrixRequestLog(1);
198+
}
174199

175200
/**
176201
* Test a command execution that fails but has a fallback.
@@ -188,6 +213,30 @@ public void testExecutionFailureWithFallback() {
188213
assertSaneHystrixRequestLog(1);
189214
}
190215

216+
/**
217+
* Test a command execution that throws exception that should not be wrapped but has a fallback.
218+
*/
219+
@Test
220+
public void testNotWrappedExceptionWithFallback() {
221+
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
222+
try {
223+
command.execute();
224+
fail("we shouldn't get here");
225+
} catch (HystrixRuntimeException e) {
226+
e.printStackTrace();
227+
fail("we shouldn't get a HystrixRuntimeException");
228+
} catch (RuntimeException e) {
229+
assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException);
230+
}
231+
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
232+
assertTrue(command.isFailedExecution());
233+
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
234+
assertNotNull(command.getExecutionException());
235+
assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestRuntimeException);
236+
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
237+
assertSaneHystrixRequestLog(1);
238+
}
239+
191240
/**
192241
* Test a command execution that fails, has getFallback implemented but that fails as well.
193242
*/
@@ -2258,6 +2307,56 @@ public void onNext(Boolean args) {
22582307
assertSaneHystrixRequestLog(1);
22592308
}
22602309

2310+
/**
2311+
* Test an Exception implementing NotWrappedByHystrix being thrown
2312+
*
2313+
* @throws InterruptedException
2314+
*/
2315+
@Test
2316+
public void testNotWrappedExceptionViaObserve() throws InterruptedException {
2317+
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
2318+
CommandWithNotWrappedByHystrixException command = new CommandWithNotWrappedByHystrixException(circuitBreaker);
2319+
final AtomicReference<Throwable> t = new AtomicReference<Throwable>();
2320+
final CountDownLatch latch = new CountDownLatch(1);
2321+
try {
2322+
command.observe().subscribe(new Observer<Boolean>() {
2323+
2324+
@Override
2325+
public void onCompleted() {
2326+
latch.countDown();
2327+
}
2328+
2329+
@Override
2330+
public void onError(Throwable e) {
2331+
t.set(e);
2332+
latch.countDown();
2333+
}
2334+
2335+
@Override
2336+
public void onNext(Boolean args) {
2337+
2338+
}
2339+
2340+
});
2341+
} catch (Exception e) {
2342+
e.printStackTrace();
2343+
fail("we should not get anything thrown, it should be emitted via the Observer#onError method");
2344+
}
2345+
2346+
latch.await(1, TimeUnit.SECONDS);
2347+
assertNotNull(t.get());
2348+
t.get().printStackTrace();
2349+
2350+
assertTrue(t.get() instanceof NotWrappedByHystrixTestException);
2351+
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
2352+
assertTrue(command.isFailedExecution());
2353+
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
2354+
assertNotNull(command.getExecutionException());
2355+
assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestException);
2356+
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
2357+
assertSaneHystrixRequestLog(1);
2358+
}
2359+
22612360
@Test
22622361
public void testSemaphoreExecutionWithTimeout() {
22632362
TestHystrixCommand<Boolean> cmd = new InterruptibleCommand(new TestCircuitBreaker(), false);
@@ -4750,6 +4849,8 @@ protected Integer run() throws Exception {
47504849
return FlexibleTestHystrixCommand.EXECUTE_VALUE;
47514850
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.FAILURE) {
47524851
throw new RuntimeException("Execution Failure for TestHystrixCommand");
4852+
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE) {
4853+
throw new NotWrappedByHystrixTestRuntimeException();
47534854
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE) {
47544855
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixCommand.class, "Execution Hystrix Failure for TestHystrixCommand", new RuntimeException("Execution Failure for TestHystrixCommand"), new RuntimeException("Fallback Failure for TestHystrixCommand"));
47554856
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.RECOVERABLE_ERROR) {
@@ -5534,6 +5635,20 @@ protected Boolean run() throws Exception {
55345635

55355636
}
55365637

5638+
private static class CommandWithNotWrappedByHystrixException extends TestHystrixCommand<Boolean> {
5639+
5640+
public CommandWithNotWrappedByHystrixException(TestCircuitBreaker circuitBreaker) {
5641+
super(testPropsBuilder()
5642+
.setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
5643+
}
5644+
5645+
@Override
5646+
protected Boolean run() throws Exception {
5647+
throw new NotWrappedByHystrixTestException();
5648+
}
5649+
5650+
}
5651+
55375652
private static class InterruptibleCommand extends TestHystrixCommand<Boolean> {
55385653

55395654
public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt, boolean shouldInterruptOnCancel, int timeoutInMillis) {

hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
2929
import com.netflix.hystrix.strategy.properties.HystrixProperty;
3030
import org.junit.After;
31-
import org.junit.Before;
3231
import org.junit.Rule;
3332
import org.junit.Test;
3433
import rx.Notification;
@@ -4679,6 +4678,9 @@ protected Observable<Integer> construct() {
46794678
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE) {
46804679
addLatency(executionLatency);
46814680
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixObservableCommand.class, "Execution Hystrix Failure for TestHystrixObservableCommand", new RuntimeException("Execution Failure for TestHystrixObservableCommand"), new RuntimeException("Fallback Failure for TestHystrixObservableCommand"));
4681+
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE) {
4682+
addLatency(executionLatency);
4683+
throw new NotWrappedByHystrixTestRuntimeException();
46824684
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.RECOVERABLE_ERROR) {
46834685
addLatency(executionLatency);
46844686
throw new java.lang.Error("Execution Sync Error for TestHystrixObservableCommand");
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.netflix.hystrix;
2+
3+
import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix;
4+
5+
public class NotWrappedByHystrixTestException extends Exception implements ExceptionNotWrappedByHystrix {
6+
private static final long serialVersionUID = 1L;
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.netflix.hystrix;
2+
3+
import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix;
4+
5+
public class NotWrappedByHystrixTestRuntimeException extends RuntimeException implements ExceptionNotWrappedByHystrix {
6+
private static final long serialVersionUID = 1L;
7+
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.netflix.hystrix.util;
2+
3+
import org.junit.Assert;
4+
import org.junit.Test;
5+
6+
import java.io.IOException;
7+
8+
import static org.junit.Assert.assertTrue;
9+
import static org.junit.Assert.fail;
10+
11+
public class ExceptionsTest {
12+
13+
@Test
14+
public void testCastOfException(){
15+
Exception exception = new IOException("simulated checked exception message");
16+
try{
17+
Exceptions.sneakyThrow(exception);
18+
fail();
19+
} catch(Exception e){
20+
assertTrue(e instanceof IOException);
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)