From 281add500556b90c52aa5bbea349204deb435fbc Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Fri, 16 Jan 2015 14:15:44 -0800 Subject: [PATCH 1/5] Port of comprehensive unit tests of execution hooks --- .../netflix/hystrix/HystrixCommandTest.java | 2391 +++++++++++------ 1 file changed, 1569 insertions(+), 822 deletions(-) diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java index 634b414be..f7feae66c 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java @@ -16,6 +16,7 @@ import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -30,7 +31,9 @@ import rx.Observable; import rx.Observer; import rx.Scheduler; +import rx.Subscriber; import rx.functions.Action1; +import rx.functions.Func0; import rx.observers.TestSubscriber; import com.netflix.config.ConfigurationManager; @@ -271,7 +274,7 @@ public void testExecutionFailureWithFallback() { */ @Test public void testExecutionFailureWithFallbackFailure() { - TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); try { command.execute(); fail("we shouldn't get here"); @@ -457,7 +460,7 @@ public void testQueueFailureWithFallback() { */ @Test public void testQueueFailureWithFallbackFailure() { - TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); try { command.queue().get(); fail("we shouldn't get here"); @@ -1446,7 +1449,7 @@ public void testShortCircuitFallbackCounter() { @Test public void testRejectedThreadWithNoFallback() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); - SingleThreadedPool pool = new SingleThreadedPool(1); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1); // fill up the queue pool.queue.add(new Runnable() { @@ -1526,7 +1529,7 @@ public void run() { @Test public void testRejectedThreadWithFallback() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); - SingleThreadedPool pool = new SingleThreadedPool(1); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1); // fill up the queue pool.queue.add(new Runnable() { @@ -1585,7 +1588,7 @@ public void run() { @Test public void testRejectedThreadWithFallbackFailure() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); - SingleThreadedPool pool = new SingleThreadedPool(1); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1); // fill up the queue pool.queue.add(new Runnable() { @@ -1647,7 +1650,7 @@ public void run() { @Test public void testRejectedThreadUsingQueueSize() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); - SingleThreadedPool pool = new SingleThreadedPool(10, 1); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(10, 1); // put 1 item in the queue // the thread pool won't pick it up because we're bypassing the pool and adding to the queue directly so this will keep the queue full pool.queue.add(new Runnable() { @@ -1714,7 +1717,7 @@ public void run() { */ @Test public void testTimedOutCommandDoesNotExecute() { - SingleThreadedPool pool = new SingleThreadedPool(5); + SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(5); TestCircuitBreaker s1 = new TestCircuitBreaker(); TestCircuitBreaker s2 = new TestCircuitBreaker(); @@ -1852,7 +1855,7 @@ public void testExecutionSemaphoreWithQueue() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); // single thread should work try { - boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200).queue().get(); + boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).queue().get(); assertTrue(result); } catch (Exception e) { // we shouldn't fail on this one @@ -1869,7 +1872,7 @@ public void testExecutionSemaphoreWithQueue() { @Override public void run() { try { - new TestSemaphoreCommand(circuitBreaker, semaphore, 200).queue().get(); + new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).queue().get(); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -1920,7 +1923,7 @@ public void testExecutionSemaphoreWithExecution() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); // single thread should work try { - TestSemaphoreCommand command = new TestSemaphoreCommand(circuitBreaker, 1, 200); + TestSemaphoreCommand command = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); boolean result = command.execute(); assertFalse(command.isExecutedInThread()); assertTrue(result); @@ -1941,7 +1944,7 @@ public void testExecutionSemaphoreWithExecution() { @Override public void run() { try { - results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200).execute()); + results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).execute()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -3426,904 +3429,1588 @@ public void onNext(Boolean args) { } /** - * Execution hook on successful execution + * Run the command in multiple modes and check that the hook assertions hold in each and that the command succeeds + * @param ctor {@link com.netflix.hystrix.HystrixCommand.UnitTest.TestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + * @param type of object returned by command */ - @Test - public void testExecutionHookSuccessfulCommand() { - /* test with execute() */ - TestHystrixCommand command = new SuccessfulTestCommand(); - command.execute(); - - System.out.println("hook: " + command.builder.executionHook); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from execute() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - - /* test with queue() */ - command = new SuccessfulTestCommand(); - try { - command.queue().get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - + private void assertHooksOnSuccess(Func0> ctor, Action1> assertion) { + assertExecute(ctor.call(), assertion, true); + assertBlockingQueue(ctor.call(), assertion, true); + //assertNonBlockingQueue(ctor.call(), assertion, true); + assertBlockingObserve(ctor.call(), assertion, true); + assertNonBlockingObserve(ctor.call(), assertion, true); } /** - * Execution hook on successful execution with "fire and forget" approach + * Run the command in multiple modes and check that the hook assertions hold in each and that the command fails + * @param ctor {@link com.netflix.hystrix.HystrixCommand.UnitTest.TestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + * @param type of object returned by command */ - @Test - public void testExecutionHookSuccessfulCommandViaFireAndForget() { - TestHystrixCommand command = new SuccessfulTestCommand(); - try { - // do not block on "get()" ... fire this asynchronously - command.queue(); - } catch (Exception e) { - throw new RuntimeException(e); - } + private void assertHooksOnFailure(Func0> ctor, Action1> assertion) { + assertExecute(ctor.call(), assertion, false); + assertBlockingQueue(ctor.call(), assertion, false); + //assertNonBlockingQueue(ctor.call(), assertion, false); + assertBlockingObserve(ctor.call(), assertion, false); + assertNonBlockingObserve(ctor.call(), assertion, false); + } - // wait for command to execute without calling get on the future - while (!command.isExecutionComplete()) { + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#execute()} and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command + */ + private void assertExecute(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.execute() and then assertions..."); + if (isSuccess) { + command.execute(); + } else { try { - Thread.sleep(10); - } catch (InterruptedException e) { - throw new RuntimeException("interrupted"); + command.execute(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); } } - /* - * All the hooks should still work even though we didn't call get() on the future - */ - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + assertion.call(command); } /** - * Execution hook on successful execution with multiple get() calls to Future + * Run the command via {@link com.netflix.hystrix.HystrixCommand#queue()}, immediately block, and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command */ - @Test - public void testExecutionHookSuccessfulCommandWithMultipleGetsOnFuture() { - TestHystrixCommand command = new SuccessfulTestCommand(); - try { - Future f = command.queue(); - f.get(); - f.get(); - f.get(); - f.get(); - } catch (Exception e) { - throw new RuntimeException(e); + private void assertBlockingQueue(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.queue(), immediately blocking and then running assertions..."); + if (isSuccess) { + try { + command.queue().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + try { + command.queue().get(); + fail("Expected a command failure!"); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } catch (ExecutionException ee) { + System.out.println("Received expected ex : " + ee.getCause()); + ee.getCause().printStackTrace(); + } catch (Exception e) { + System.out.println("Received expected ex : " + e); + e.printStackTrace(); + } } - /* - * Despite multiple calls to get() we should only have 1 call to the hooks. - */ - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); + assertion.call(command); + } - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#queue()}, then poll for the command to be finished. + * When it is finished, assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command + */ + private void assertNonBlockingQueue(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.queue(), sleeping the test thread until command is complete, and then running assertions..."); + Future f = command.queue(); + awaitCommandCompletion(command); - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + assertion.call(command); + if (isSuccess) { + try { + f.get(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + f.get(); + fail("Expected a command failure!"); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } catch (ExecutionException ee) { + System.out.println("Received expected ex : " + ee.getCause()); + ee.getCause().printStackTrace(); + } catch (Exception e) { + System.out.println("Received expected ex : " + e); + e.printStackTrace(); + } + } } /** - * Execution hook on failed execution without a fallback + * Run the command via {@link com.netflix.hystrix.HystrixCommand#observe()}, immediately block and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command */ - @Test - public void testExecutionHookRunFailureWithoutFallback() { - /* test with execute() */ - TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); - try { - command.execute(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore + private void assertBlockingObserve(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.observe(), immediately blocking and then running assertions..."); + if (isSuccess) { + try { + command.observe().toBlocking().single(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + command.observe().toBlocking().single(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception - assertNotNull(command.builder.executionHook.runFailureException); + assertion.call(command); + } - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback is not implemented - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's not implemented and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#observe()}, let the {@link rx.Observable} terminal + * states unblock a {@link java.util.concurrent.CountDownLatch} and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command + */ + private void assertNonBlockingObserve(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.observe(), awaiting terminal state of Observable, then running assertions..."); + final CountDownLatch latch = new CountDownLatch(1); - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from execute() since we do not have a fallback and run() failed - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + Observable o = command.observe(); - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); + o.subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + @Override + public void onError(Throwable e) { + latch.countDown(); + } + @Override + public void onNext(T t) { + //do nothing + } + }); - /* test with queue() */ - command = new UnknownFailureTestCommandWithoutFallback(); try { - command.queue().get(); - fail("Expecting exception"); + latch.await(3, TimeUnit.SECONDS); + assertion.call(command); } catch (Exception e) { - // ignore + throw new RuntimeException(e); } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback is not implemented - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's not implemented and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); + if (isSuccess) { + try { + o.toBlocking().single(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + o.toBlocking().single(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } + } + } - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from queue() since we do not have a fallback and run() failed - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + private void awaitCommandCompletion(TestHystrixCommand command) { + while (!command.isExecutionComplete()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException("interrupted"); + } + } + } - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); + /** + *********************** THREAD-ISOLATED Execution Hook Tests ************************************** + */ - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: SUCCESS + */ + @Test + public void testExecutionHookThreadSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new SuccessfulTestCommand(); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on failed execution with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixBadRequestException */ @Test - public void testExecutionHookRunFailureWithFallback() { - /* test with execute() */ - TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); - command.execute(); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response from run since run() failed - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // a response since fallback is implemented - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it's implemented and succeeds - assertNull(command.builder.executionHook.fallbackFailureException); - - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from execute() since we expect a fallback despite failure of run() - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because we expect a fallback - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - - - /* test with queue() */ - command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); - try { - command.queue().get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response from run since run() failed - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // a response since fallback is implemented - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it's implemented and succeeds - assertNull(command.builder.executionHook.fallbackFailureException); - - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since we expect a fallback despite failure of run() - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because we expect a fallback - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + public void testExecutionHookThreadBadRequestException() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownHystrixBadRequestFailureTestCommand(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(HystrixBadRequestException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.BAD_REQUEST_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on failed execution with a fallback failure + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixRuntimeException + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookRunFailureWithFallbackFailure() { - /* test with execute() */ - TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); - try { - command.execute(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback fails - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's implemented but fails - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - - /* test with queue() */ - command = new KnownFailureTestCommandWithFallbackFailure(); - try { - command.queue().get(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback fails - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's implemented but fails - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + public void testExecutionHookThreadExceptionNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownFailureTestCommandWithoutFallback(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on timeout without a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixRuntimeException + * Fallback: SUCCESS */ @Test - public void testExecutionHookTimeoutWithoutFallback() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); - try { - command.queue().get(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because of timeout and no fallback - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because run() didn't fail, it timed out - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run due to timeout - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since no fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since no fallback implementation - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because of timeout and no fallback - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception because of timeout and no fallback - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // timeout failure - assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - - // we need to wait for the thread to complete before the onThreadComplete hook will be called - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - // ignore - } - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - + public void testExecutionHookThreadExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.runFailureException.getClass()); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on timeout with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixRuntimeException + * Fallback: HystrixRuntimeException */ @Test - public void testExecutionHookTimeoutWithFallback() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); - try { - command.queue().get(); - } catch (Exception e) { - throw new RuntimeException("not expecting", e); - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because of timeout - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because run() didn't fail, it timed out - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run due to timeout - assertEquals(1, command.builder.executionHook.startFallback.get()); - // response since we have a fallback - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since fallback succeeds - assertNull(command.builder.executionHook.fallbackFailureException); - - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response because of fallback - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because of fallback - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(1, command.builder.executionHook.threadStart.get()); - - // we need to wait for the thread to complete before the onThreadComplete hook will be called - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - // ignore - } - assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + public void testExecutionHookThreadExceptionUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.runFailureException.getClass()); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on rejected with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookRejectedWithFallback() { - TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); - SingleThreadedPool pool = new SingleThreadedPool(1); - - try { - // fill the queue - new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); - new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); - } catch (Exception e) { - // ignore - } + public void testExecutionHookThreadTimeoutNoFallbackRunSuccess() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - TestCommandRejection command = new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); - try { - // now execute one that will be rejected - command.queue().get(); - } catch (Exception e) { - throw new RuntimeException("not expecting", e); - } + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - assertTrue(command.isResponseRejected()); + } + }); + } - // the run() method should not run as we're rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // we should not have a response because of rejection - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because we didn't run - assertNull(command.builder.executionHook.runFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadTimeoutSuccessfulFallbackRunSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // the fallback() method should be run due to rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // response since we have a fallback - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since fallback succeeds - assertNull(command.builder.executionHook.fallbackFailureException); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response because of fallback - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because of fallback - assertNull(command.builder.executionHook.endExecuteFailureException); + } + }); + } - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunSuccess() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // expected hook execution sequence - assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on short-circuited with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookShortCircuitedWithFallback() { - TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); - KnownFailureTestCommandWithFallback command = new KnownFailureTestCommandWithFallback(circuitBreaker); + public void testExecutionHookThreadTimeoutNoFallbackRunFailure() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, TestCommandWithTimeout.RESULT_EXCEPTION); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - try { - // now execute one that will be short-circuited - command.queue().get(); - } catch (Exception e) { - throw new RuntimeException("not expecting", e); - } + assertNull(command.builder.executionHook.runSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.runFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - assertTrue(command.isResponseShortCircuited()); + } + }); + } - // the run() method should not run as we're short-circuited - assertEquals(0, command.builder.executionHook.startRun.get()); - // we should not have a response because of short-circuit - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because we didn't run - assertNull(command.builder.executionHook.runFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadTimeoutSuccessfulFallbackRunFailure() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, TestCommandWithTimeout.RESULT_EXCEPTION); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // the fallback() method should be run due to short-circuit - assertEquals(1, command.builder.executionHook.startFallback.get()); - // response since we have a fallback - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since fallback succeeds - assertNull(command.builder.executionHook.fallbackFailureException); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - onRunError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response because of fallback - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because of fallback - assertNull(command.builder.executionHook.endExecuteFailureException); + } + }); + } - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunFailure() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, TestCommandWithTimeout.RESULT_EXCEPTION); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // expected hook execution sequence - assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on short-circuit with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookShortCircuitedWithoutFallbackViaQueue() { - TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); - KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); - try { - // now execute one that will be short-circuited - command.queue().get(); - fail("we expect an error as there is no fallback"); - } catch (Exception e) { - // expecting - } - - assertTrue(command.isResponseShortCircuited()); + public void testExecutionHookThreadPoolQueueFullNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED).queue(); + // fill the queue + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED).queue(); + } catch (Exception e) { + // ignore + } - // the run() method should not run as we're rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // we should not have a response because of rejection - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because we didn't run - assertNull(command.builder.executionHook.runFailureException); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the fallback() method should be run due to rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since we don't have a fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since fallback fails and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadPoolQueueFullSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); + // fill the queue + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); + } catch (Exception e) { + // ignore + } - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because fallback fails - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we won't have an exception because short-circuit doesn't have one - assertNull(command.builder.executionHook.endExecuteFailureException); - // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT - assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadPoolQueueFullUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE).queue(); + // fill the queue + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE).queue(); + } catch (Exception e) { + // ignore + } - // expected hook execution sequence - assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on short-circuit with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookShortCircuitedWithoutFallbackViaExecute() { - TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); - KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); - try { - // now execute one that will be short-circuited - command.execute(); - fail("we expect an error as there is no fallback"); - } catch (Exception e) { - // expecting - } - - assertTrue(command.isResponseShortCircuited()); + public void testExecutionHookThreadPoolFullNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED).queue(); + } catch (Exception e) { + // ignore + } - // the run() method should not run as we're rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // we should not have a response because of rejection - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because we didn't run - assertNull(command.builder.executionHook.runFailureException); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the fallback() method should be run due to rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since we don't have a fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since fallback fails and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadPoolFullSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); + } catch (Exception e) { + // ignore + } - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because fallback fails - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we won't have an exception because short-circuit doesn't have one - assertNull(command.builder.executionHook.endExecuteFailureException); - // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT - assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadPoolFullUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE).queue(); + } catch (Exception e) { + // ignore + } - // expected hook execution sequence - assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on successful execution with semaphore isolation + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { - /* test with execute() */ - TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); - command.execute(); - - assertFalse(command.isExecutedInThread()); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from execute() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); - - // expected hook execution sequence - assertEquals("onStart - onRunStart - onRunSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); - - /* test with queue() */ - command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); - try { - command.queue().get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - assertFalse(command.isExecutedInThread()); + public void testExecutionHookThreadShortCircuitNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new KnownFailureTestCommandWithoutFallback(circuitBreaker); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadShortCircuitSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new KnownFailureTestCommandWithFallback(circuitBreaker); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadShortCircuitUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new KnownFailureTestCommandWithFallbackFailure(circuitBreaker); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the queue() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); + /** + *********************** END THREAD-ISOLATED Execution Hook Tests ************************************** + */ - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + ********************* SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ - // expected hook execution sequence - assertEquals("onStart - onRunStart - onRunSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on successful execution with semaphore isolation + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixBadRequestException */ @Test - public void testExecutionHookFailureWithSemaphoreIsolation() { - /* test with execute() */ - final TryableSemaphoreActual semaphore = - new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(0)); - - TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 200); - try { - command.execute(); - fail("we expect a failure"); - } catch (Exception e) { - // expected - } - - assertFalse(command.isExecutedInThread()); - assertTrue(command.isResponseRejected()); + public void testExecutionHookSemaphoreBadRequestException() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_BAD_REQUEST_EXCEPTION, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(HystrixBadRequestException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.BAD_REQUEST_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the run() method should not run as we are rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // null as run() does not get invoked - assertNull(command.builder.executionHook.runSuccessResponse); - // null as run() does not get invoked - assertNull(command.builder.executionHook.runFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixRuntimeException + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreExceptionNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_FAILURE, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the fallback() method should run because of rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // null since there is no fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since the fallback is not implemented - assertNotNull(command.builder.executionHook.fallbackFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixRuntimeException + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookSempahoreExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_FAILURE, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response since fallback has nothing - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we won't have an exception because rejection doesn't have one - assertNull(command.builder.executionHook.endExecuteFailureException); - // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT - assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixRuntimeException + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreExceptionUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_FAILURE, + TestSemaphoreCommand.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreRejectedNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TryableSemaphore semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final TestHystrixCommand cmd1 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + final TestHystrixCommand cmd2 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.queue(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.queue(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } - // expected hook execution sequence - assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + return new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, null=ex in this case + assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on fail with HystrixBadRequest exception + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: SUCCESS */ @Test - public void testExecutionHookFailedOnHystrixBadRequestWithSemaphoreIsolation() { - - TestSemaphoreCommandFailWithHystrixBadRequestException command = new TestSemaphoreCommandFailWithHystrixBadRequestException(new TestCircuitBreaker(), 1, 10); - try { - command.execute(); - fail("we expect a failure"); - } catch (Exception e) { - // expected - } + public void testExecutionHookSempahoreRejectedSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TryableSemaphore semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final TestHystrixCommand cmd1 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + final TestHystrixCommand cmd2 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.queue(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.queue(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } - assertFalse(command.isExecutedInThread()); + return new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect no response from run() - assertNull(command.builder.executionHook.runSuccessResponse); - // we expect an exception - assertNotNull(command.builder.executionHook.runFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreRejectedUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TryableSemaphore semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final TestHystrixCommand cmd1 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + final TestHystrixCommand cmd2 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.queue(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.queue(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } - // the fallback() method should not be run as BadRequestException is handled by immediate propagation - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); + return new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the execute() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from execute() - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have a HystrixBadRequest exception since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteFailureException); + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreShortCircuitNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new TestSemaphoreCommand(circuitBreaker, 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreShortCircuitSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new TestSemaphoreCommand(circuitBreaker, 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // expected hook execution sequence - assertEquals("onStart - onRunStart - onRunError - onError - ", command.builder.executionHook.executionSequence.toString()); + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreShortCircuitUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new TestSemaphoreCommand(circuitBreaker, 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } + /** + ********************* END SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + /** * Test a command execution that fails but has a fallback. */ @@ -4665,27 +5352,20 @@ protected Boolean run() { } - private static class KnownHystrixBadRequestFailureTestCommandWithoutFallback extends TestHystrixCommand { + /** + * Failed execution with {@link HystrixBadRequestException} + */ + private static class KnownHystrixBadRequestFailureTestCommand extends TestHystrixCommand { - public KnownHystrixBadRequestFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker) { + public KnownHystrixBadRequestFailureTestCommand(TestCircuitBreaker circuitBreaker) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } - public KnownHystrixBadRequestFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker, boolean fallbackEnabled) { - super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) - .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackEnabled(fallbackEnabled))); - } - @Override protected Boolean run() { System.out.println("*** simulated failed with HystrixBadRequestException ***"); throw new HystrixBadRequestException("we failed with a simulated issue"); } - - @Override - protected Boolean getFallback() { - return false; - } } /** @@ -4719,8 +5399,8 @@ protected Boolean getFallback() { */ private static class KnownFailureTestCommandWithFallbackFailure extends TestHystrixCommand { - private KnownFailureTestCommandWithFallbackFailure() { - super(testPropsBuilder()); + private KnownFailureTestCommandWithFallbackFailure(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } @Override @@ -4871,17 +5551,32 @@ private static class TestCommandWithTimeout extends TestHystrixCommand private final int fallbackBehavior; + private final static int RESULT_SUCCESS = 10; + private final static int RESULT_EXCEPTION = 11; + + private final int result; + private TestCommandWithTimeout(long timeout, int fallbackBehavior) { - super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationThreadTimeoutInMilliseconds((int) timeout))); + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationThreadTimeoutInMilliseconds((int) timeout))); this.timeout = timeout; this.fallbackBehavior = fallbackBehavior; + this.result = RESULT_SUCCESS; + } + + private TestCommandWithTimeout(long timeout, int fallbackBehavior, int result) { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationThreadTimeoutInMilliseconds((int) timeout))); + this.timeout = timeout; + this.fallbackBehavior = fallbackBehavior; + this.result = result; } @Override protected Boolean run() { System.out.println("***** running"); try { - Thread.sleep(timeout * 10); + Thread.sleep(timeout * 3); } catch (InterruptedException e) { e.printStackTrace(); // ignore and sleep some more to simulate a dependency that doesn't obey interrupts @@ -4892,7 +5587,13 @@ protected Boolean run() { } System.out.println("after interruption with extra sleep"); } - return true; + if (result == RESULT_SUCCESS) { + return true; + } else if (result == RESULT_EXCEPTION) { + throw new RuntimeException("Failure at end of TestCommandWithTimeout"); + } else { + throw new RuntimeException("You passed in a bad result enum : " + result); + } } @Override @@ -4911,17 +5612,17 @@ protected Boolean getFallback() { /** * Threadpool with 1 thread, queue of size 1 */ - private static class SingleThreadedPool implements HystrixThreadPool { + private static class SingleThreadedPoolWithQueue implements HystrixThreadPool { final LinkedBlockingQueue queue; final ThreadPoolExecutor pool; private final int rejectionQueueSizeThreshold; - public SingleThreadedPool(int queueSize) { + public SingleThreadedPoolWithQueue(int queueSize) { this(queueSize, 100); } - public SingleThreadedPool(int queueSize, int rejectionQueueSizeThreshold) { + public SingleThreadedPoolWithQueue(int queueSize, int rejectionQueueSizeThreshold) { queue = new LinkedBlockingQueue(queueSize); pool = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, queue); this.rejectionQueueSizeThreshold = rejectionQueueSizeThreshold; @@ -4954,6 +5655,46 @@ public boolean isQueueSpaceAvailable() { } + /** + * Threadpool with 1 thread, queue of size 1 + */ + private static class SingleThreadedPoolWithNoQueue implements HystrixThreadPool { + + final SynchronousQueue queue; + final ThreadPoolExecutor pool; + + public SingleThreadedPoolWithNoQueue() { + queue = new SynchronousQueue(); + pool = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, queue); + } + + @Override + public ThreadPoolExecutor getExecutor() { + return pool; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public void markThreadExecution() { + // not used for this test + } + + @Override + public void markThreadCompletion() { + // not used for this test + } + + @Override + public boolean isQueueSpaceAvailable() { + return true; //let the thread pool reject + } + + } + /** * This has a ThreadPool that has a single thread and queueSize of 1. */ @@ -5080,26 +5821,42 @@ public String getCacheKey() { } /** - * The run() will take time. No fallback implementation. + * The run() will take time. Configurable fallback implementation. */ private static class TestSemaphoreCommand extends TestHystrixCommand { private final long executionSleep; - private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep) { + private final static int RESULT_SUCCESS = 1; + private final static int RESULT_FAILURE = 2; + private final static int RESULT_BAD_REQUEST_EXCEPTION = 3; + + private final int resultBehavior; + + private final static int FALLBACK_SUCCESS = 10; + private final static int FALLBACK_NOT_IMPLEMENTED = 11; + private final static int FALLBACK_FAILURE = 12; + + private final int fallbackBehavior; + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, int resultBehavior, int fallbackBehavior) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) .withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; } - private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep) { + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep, int resultBehavior, int fallbackBehavior) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) .setExecutionSemaphore(semaphore)); this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; } @Override @@ -5109,37 +5866,27 @@ protected Boolean run() { } catch (InterruptedException e) { e.printStackTrace(); } - return true; - } - } - - /** - * The run() will take time. No fallback implementation. - */ - private static class TestSemaphoreCommandFailWithHystrixBadRequestException extends TestHystrixCommand { - - private final long executionSleep; - - private TestSemaphoreCommandFailWithHystrixBadRequestException(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep) { - super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) - .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() - .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) - .withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); - this.executionSleep = executionSleep; + if (resultBehavior == RESULT_SUCCESS) { + return true; + } else if (resultBehavior == RESULT_FAILURE) { + throw new RuntimeException("TestSemaphoreCommand failure"); + } else if (resultBehavior == RESULT_BAD_REQUEST_EXCEPTION) { + throw new HystrixBadRequestException("TestSemaphoreCommand BadRequestException"); + } else { + throw new IllegalStateException("Didn't use a proper enum for result behavior"); + } } - private TestSemaphoreCommandFailWithHystrixBadRequestException(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep) { - super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) - .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() - .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) - .setExecutionSemaphore(semaphore)); - this.executionSleep = executionSleep; - } @Override - protected Boolean run() { - System.out.print("*** simulated failed execution ***"); - throw new HystrixBadRequestException("we failed with a simulated issue"); + protected Boolean getFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return false; + } else if (fallbackBehavior == FALLBACK_FAILURE) { + throw new RuntimeException("fallback failure"); + } else { //FALLBACK_NOT_IMPLEMENTED + return super.getFallback(); + } } } From 12873a672ed5b48cd62cf39cd04d3ad7af089bd4 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Mon, 19 Jan 2015 10:34:58 -0800 Subject: [PATCH 2/5] Adding consistent way to call onRunSuccess/onRunError hooks from underlying Hystrix thread. Also moved the threadpool cleanup to this path in the case that the command timed-out --- .../com/netflix/hystrix/AbstractCommand.java | 71 ++++++++++--------- .../com/netflix/hystrix/HystrixCommand.java | 13 ---- .../netflix/hystrix/HystrixCommandTest.java | 2 +- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java b/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java index 589dfae33..80a3aa0f0 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java @@ -516,12 +516,7 @@ public void call(Subscriber s) { // store the command that is being run endCurrentThreadExecutingCommand.set(Hystrix.startCurrentThreadExecutingCommand(getCommandKey())); isExecutedInThread.set(true); - getExecutionObservable().map(new Func1() { - @Override - public R call(R r) { - return executionHook.onRunSuccess(_self, r); - } - }).unsafeSubscribe(s); + getExecutionObservableWithLifecycle().unsafeSubscribe(s); } catch (Throwable t) { // the run() method is a user provided implementation so can throw instead of using Observable.onError // so we catch it here and turn it into Observable.error @@ -537,12 +532,7 @@ public R call(R r) { // store the command that is being run endCurrentThreadExecutingCommand.set(Hystrix.startCurrentThreadExecutingCommand(getCommandKey())); try { - run = getExecutionObservable().map(new Func1() { - @Override - public R call(R r) { - return executionHook.onRunSuccess(_self, r); - } - }); + run = getExecutionObservableWithLifecycle(); } catch (Throwable t) { // the run() method is a user provided implementation so can throw instead of using Observable.onError // so we catch it here and turn it into Observable.error @@ -605,18 +595,6 @@ public Observable call(Throwable t) { /** * BadRequest handling */ - try { - Exception decorated = executionHook.onRunError(_self, (Exception) t); - - if (decorated instanceof HystrixBadRequestException) { - t = (HystrixBadRequestException) decorated; - } else { - logger.warn("ExecutionHook.onRunError returned an exception that was not an instance of HystrixBadRequestException so will be ignored.", decorated); - } - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.onRunError", hookException); - } - try { Exception decorated = executionHook.onError(_self, FailureType.BAD_REQUEST_EXCEPTION, (Exception) t); @@ -633,14 +611,6 @@ public Observable call(Throwable t) { */ return Observable.error(t); } else { - /** - * All other error handling - */ - try { - e = executionHook.onRunError(_self, e); - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.endRunFailure", hookException); - } /* * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException. */ @@ -648,6 +618,10 @@ public Observable call(Throwable t) { return Observable.error(e); } + + /** + * All other error handling + */ logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", e); // report failure @@ -686,6 +660,39 @@ public R call(R t1) { return run; } + private Observable getExecutionObservableWithLifecycle() { + final HystrixInvokable _self = this; + + return getExecutionObservable().map(new Func1() { + @Override + public R call(R r) { + return executionHook.onRunSuccess(_self, r); + } + }).onErrorResumeNext(new Func1>() { + @Override + public Observable call(Throwable t) { + try { + Throwable wrappedThrowable = executionHook.onRunError(_self, (Exception) t); + return Observable.error(wrappedThrowable); + } catch (Throwable ex) { + logger.warn("Error calling ExecutionHook.onRunError", ex); + return Observable.error(t); + } + + } + }).doOnTerminate(new Action0() { + @Override + public void call() { + //If the command timed out, then the calling thread has already walked away so we need + //to handle these markers. Otherwise, the calling thread will perform these for us. + if (isCommandTimedOut.get().equals(TimedOutStatus.TIMED_OUT)) { + handleThreadEnd(); + + } + } + }); + } + /** * Execute getFallback() within protection of a semaphore that limits number of concurrent executions. *

diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java index 22b2ec880..a7a847053 100755 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java @@ -24,11 +24,7 @@ import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; -import rx.functions.Action0; -import rx.functions.Func1; -import rx.schedulers.Schedulers; -import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; @@ -306,15 +302,6 @@ public void call(Subscriber s) { } } - }).doOnTerminate(new Action0() { - @Override - public void call() { - //If the command timed out, then the calling thread has already walked away so we need - //to handle these markers. Otherwise, the calling thread will perform these for us. - if (isCommandTimedOut.get().equals(TimedOutStatus.TIMED_OUT)) { - handleThreadEnd(); - } - } }); } diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java index f7feae66c..38fd092f3 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java @@ -4778,7 +4778,7 @@ public void call(TestHystrixCommand command) { * Fallback: SUCCESS */ @Test - public void testExecutionHookSempahoreRejectedSuccessfulFallback() { + public void testExecutionHookSemaphoreRejectedSuccessfulFallback() { assertHooksOnSuccess( new Func0>() { @Override From 2904296f94328ff4edba734f87194d92ff74121d Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Mon, 19 Jan 2015 10:46:38 -0800 Subject: [PATCH 3/5] Add call to onComplete hook in semaphore rejection path --- .../java/com/netflix/hystrix/AbstractCommand.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java b/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java index 80a3aa0f0..849525dfa 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java @@ -404,7 +404,16 @@ public void call() { metrics.markSemaphoreRejection(); logger.debug("HystrixCommand Execution Rejection by Semaphore."); // debug only since we're throwing the exception and someone higher will do something with it // retrieve a fallback or throw an exception if no fallback available - getFallbackOrThrowException(HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION, "could not acquire a semaphore for execution").unsafeSubscribe(observer); + getFallbackOrThrowException(HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION, "could not acquire a semaphore for execution"). + map(new Func1() { + + @Override + public R call(R t1) { + // allow transforming the results via the executionHook if the fallback succeeds + return executionHook.onComplete(_this, t1); + } + + }).unsafeSubscribe(observer); } } else { // record that we are returning a short-circuited fallback From 173a16adf685394f58451ce2d6408a42699ac326 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Mon, 19 Jan 2015 11:43:36 -0800 Subject: [PATCH 4/5] Ported comprehensive hook unit tests to HystrixObservableCommandTest * Also refactored execution hook mechanism into TestableExecutionHook --- .../netflix/hystrix/HystrixCommandTest.java | 122 +- .../hystrix/HystrixObservableCommandTest.java | 2406 +++++++++++------ .../hystrix/TestableExecutionHook.java | 118 + 3 files changed, 1728 insertions(+), 918 deletions(-) create mode 100644 hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java index 38fd092f3..aa4e628f7 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java @@ -48,7 +48,6 @@ import com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable; import com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; -import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; import com.netflix.hystrix.util.HystrixRollingNumberEvent; @@ -78,10 +77,6 @@ public void cleanup() { } } - private static void recordHookCall(StringBuilder sequenceRecorder, String methodName) { - sequenceRecorder.append(methodName).append(" - "); - } - /** * Test a successful command execution. */ @@ -3430,7 +3425,7 @@ public void onNext(Boolean args) { /** * Run the command in multiple modes and check that the hook assertions hold in each and that the command succeeds - * @param ctor {@link com.netflix.hystrix.HystrixCommand.UnitTest.TestHystrixCommand} constructor + * @param ctor {@link com.netflix.hystrix.HystrixCommandTest.TestHystrixCommand} constructor * @param assertion sequence of assertions to check after the command has completed * @param type of object returned by command */ @@ -3444,7 +3439,7 @@ private void assertHooksOnSuccess(Func0> ctor, Action1 /** * Run the command in multiple modes and check that the hook assertions hold in each and that the command fails - * @param ctor {@link com.netflix.hystrix.HystrixCommand.UnitTest.TestHystrixCommand} constructor + * @param ctor {@link com.netflix.hystrix.HystrixCommandTest.TestHystrixCommand} constructor * @param assertion sequence of assertions to check after the command has completed * @param type of object returned by command */ @@ -5202,7 +5197,7 @@ static class TestCommandBuilder { HystrixCommandMetrics metrics = _cb.metrics; TryableSemaphore fallbackSemaphore = null; TryableSemaphore executionSemaphore = null; - TestExecutionHook executionHook = new TestExecutionHook(); + TestableExecutionHook executionHook = new TestableExecutionHook(); TestCommandBuilder setOwner(HystrixCommandGroupKey owner) { this.owner = owner; @@ -5254,7 +5249,7 @@ TestCommandBuilder setExecutionSemaphore(TryableSemaphore executionSemaphore) { return this; } - TestCommandBuilder setExecutionHook(TestExecutionHook executionHook) { + TestCommandBuilder setExecutionHook(TestableExecutionHook executionHook) { this.executionHook = executionHook; return this; } @@ -6112,7 +6107,7 @@ public ExceptionToBadRequestByExecutionHookCommand(TestCircuitBreaker circuitBre super(testPropsBuilder() .setCircuitBreaker(circuitBreaker) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationType)) - .setExecutionHook(new TestExecutionHook(){ + .setExecutionHook(new TestableExecutionHook(){ @Override public Exception onRunError(HystrixInvokable commandInstance, Exception e) { super.onRunError(commandInstance, e); @@ -6213,111 +6208,4 @@ public String getCollapserPropertiesCacheKey(HystrixCollapserKey collapserKey, c } } - - private static class TestExecutionHook extends HystrixCommandExecutionHook { - - StringBuilder executionSequence = new StringBuilder(); - AtomicInteger startExecute = new AtomicInteger(); - - @Override - public void onStart(HystrixInvokable commandInstance) { - super.onStart(commandInstance); - recordHookCall(executionSequence, "onStart"); - startExecute.incrementAndGet(); - } - - Object endExecuteSuccessResponse = null; - - @Override - public T onComplete(HystrixInvokable commandInstance, T response) { - endExecuteSuccessResponse = response; - recordHookCall(executionSequence, "onComplete"); - return super.onComplete(commandInstance, response); - } - - Exception endExecuteFailureException = null; - FailureType endExecuteFailureType = null; - - @Override - public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { - endExecuteFailureException = e; - endExecuteFailureType = failureType; - recordHookCall(executionSequence, "onError"); - return super.onError(commandInstance, failureType, e); - } - - AtomicInteger startRun = new AtomicInteger(); - - @Override - public void onRunStart(HystrixInvokable commandInstance) { - super.onRunStart(commandInstance); - recordHookCall(executionSequence, "onRunStart"); - startRun.incrementAndGet(); - } - - Object runSuccessResponse = null; - - @Override - public T onRunSuccess(HystrixInvokable commandInstance, T response) { - runSuccessResponse = response; - recordHookCall(executionSequence, "onRunSuccess"); - return super.onRunSuccess(commandInstance, response); - } - - Exception runFailureException = null; - - @Override - public Exception onRunError(HystrixInvokable commandInstance, Exception e) { - runFailureException = e; - recordHookCall(executionSequence, "onRunError"); - return super.onRunError(commandInstance, e); - } - - AtomicInteger startFallback = new AtomicInteger(); - - @Override - public void onFallbackStart(HystrixInvokable commandInstance) { - super.onFallbackStart(commandInstance); - recordHookCall(executionSequence, "onFallbackStart"); - startFallback.incrementAndGet(); - } - - Object fallbackSuccessResponse = null; - - @Override - public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { - fallbackSuccessResponse = response; - recordHookCall(executionSequence, "onFallbackSuccess"); - return super.onFallbackSuccess(commandInstance, response); - } - - Exception fallbackFailureException = null; - - @Override - public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { - fallbackFailureException = e; - recordHookCall(executionSequence, "onFallbackError"); - return super.onFallbackError(commandInstance, e); - } - - AtomicInteger threadStart = new AtomicInteger(); - - @Override - public void onThreadStart(HystrixInvokable commandInstance) { - super.onThreadStart(commandInstance); - recordHookCall(executionSequence, "onThreadStart"); - threadStart.incrementAndGet(); - } - - AtomicInteger threadComplete = new AtomicInteger(); - - @Override - public void onThreadComplete(HystrixInvokable commandInstance) { - super.onThreadComplete(commandInstance); - recordHookCall(executionSequence, "onThreadComplete"); - threadComplete.incrementAndGet(); - } - - } - } diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java index 4c52cfcb3..868a72cea 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java @@ -17,6 +17,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -412,7 +415,7 @@ public void testThreadIsolatedObserveFailureWithFallback() { */ @Test public void testSemaphoreIsolatedObserveFailureWithFallbackFailure() { - TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); @@ -1094,7 +1097,7 @@ public void testExecutionSuccessWithCircuitBreakerDisabled() { */ @Test public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.SEMAPHORE); + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); @@ -1143,7 +1146,7 @@ public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { */ @Test public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.SEMAPHORE); + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); try { assertEquals(false, command.observe().toBlocking().single()); // the time should be 50+ since we timeout at 50ms @@ -1180,7 +1183,7 @@ public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { */ @Test public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.SEMAPHORE); + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); @@ -1224,7 +1227,7 @@ public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { */ @Test public void testExecutionTimeoutWithNoFallbackUsingThreadIsolation() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.THREAD); + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_SUCCESS); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); @@ -1273,7 +1276,7 @@ public void testExecutionTimeoutWithNoFallbackUsingThreadIsolation() { */ @Test public void testExecutionTimeoutWithFallbackUsingThreadIsolation() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.THREAD); + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_SUCCESS); try { assertEquals(false, command.observe().toBlocking().single()); // the time should be 50+ since we timeout at 50ms @@ -1310,7 +1313,7 @@ public void testExecutionTimeoutWithFallbackUsingThreadIsolation() { */ @Test public void testExecutionTimeoutFallbackFailureUsingThreadIsolation() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.THREAD); + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_SUCCESS); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); @@ -1503,7 +1506,7 @@ public void testExecutionSemaphoreWithQueue() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); // single thread should work try { - boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200).observe().toBlocking().toFuture().get(); + boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).observe().toBlocking().toFuture().get(); assertTrue(result); } catch (Exception e) { // we shouldn't fail on this one @@ -1520,7 +1523,7 @@ public void testExecutionSemaphoreWithQueue() { @Override public void run() { try { - new TestSemaphoreCommand(circuitBreaker, semaphore, 200).observe().toBlocking().toFuture().get(); + new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).observe().toBlocking().toFuture().get(); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -1571,7 +1574,7 @@ public void testExecutionSemaphoreWithExecution() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); // single thread should work try { - TestSemaphoreCommand command = new TestSemaphoreCommand(circuitBreaker, 1, 200); + TestSemaphoreCommand command = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); boolean result = command.observe().toBlocking().single(); assertFalse(command.isExecutedInThread()); assertTrue(result); @@ -1592,7 +1595,7 @@ public void testExecutionSemaphoreWithExecution() { @Override public void run() { try { - results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200).observe().toBlocking().single()); + results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -3107,92 +3110,111 @@ public void onNext(Boolean args) { } /** - * Execution hook on successful execution + * Run the command in multiple modes and check that the hook assertions hold in each and that the command succeeds + * @param ctor {@link com.netflix.hystrix.HystrixObservableCommandTest.TestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + * @param type of object returned by command */ - @Test - public void testExecutionHookSuccessfulCommand() { - //test with observe().toBlocking().single() - TestHystrixCommand command = new SuccessfulTestCommand(); - command.observe().toBlocking().single(); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().single() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().single() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); + private void assertHooksOnSuccess(Func0> ctor, Action1> assertion) { + assertBlockingObserve(ctor.call(), assertion, true); + assertNonBlockingObserve(ctor.call(), assertion, true); + } - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + /** + * Run the command in multiple modes and check that the hook assertions hold in each and that the command fails + * @param ctor {@link com.netflix.hystrix.HystrixObservableCommandTest.TestHystrixCommand} constructor + * @param assertion sequence of assertions to check after the command has completed + * @param type of object returned by command + */ + private void assertHooksOnFailure(Func0> ctor, Action1> assertion) { + assertBlockingObserve(ctor.call(), assertion, false); + assertNonBlockingObserve(ctor.call(), assertion, false); + } - // test with observe().toBlocking().toFuture() - command = new SuccessfulTestCommand(); - try { - command.observe().toBlocking().toFuture().get(); - } catch (Exception e) { - throw new RuntimeException(e); + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#observe()}, immediately block and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command + */ + private void assertBlockingObserve(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.observe(), immediately blocking and then running assertions..."); + if (isSuccess) { + try { + command.observe().toBlocking().single(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + command.observe().toBlocking().single(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); + assertion.call(command); + } - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); + /** + * Run the command via {@link com.netflix.hystrix.HystrixCommand#observe()}, let the {@link rx.Observable} terminal + * states unblock a {@link java.util.concurrent.CountDownLatch} and then assert + * @param command command to run + * @param assertion assertions to check + * @param isSuccess should the command succeed? + * @param type of object returned by command + */ + private void assertNonBlockingObserve(TestHystrixCommand command, Action1> assertion, boolean isSuccess) { + System.out.println("Running command.observe(), awaiting terminal state of Observable, then running assertions..."); + final CountDownLatch latch = new CountDownLatch(1); - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().toFuture() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); + Observable o = command.observe(); - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + o.subscribe(new Subscriber() { + @Override + public void onCompleted() { + latch.countDown(); + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + @Override + public void onError(Throwable e) { + latch.countDown(); + } - assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); - } + @Override + public void onNext(T t) { + //do nothing + } + }); - /** - * Execution hook on successful execution with "fire and forget" approach - */ - @Test - public void testExecutionHookSuccessfulCommandViaFireAndForget() { - TestHystrixCommand command = new SuccessfulTestCommand(); try { - // do not block on "get()" ... fire this asynchronously - command.observe().toBlocking().toFuture(); + latch.await(3, TimeUnit.SECONDS); + assertion.call(command); } catch (Exception e) { throw new RuntimeException(e); } - // wait for command to execute without calling get on the future + if (isSuccess) { + try { + o.toBlocking().single(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + try { + o.toBlocking().single(); + fail("Expected a command failure!"); + } catch (Exception ex) { + System.out.println("Received expected ex : " + ex); + ex.printStackTrace(); + } + } + } + + private void awaitCommandCompletion(TestHystrixCommand command) { while (!command.isExecutionComplete()) { try { Thread.sleep(10); @@ -3200,704 +3222,1376 @@ public void testExecutionHookSuccessfulCommandViaFireAndForget() { throw new RuntimeException("interrupted"); } } + } - /* All the hooks should still work even though we didn't call get() on the future */ + /** + *********************** THREAD-ISOLATED Execution Hook Tests ************************************** + */ - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: SUCCESS + */ + @Test + public void testExecutionHookThreadSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new SuccessfulTestCommand(); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixBadRequestException + */ + @Test + public void testExecutionHookThreadBadRequestException() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownHystrixBadRequestFailureTestCommand(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(HystrixBadRequestException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.BAD_REQUEST_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().toFuture() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixRuntimeException + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadExceptionNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownFailureTestCommandWithoutFallback(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixRuntimeException + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.runFailureException.getClass()); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackSuccess - onComplete - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: NO + * Execution Result: HystrixRuntimeException + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadExceptionUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.runFailureException.getClass()); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadTimeoutNoFallbackRunSuccess() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + + } + }); } /** - * Execution hook on successful execution with multiple get() calls to Future + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: SUCCESS */ @Test - public void testExecutionHookSuccessfulCommandWithMultipleGetsOnFuture() { - TestHystrixCommand command = new SuccessfulTestCommand(); - try { - Future f = command.observe().toBlocking().toFuture(); - f.get(); - f.get(); - f.get(); - f.get(); - } catch (Exception e) { - throw new RuntimeException(e); - } + public void testExecutionHookThreadTimeoutSuccessfulFallbackRunSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); - /* Despite multiple calls to get() we should only have 1 call to the hooks. */ + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); + } + }); + } - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().toFuture() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: SUCCESS (but timeout prior) + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunSuccess() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunSuccess - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + }); } /** - * Execution hook on failed execution without a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookRunFailureWithoutFallback() { - // test with observe().toBlocking().single() - TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); - try { - command.observe().toBlocking().single(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } + public void testExecutionHookThreadTimeoutNoFallbackRunFailure() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, TestCommandWithTimeout.RESULT_EXCEPTION); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback is not implemented - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's not implemented and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().single() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from observe().toBlocking().single() since we do not have a fallback and run() failed - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // test with observe().toBlocking().toFuture() - command = new UnknownFailureTestCommandWithoutFallback(); - try { - command.observe().toBlocking().toFuture().get(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback is not implemented - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's not implemented and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from observe().toBlocking().toFuture() since we do not have a fallback and run() failed - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.runFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - // semaphore isolated - assertFalse(command.isExecutedInThread()); + } + }); + } - assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadTimeoutSuccessfulFallbackRunFailure() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, TestCommandWithTimeout.RESULT_EXCEPTION); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackSuccess - onComplete - onRunError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); + + } + }); } /** - * Execution hook on failed execution with a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : NO + * Thread Pool Queue full?: NO + * Timeout: YES + * Execution Result: HystrixRuntimeException (but timeout prior) + * Fallback: HystrixRuntimeException */ @Test - public void testExecutionHookRunFailureWithFallback() { - // test with observe().toBlocking().single() - TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); - command.observe().toBlocking().single(); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response from run since run() failed - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // a response since fallback is implemented - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it's implemented and succeeds - assertNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().single() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().single() since we expect a fallback despite failure of run() - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because we expect a fallback - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // test with observe().toBlocking().toFuture() - command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); - try { - command.observe().toBlocking().toFuture().get(); - } catch (Exception e) { - throw new RuntimeException(e); - } + public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunFailure() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, TestCommandWithTimeout.RESULT_EXCEPTION); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(TimeoutException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); //null b/c run() is still going when command returns + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(1, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); //0 b/c thread is still in flight when command returns + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response from run since run() failed - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception since run() failed - assertNotNull(command.builder.executionHook.runFailureException); + try { + Thread.sleep(300); + } catch (Exception ex) { + throw new RuntimeException(ex); + } - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // a response since fallback is implemented - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it's implemented and succeeds - assertNull(command.builder.executionHook.fallbackFailureException); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onThreadStart - onRunStart - onFallbackStart - onFallbackError - onError - onRunError - onThreadComplete - ", command.builder.executionHook.executionSequence.toString()); - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().toFuture() since we expect a fallback despite failure of run() - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because we expect a fallback - assertNull(command.builder.executionHook.endExecuteFailureException); + } + }); + } - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadPoolQueueFullNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED).observe(); + // fill the queue + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED).observe(); + } catch (Exception e) { + // ignore + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadPoolQueueFullSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe(); + // fill the queue + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe(); + } catch (Exception e) { + // ignore + } + + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on failed execution with a fallback failure + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: YES + * Fallback: HystrixRuntimeException */ @Test - public void testExecutionHookRunFailureWithFallbackFailure() { - // test with observe().toBlocking().single() - TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); - try { - command.observe().toBlocking().single(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } + public void testExecutionHookThreadPoolQueueFullUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE).observe(); + // fill the queue + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE).observe(); + } catch (Exception e) { + // ignore + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback fails - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's implemented but fails - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().single() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); - - // test with observe().toBlocking().toFuture() - command = new KnownFailureTestCommandWithFallbackFailure(); - try { - command.observe().toBlocking().toFuture().get(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.runSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run since run() failed - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since fallback fails - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since it's implemented but fails - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because run() and fallback fail - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception because run() and fallback fail - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // run() failure - assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadPoolFullNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED).observe(); + } catch (Exception e) { + // ignore + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadPoolFullSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe(); + } catch (Exception e) { + // ignore + } + + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on timeout without a fallback + * Short-circuit? : NO + * Thread/semaphore: THREAD + * Thread Pool full? : YES + * Thread Pool Queue full?: N/A + * Fallback: HystrixRuntimeException */ @Test - public void testExecutionHookTimeoutWithoutFallback() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); - try { - System.out.println("start at : " + System.currentTimeMillis()); - command.observe().toBlocking().toFuture().get(); - fail("Expecting exception"); - } catch (Exception e) { - // ignore - } + public void testExecutionHookThreadPoolFullUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue(); + try { + // fill the pool + new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE).observe(); + } catch (Exception e) { + // ignore + } - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because of timeout and no fallback - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because run() didn't fail, it timed out - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run due to timeout - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since no fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since no fallback implementation - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because of timeout and no fallback - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should have an exception because of timeout and no fallback - assertNotNull(command.builder.executionHook.endExecuteFailureException); - // timeout failure - assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + return new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RejectedExecutionException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.REJECTED_THREAD_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookThreadShortCircuitNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new KnownFailureTestCommandWithoutFallback(circuitBreaker); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookThreadShortCircuitSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new KnownFailureTestCommandWithFallback(circuitBreaker); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on timeout with a fallback + * Short-circuit? : YES + * Thread/semaphore: THREAD + * Fallback: HystrixRuntimeException */ @Test - public void testExecutionHookTimeoutWithFallback() { - TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); - try { - command.observe().toBlocking().toFuture().get(); - } catch (Exception e) { - throw new RuntimeException("not expecting", e); - } - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we should not have a response because of timeout - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because run() didn't fail, it timed out - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run due to timeout - assertEquals(1, command.builder.executionHook.startFallback.get()); - // response since we have a fallback - assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - // null since fallback succeeds - assertNull(command.builder.executionHook.fallbackFailureException); - - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response because of fallback - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception because of fallback - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - // assertEquals(1, command.builder.executionHook.threadStart.get()); - - // we need to wait for the thread to complete before the onThreadComplete hook will be called - // try { - // Thread.sleep(400); - // } catch (InterruptedException e) { - // // ignore - // } - // assertEquals(1, command.builder.executionHook.threadComplete.get()); + public void testExecutionHookThreadShortCircuitUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new KnownFailureTestCommandWithFallbackFailure(circuitBreaker); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + /** + *********************** END THREAD-ISOLATED Execution Hook Tests ************************************** + */ - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + ********************* SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreSuccess() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNotNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on rejected with a fallback + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixBadRequestException */ - /* - * @Test - * public void testExecutionHookRejectedWithFallback() { - * TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); - * SingleThreadedPool pool = new SingleThreadedPool(1); - * - * try { - * // fill the queue - * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe().toBlocking().toFuture(); - * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe().toBlocking().toFuture(); - * } catch (Exception e) { - * // ignore - * } - * - * TestCommandRejection command = new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); - * try { - * // now execute one that will be rejected - * command.observe().toBlocking().toFuture().get(); - * } catch (Exception e) { - * throw new RuntimeException("not expecting", e); - * } - * - * assertTrue(command.isResponseRejected()); - * - * // the run() method should not run as we're rejected - * assertEquals(0, command.builder.executionHook.startRun.get()); - * // we should not have a response because of rejection - * assertNull(command.builder.executionHook.runSuccessResponse); - * // we should not have an exception because we didn't run - * assertNull(command.builder.executionHook.runFailureException); - * - * // the fallback() method should be run due to rejection - * assertEquals(1, command.builder.executionHook.startFallback.get()); - * // response since we have a fallback - * assertNotNull(command.builder.executionHook.fallbackSuccessResponse); - * // null since fallback succeeds - * assertNull(command.builder.executionHook.fallbackFailureException); - * - * // execution occurred - * assertEquals(1, command.builder.executionHook.startExecute.get()); - * // we should have a response because of fallback - * assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - * // we should not have an exception because of fallback - * assertNull(command.builder.executionHook.endExecuteFailureException); - * - * // thread execution - * // assertEquals(0, command.builder.executionHook.threadStart.get()); - * // assertEquals(0, command.builder.executionHook.threadComplete.get()); - * } + @Test + public void testExecutionHookSemaphoreBadRequestException() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_BAD_REQUEST_EXCEPTION, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(HystrixBadRequestException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.BAD_REQUEST_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(0, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixRuntimeException + * Fallback: UnsupportedOperationException */ + @Test + public void testExecutionHookSemaphoreExceptionNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_FAILURE, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } + /** - * Execution hook on short-circuit with a fallback + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixRuntimeException + * Fallback: SUCCESS */ @Test - public void testExecutionHookShortCircuitedWithFallbackViaQueue() { - TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); - KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); - try { - // now execute one that will be short-circuited - command.observe().toBlocking().toFuture().get(); - fail("we expect an error as there is no fallback"); - } catch (Exception e) { - // expecting - } + public void testExecutionHookSempahoreExceptionSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_FAILURE, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertTrue(command.isResponseShortCircuited()); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : NO + * Execution Result: HystrixRuntimeException + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreExceptionUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + return new TestSemaphoreCommand(new TestCircuitBreaker(), 10, 10, TestSemaphoreCommand.RESULT_FAILURE, + TestSemaphoreCommand.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.endExecuteFailureException.getClass()); + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(1, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNotNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onRunStart - onRunError - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } + + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: UnsupportedOperationException + */ + @Test + public void testExecutionHookSemaphoreRejectedNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + AbstractCommand.TryableSemaphore semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final TestHystrixCommand cmd1 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + final TestHystrixCommand cmd2 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.observe(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.observe(); + } + }.start(); - // the run() method should not run as we're rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // we should not have a response because of rejection - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because we didn't run - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run due to rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since we don't have a fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since fallback fails and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because fallback fails - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we won't have an exception because short-circuit doesn't have one - assertNull(command.builder.executionHook.endExecuteFailureException); - // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT - assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); - - // thread execution - // assertEquals(0, command.builder.executionHook.threadStart.get()); - // assertEquals(0, command.builder.executionHook.threadComplete.get()); + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } - // semaphore isolated - assertFalse(command.isExecutedInThread()); + return new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, null=ex in this case + assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: SUCCESS + */ + @Test + public void testExecutionHookSemaphoreRejectedSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + AbstractCommand.TryableSemaphore semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final TestHystrixCommand cmd1 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + final TestHystrixCommand cmd2 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.observe(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.observe(); + } + }.start(); + + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + + return new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on short-circuit with a fallback + * Short-circuit? : NO + * Thread/semaphore: SEMAPHORE + * Semaphore Permit reached? : YES + * Fallback: HystrixRuntimeException */ @Test - public void testExecutionHookShortCircuitedWithFallbackViaExecute() { - TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); - KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); - try { - // now execute one that will be short-circuited - command.observe().toBlocking().single(); - fail("we expect an error as there is no fallback"); - } catch (Exception e) { - // expecting - } - - assertTrue(command.isResponseShortCircuited()); - - // the run() method should not run as we're rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // we should not have a response because of rejection - assertNull(command.builder.executionHook.runSuccessResponse); - // we should not have an exception because we didn't run - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should be run due to rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // no response since we don't have a fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since fallback fails and throws an exception - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // execution occurred - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response because fallback fails - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we won't have an exception because short-circuit doesn't have one - assertNull(command.builder.executionHook.endExecuteFailureException); - // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT - assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); - - // thread execution - // assertEquals(0, command.builder.executionHook.threadStart.get()); - // assertEquals(0, command.builder.executionHook.threadComplete.get()); + public void testExecutionHookSemaphoreRejectedUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + AbstractCommand.TryableSemaphore semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(2)); + + final TestHystrixCommand cmd1 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + final TestHystrixCommand cmd2 = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + + //saturate the semaphore + new Thread() { + @Override + public void run() { + cmd1.observe(); + } + }.start(); + new Thread() { + @Override + public void run() { + cmd2.observe(); + } + }.start(); - // semaphore isolated - assertFalse(command.isExecutedInThread()); + try { + //give the saturating threads a chance to run before we run the command we want to get rejected + Thread.sleep(200); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + return new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 500, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on successful execution with semaphore isolation + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: UnsupportedOperationException */ @Test - public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { - // test with observe().toBlocking().single() - TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); - command.observe().toBlocking().single(); - - assertFalse(command.isExecutedInThread()); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().single() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().single() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); - - // test with observe().toBlocking().toFuture() - command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); - try { - command.observe().toBlocking().toFuture().get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - assertFalse(command.isExecutedInThread()); - - // the run() method should run as we're not short-circuited or rejected - assertEquals(1, command.builder.executionHook.startRun.get()); - // we expect a successful response from run() - assertNotNull(command.builder.executionHook.runSuccessResponse); - // we do not expect an exception - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should not be run as we were successful - assertEquals(0, command.builder.executionHook.startFallback.get()); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // null since it didn't run - assertNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().toFuture() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from observe().toBlocking().toFuture() since run() succeeded - assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); - // we should not have an exception since run() succeeded - assertNull(command.builder.executionHook.endExecuteFailureException); - - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); - - // semaphore isolated - assertFalse(command.isExecutedInThread()); - - assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + public void testExecutionHookSemaphoreShortCircuitNoFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new TestSemaphoreCommand(circuitBreaker, 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(UnsupportedOperationException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } /** - * Execution hook on successful execution with semaphore isolation + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: SUCCESS */ @Test - public void testExecutionHookFailureWithSemaphoreIsolation() { - // test with observe().toBlocking().single() - final TryableSemaphoreActual semaphore = - new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(0)); - - TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 200); - try { - command.observe().toBlocking().single(); - fail("we expect a failure"); - } catch (Exception e) { - // expected - } - - assertFalse(command.isExecutedInThread()); - assertTrue(command.isResponseRejected()); - - // the run() method should not run as we are rejected - assertEquals(0, command.builder.executionHook.startRun.get()); - // null as run() does not get invoked - assertNull(command.builder.executionHook.runSuccessResponse); - // null as run() does not get invoked - assertNull(command.builder.executionHook.runFailureException); - - // the fallback() method should run because of rejection - assertEquals(1, command.builder.executionHook.startFallback.get()); - // null since there is no fallback - assertNull(command.builder.executionHook.fallbackSuccessResponse); - // not null since the fallback is not implemented - assertNotNull(command.builder.executionHook.fallbackFailureException); - - // the observe().toBlocking().single() method was used - assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response since fallback has nothing - assertNull(command.builder.executionHook.endExecuteSuccessResponse); - // we won't have an exception because rejection doesn't have one - assertNull(command.builder.executionHook.endExecuteFailureException); - // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT - assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); - - // thread execution - assertEquals(0, command.builder.executionHook.threadStart.get()); - assertEquals(0, command.builder.executionHook.threadComplete.get()); - - // semaphore isolated - assertFalse(command.isExecutedInThread()); + public void testExecutionHookSemaphoreShortCircuitSuccessfulFallback() { + assertHooksOnSuccess( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new TestSemaphoreCommand(circuitBreaker, 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_SUCCESS); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); + assertNull(command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + assertNull(command.builder.executionHook.fallbackFailureException); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackSuccess - onComplete - ", command.builder.executionHook.executionSequence.toString()); + } + }); + } - assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + /** + * Short-circuit? : YES + * Thread/semaphore: SEMAPHORE + * Fallback: HystrixRuntimeException + */ + @Test + public void testExecutionHookSemaphoreShortCircuitUnsuccessfulFallback() { + assertHooksOnFailure( + new Func0>() { + @Override + public TestHystrixCommand call() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + return new TestSemaphoreCommand(circuitBreaker, 10, 10, TestSemaphoreCommand.RESULT_SUCCESS, + TestSemaphoreCommand.FALLBACK_FAILURE); + } + }, + new Action1>() { + @Override + public void call(TestHystrixCommand command) { + assertEquals(1, command.builder.executionHook.startExecute.get()); + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + assertNull(command.builder.executionHook.endExecuteFailureException); //by design, ex=null in this case + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + assertEquals(0, command.builder.executionHook.startRun.get()); + assertNull(command.builder.executionHook.runSuccessResponse); + assertNull(command.builder.executionHook.runFailureException); + assertEquals(1, command.builder.executionHook.startFallback.get()); + assertNull(command.builder.executionHook.fallbackSuccessResponse); + assertEquals(RuntimeException.class, command.builder.executionHook.fallbackFailureException.getClass()); + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.builder.executionHook.executionSequence.toString()); + } + }); } + /** + ********************* END SEMAPHORE-ISOLATED Execution Hook Tests *********************************** + */ + /** * Test a command execution that fails but has a fallback. */ @@ -4228,7 +4922,7 @@ public void testRejectedViaSemaphoreIsolation() { public void run() { try { executionThreads.add(Thread.currentThread()); - results.add(new TestSemaphoreCommand(circuitBreaker, 1, 200).toObservable().map(new Func1() { + results.add(new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED).toObservable().map(new Func1() { @Override public Boolean call(Boolean b) { @@ -6162,9 +6856,9 @@ static class TestCommandBuilder { HystrixCommandProperties.Setter commandPropertiesDefaults; HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults = HystrixThreadPoolProperties.Setter.getUnitTestPropertiesBuilder(); HystrixCommandMetrics metrics = _cb.metrics; - TryableSemaphoreActual fallbackSemaphore = null; - TryableSemaphoreActual executionSemaphore = null; - TestExecutionHook executionHook = new TestExecutionHook(); + TryableSemaphore fallbackSemaphore = null; + TryableSemaphore executionSemaphore = null; + TestableExecutionHook executionHook = new TestableExecutionHook(); TestCommandBuilder(ExecutionIsolationStrategy isolationStrategy) { this.commandPropertiesDefaults = HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy); @@ -6210,12 +6904,12 @@ TestCommandBuilder setMetrics(HystrixCommandMetrics metrics) { return this; } - TestCommandBuilder setFallbackSemaphore(TryableSemaphoreActual fallbackSemaphore) { + TestCommandBuilder setFallbackSemaphore(TryableSemaphore fallbackSemaphore) { this.fallbackSemaphore = fallbackSemaphore; return this; } - TestCommandBuilder setExecutionSemaphore(TryableSemaphoreActual executionSemaphore) { + TestCommandBuilder setExecutionSemaphore(TryableSemaphore executionSemaphore) { this.executionSemaphore = executionSemaphore; return this; } @@ -6455,6 +7149,23 @@ protected Observable resumeWithFallback() { } } + /** + * Failed execution with {@link HystrixBadRequestException} + */ + private static class KnownHystrixBadRequestFailureTestCommand extends TestHystrixCommand { + + public KnownHystrixBadRequestFailureTestCommand(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected Observable construct() { + System.out.println("*** simulated failed with HystrixBadRequestException ***"); + return Observable.error(new HystrixBadRequestException("we failed with a simulated issue")); + } + } + + /** * Failing execution - successful fallback implementation. */ @@ -6540,8 +7251,8 @@ protected Observable resumeWithFallback() { */ private static class KnownFailureTestCommandWithFallbackFailure extends TestHystrixCommand { - private KnownFailureTestCommandWithFallbackFailure() { - super(testPropsBuilder()); + private KnownFailureTestCommandWithFallbackFailure(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder(ExecutionIsolationStrategy.THREAD).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } @Override @@ -6701,6 +7412,144 @@ public String getCacheKey() { } } + /** + * Threadpool with 1 thread, queue of size 1 + */ + private static class SingleThreadedPoolWithQueue implements HystrixThreadPool { + + final LinkedBlockingQueue queue; + final ThreadPoolExecutor pool; + private final int rejectionQueueSizeThreshold; + + public SingleThreadedPoolWithQueue(int queueSize) { + this(queueSize, 100); + } + + public SingleThreadedPoolWithQueue(int queueSize, int rejectionQueueSizeThreshold) { + queue = new LinkedBlockingQueue(queueSize); + pool = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, queue); + this.rejectionQueueSizeThreshold = rejectionQueueSizeThreshold; + } + + @Override + public ThreadPoolExecutor getExecutor() { + return pool; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public void markThreadExecution() { + // not used for this test + } + + @Override + public void markThreadCompletion() { + // not used for this test + } + + @Override + public boolean isQueueSpaceAvailable() { + return queue.size() < rejectionQueueSizeThreshold; + } + + } + + /** + * Threadpool with 1 thread, queue of size 1 + */ + private static class SingleThreadedPoolWithNoQueue implements HystrixThreadPool { + + final SynchronousQueue queue; + final ThreadPoolExecutor pool; + + public SingleThreadedPoolWithNoQueue() { + queue = new SynchronousQueue(); + pool = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MINUTES, queue); + } + + @Override + public ThreadPoolExecutor getExecutor() { + return pool; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + @Override + public void markThreadExecution() { + // not used for this test + } + + @Override + public void markThreadCompletion() { + // not used for this test + } + + @Override + public boolean isQueueSpaceAvailable() { + return true; //let the thread pool reject + } + + } + + /** + * This has a ThreadPool that has a single thread and queueSize of 1. + */ + private static class TestCommandRejection extends TestHystrixCommand { + + private final static int FALLBACK_NOT_IMPLEMENTED = 1; + private final static int FALLBACK_SUCCESS = 2; + private final static int FALLBACK_FAILURE = 3; + + private final int fallbackBehavior; + + private final int sleepTime; + + private TestCommandRejection(TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int sleepTime, int timeout, int fallbackBehavior) { + super(testPropsBuilder().setThreadPool(threadPool).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationThreadTimeoutInMilliseconds(timeout))); + this.fallbackBehavior = fallbackBehavior; + this.sleepTime = sleepTime; + } + + @Override + protected Observable construct() { + System.out.println(">>> TestCommandRejection being constructed"); + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + e.printStackTrace(); + subscriber.onError(e); + } + subscriber.onNext(true); + subscriber.onCompleted(); + } + }); + } + + @Override + protected Observable resumeWithFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return Observable.just(true); + } else if (fallbackBehavior == FALLBACK_FAILURE) { + return Observable.error(new RuntimeException("failed on fallback")); + } else { + //FALLBACK_NOT_IMPLEMENTED + return super.resumeWithFallback(); + } + } + } + + /** * Successful execution - no fallback implementation, circuit-breaker disabled. */ @@ -6731,14 +7580,24 @@ private static class TestCommandWithTimeout extends TestHystrixCommand private final int fallbackBehavior; + private final static int RESULT_SUCCESS = 10; + private final static int RESULT_EXCEPTION = 11; + + private final int result; + private TestCommandWithTimeout(long timeout, int fallbackBehavior) { - this(timeout, fallbackBehavior, ExecutionIsolationStrategy.SEMAPHORE); + this(timeout, fallbackBehavior, RESULT_SUCCESS); + } + + private TestCommandWithTimeout(long timeout, int fallbackBehavior, int result) { + this(timeout, fallbackBehavior, ExecutionIsolationStrategy.SEMAPHORE, result); } - private TestCommandWithTimeout(long timeout, int fallbackBehavior, ExecutionIsolationStrategy isolationStrategy) { + private TestCommandWithTimeout(long timeout, int fallbackBehavior, ExecutionIsolationStrategy isolationStrategy, int result) { super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy).withExecutionIsolationThreadTimeoutInMilliseconds((int) timeout))); this.timeout = timeout; this.fallbackBehavior = fallbackBehavior; + this.result = result; } @Override @@ -6760,10 +7619,15 @@ public void call(Subscriber sub) { } System.out.println("after interruption with extra sleep"); } - sub.onNext(true); - sub.onCompleted(); + if (result == RESULT_SUCCESS) { + sub.onNext(true); + sub.onCompleted(); + } else if (result == RESULT_EXCEPTION) { + sub.onError(new RuntimeException("Failure at end of TestCommandWithTimeout")); + } else { + sub.onError(new RuntimeException("You passed in a bad result enum : " + result)); + } } - }).subscribeOn(Schedulers.computation()); } @@ -6815,44 +7679,80 @@ public String getCacheKey() { } /** - * The run() will take time. No fallback implementation. + * The run() will take time. Configurable fallback implementation. */ private static class TestSemaphoreCommand extends TestHystrixCommand { private final long executionSleep; - private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep) { + private final static int RESULT_SUCCESS = 1; + private final static int RESULT_FAILURE = 2; + private final static int RESULT_BAD_REQUEST_EXCEPTION = 3; + + private final int resultBehavior; + + private final static int FALLBACK_SUCCESS = 10; + private final static int FALLBACK_NOT_IMPLEMENTED = 11; + private final static int FALLBACK_FAILURE = 12; + + private final int fallbackBehavior; + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, int resultBehavior, int fallbackBehavior) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) .withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; } - private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphoreActual semaphore, long executionSleep) { + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep, int resultBehavior, int fallbackBehavior) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) .setExecutionSemaphore(semaphore)); this.executionSleep = executionSleep; + this.resultBehavior = resultBehavior; + this.fallbackBehavior = fallbackBehavior; } @Override protected Observable construct() { return Observable.create(new OnSubscribe() { - @Override - public void call(Subscriber s) { + public void call(Subscriber subscriber) { try { Thread.sleep(executionSleep); } catch (InterruptedException e) { e.printStackTrace(); } - s.onNext(true); - s.onCompleted(); + if (resultBehavior == RESULT_SUCCESS) { + subscriber.onNext(true); + subscriber.onCompleted(); + } else if (resultBehavior == RESULT_FAILURE) { + subscriber.onError(new RuntimeException("TestSemaphoreCommand failure")); + } else if (resultBehavior == RESULT_BAD_REQUEST_EXCEPTION) { + subscriber.onError(new HystrixBadRequestException("TestSemaphoreCommand BadRequestException")); + } else { + subscriber.onError(new IllegalStateException("Didn't use a proper enum for result behavior")); + } } + }); - }).subscribeOn(Schedulers.computation()); + + } + + + @Override + protected Observable resumeWithFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return Observable.just(false); + } else if (fallbackBehavior == FALLBACK_FAILURE) { + return Observable.error(new RuntimeException("fallback failure")); + } else { //FALLBACK_NOT_IMPLEMENTED + return super.resumeWithFallback(); + } } } @@ -7221,101 +8121,5 @@ public String getThreadPoolPropertiesCacheKey(HystrixThreadPoolKey threadPoolKey public String getCollapserPropertiesCacheKey(HystrixCollapserKey collapserKey, com.netflix.hystrix.HystrixCollapserProperties.Setter builder) { return null; } - - } - - private static class TestExecutionHook extends HystrixCommandExecutionHook { - - AtomicInteger startExecute = new AtomicInteger(); - - @Override - public void onStart(HystrixInvokable commandInstance) { - super.onStart(commandInstance); - startExecute.incrementAndGet(); - } - - Object endExecuteSuccessResponse = null; - - @Override - public T onComplete(HystrixInvokable commandInstance, T response) { - endExecuteSuccessResponse = response; - return super.onComplete(commandInstance, response); - } - - Exception endExecuteFailureException = null; - FailureType endExecuteFailureType = null; - - @Override - public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { - endExecuteFailureException = e; - endExecuteFailureType = failureType; - return super.onError(commandInstance, failureType, e); - } - - AtomicInteger startRun = new AtomicInteger(); - - @Override - public void onRunStart(HystrixInvokable commandInstance) { - super.onRunStart(commandInstance); - startRun.incrementAndGet(); - } - - Object runSuccessResponse = null; - - @Override - public T onRunSuccess(HystrixInvokable commandInstance, T response) { - runSuccessResponse = response; - return super.onRunSuccess(commandInstance, response); - } - - Exception runFailureException = null; - - @Override - public Exception onRunError(HystrixInvokable commandInstance, Exception e) { - runFailureException = e; - return super.onRunError(commandInstance, e); - } - - AtomicInteger startFallback = new AtomicInteger(); - - @Override - public void onFallbackStart(HystrixInvokable commandInstance) { - super.onFallbackStart(commandInstance); - startFallback.incrementAndGet(); - } - - Object fallbackSuccessResponse = null; - - @Override - public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { - fallbackSuccessResponse = response; - return super.onFallbackSuccess(commandInstance, response); - } - - Exception fallbackFailureException = null; - - @Override - public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { - fallbackFailureException = e; - return super.onFallbackError(commandInstance, e); - } - - AtomicInteger threadStart = new AtomicInteger(); - - @Override - public void onThreadStart(HystrixInvokable commandInstance) { - super.onThreadStart(commandInstance); - threadStart.incrementAndGet(); - } - - AtomicInteger threadComplete = new AtomicInteger(); - - @Override - public void onThreadComplete(HystrixInvokable commandInstance) { - super.onThreadComplete(commandInstance); - threadComplete.incrementAndGet(); - } - } - } diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java b/hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java new file mode 100644 index 000000000..5caeca180 --- /dev/null +++ b/hystrix-core/src/test/java/com/netflix/hystrix/TestableExecutionHook.java @@ -0,0 +1,118 @@ +package com.netflix.hystrix; + +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; + + +import java.util.concurrent.atomic.AtomicInteger; + +class TestableExecutionHook extends HystrixCommandExecutionHook { + + private static void recordHookCall(StringBuilder sequenceRecorder, String methodName) { + sequenceRecorder.append(methodName).append(" - "); + } + + StringBuilder executionSequence = new StringBuilder(); + AtomicInteger startExecute = new AtomicInteger(); + + @Override + public void onStart(HystrixInvokable commandInstance) { + super.onStart(commandInstance); + recordHookCall(executionSequence, "onStart"); + startExecute.incrementAndGet(); + } + + Object endExecuteSuccessResponse = null; + + @Override + public T onComplete(HystrixInvokable commandInstance, T response) { + endExecuteSuccessResponse = response; + recordHookCall(executionSequence, "onComplete"); + return super.onComplete(commandInstance, response); + } + + Exception endExecuteFailureException = null; + HystrixRuntimeException.FailureType endExecuteFailureType = null; + + @Override + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { + endExecuteFailureException = e; + endExecuteFailureType = failureType; + recordHookCall(executionSequence, "onError"); + return super.onError(commandInstance, failureType, e); + } + + AtomicInteger startRun = new AtomicInteger(); + + @Override + public void onRunStart(HystrixInvokable commandInstance) { + super.onRunStart(commandInstance); + recordHookCall(executionSequence, "onRunStart"); + startRun.incrementAndGet(); + } + + Object runSuccessResponse = null; + + @Override + public T onRunSuccess(HystrixInvokable commandInstance, T response) { + runSuccessResponse = response; + recordHookCall(executionSequence, "onRunSuccess"); + return super.onRunSuccess(commandInstance, response); + } + + Exception runFailureException = null; + + @Override + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { + runFailureException = e; + recordHookCall(executionSequence, "onRunError"); + return super.onRunError(commandInstance, e); + } + + AtomicInteger startFallback = new AtomicInteger(); + + @Override + public void onFallbackStart(HystrixInvokable commandInstance) { + super.onFallbackStart(commandInstance); + recordHookCall(executionSequence, "onFallbackStart"); + startFallback.incrementAndGet(); + } + + Object fallbackSuccessResponse = null; + + @Override + public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { + fallbackSuccessResponse = response; + recordHookCall(executionSequence, "onFallbackSuccess"); + return super.onFallbackSuccess(commandInstance, response); + } + + Exception fallbackFailureException = null; + + @Override + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { + fallbackFailureException = e; + recordHookCall(executionSequence, "onFallbackError"); + return super.onFallbackError(commandInstance, e); + } + + AtomicInteger threadStart = new AtomicInteger(); + + @Override + public void onThreadStart(HystrixInvokable commandInstance) { + super.onThreadStart(commandInstance); + recordHookCall(executionSequence, "onThreadStart"); + threadStart.incrementAndGet(); + } + + AtomicInteger threadComplete = new AtomicInteger(); + + @Override + public void onThreadComplete(HystrixInvokable commandInstance) { + super.onThreadComplete(commandInstance); + recordHookCall(executionSequence, "onThreadComplete"); + threadComplete.incrementAndGet(); + } + +} From 7a3bd19d0a3c0036276ed013d2f70480431ee57f Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Mon, 19 Jan 2015 12:49:31 -0800 Subject: [PATCH 5/5] Fixing up unit tests in HystrixObservableCommandTest --- .../hystrix/HystrixObservableCommandTest.java | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java index 868a72cea..d916e500b 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java @@ -415,7 +415,7 @@ public void testThreadIsolatedObserveFailureWithFallback() { */ @Test public void testSemaphoreIsolatedObserveFailureWithFallbackFailure() { - TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker(), ExecutionIsolationStrategy.SEMAPHORE); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); @@ -3242,7 +3242,7 @@ public void testExecutionHookThreadSuccess() { new Func0>() { @Override public TestHystrixCommand call() { - return new SuccessfulTestCommand(); + return new SuccessfulTestCommand(ExecutionIsolationStrategy.THREAD); } }, new Action1>() { @@ -3317,7 +3317,7 @@ public void testExecutionHookThreadExceptionNoFallback() { new Func0>() { @Override public TestHystrixCommand call() { - return new KnownFailureTestCommandWithoutFallback(new TestCircuitBreaker()); + return new KnownFailureTestCommandWithoutFallback(new TestCircuitBreaker(), ExecutionIsolationStrategy.THREAD); } }, new Action1>() { @@ -3355,7 +3355,7 @@ public void testExecutionHookThreadExceptionSuccessfulFallback() { new Func0>() { @Override public TestHystrixCommand call() { - return new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + return new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), ExecutionIsolationStrategy.THREAD); } }, new Action1>() { @@ -3393,7 +3393,7 @@ public void testExecutionHookThreadExceptionUnsuccessfulFallback() { new Func0>() { @Override public TestHystrixCommand call() { - return new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker()); + return new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker(), ExecutionIsolationStrategy.THREAD); } }, new Action1>() { @@ -3431,7 +3431,7 @@ public void testExecutionHookThreadTimeoutNoFallbackRunSuccess() { new Func0>() { @Override public TestHystrixCommand call() { - return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_SUCCESS); } }, new Action1>() { @@ -3481,7 +3481,7 @@ public void testExecutionHookThreadTimeoutSuccessfulFallbackRunSuccess() { new Func0>() { @Override public TestHystrixCommand call() { - return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_SUCCESS); } }, new Action1>() { @@ -3531,7 +3531,7 @@ public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunSuccess() { new Func0>() { @Override public TestHystrixCommand call() { - return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_SUCCESS); } }, new Action1>() { @@ -3581,7 +3581,7 @@ public void testExecutionHookThreadTimeoutNoFallbackRunFailure() { new Func0>() { @Override public TestHystrixCommand call() { - return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, TestCommandWithTimeout.RESULT_EXCEPTION); + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_EXCEPTION); } }, new Action1>() { @@ -3631,7 +3631,7 @@ public void testExecutionHookThreadTimeoutSuccessfulFallbackRunFailure() { new Func0>() { @Override public TestHystrixCommand call() { - return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, TestCommandWithTimeout.RESULT_EXCEPTION); + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_EXCEPTION); } }, new Action1>() { @@ -3681,7 +3681,7 @@ public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunFailure() { new Func0>() { @Override public TestHystrixCommand call() { - return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, TestCommandWithTimeout.RESULT_EXCEPTION); + return new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.THREAD, TestCommandWithTimeout.RESULT_EXCEPTION); } }, new Action1>() { @@ -6927,6 +6927,10 @@ public SuccessfulTestCommand() { this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)); } + public SuccessfulTestCommand(ExecutionIsolationStrategy isolationStrategy) { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)); + } + public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) { super(testPropsBuilder().setCommandPropertiesDefaults(properties)); } @@ -7113,11 +7117,15 @@ private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } + private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy) { + super(testPropsBuilder(isolationStrategy).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + @Override protected Observable construct() { - // TODO duplicate with error inside async Observable + // TODO duplicate with error inside sync Observable System.out.println("*** simulated failed execution ***"); - throw new RuntimeException("we failed with a simulated issue"); + return Observable.error(new RuntimeException("we failed with a simulated issue")); } } @@ -7131,6 +7139,10 @@ public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } + public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy) { + super(testPropsBuilder(isolationStrategy).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, boolean fallbackEnabled) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackEnabled(fallbackEnabled).withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); @@ -7138,9 +7150,9 @@ public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, bo @Override protected Observable construct() { - // TODO duplicate with error inside async Observable + // TODO duplicate with error inside sync Observable (I know at least 1 unit test will fail. See https://github.com/Netflix/Hystrix/issues/525) System.out.println("*** simulated failed execution ***"); - throw new RuntimeException("we failed with a simulated issue"); + return Observable.error(new RuntimeException("we failed with a simulated issue")); } @Override @@ -7155,7 +7167,7 @@ protected Observable resumeWithFallback() { private static class KnownHystrixBadRequestFailureTestCommand extends TestHystrixCommand { public KnownHystrixBadRequestFailureTestCommand(TestCircuitBreaker circuitBreaker) { - super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + super(testPropsBuilder(ExecutionIsolationStrategy.THREAD).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } @Override @@ -7255,10 +7267,15 @@ private KnownFailureTestCommandWithFallbackFailure(TestCircuitBreaker circuitBre super(testPropsBuilder(ExecutionIsolationStrategy.THREAD).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } + private KnownFailureTestCommandWithFallbackFailure(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy) { + super(testPropsBuilder(isolationStrategy).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + @Override protected Observable construct() { + //TODO duplicate with sync error System.out.println("*** simulated failed execution ***"); - throw new RuntimeException("we failed with a simulated issue"); + return Observable.error(new RuntimeException("we failed with a simulated issue")); } @Override