/** * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.netflix.hystrix; import com.hystrix.junit.HystrixRequestContextRule; import com.netflix.config.ConfigurationManager; import com.netflix.hystrix.AbstractCommand.TryableSemaphoreActual; import com.netflix.hystrix.HystrixCircuitBreakerTest.TestCircuitBreaker; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.netflix.hystrix.strategy.HystrixPlugins; 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.properties.HystrixProperty; import org.junit.After; import org.junit.Rule; import org.junit.Test; import rx.*; import rx.Observable.OnSubscribe; import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Func0; import rx.functions.Func1; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.*; public class HystrixObservableCommandTest extends CommonHystrixCommandTests<TestHystrixObservableCommand<Integer>> { @Rule public HystrixRequestContextRule ctx = new HystrixRequestContextRule(); @After public void cleanup() { // force properties to be clean as well ConfigurationManager.getConfigInstance().clear(); /* * RxJava will create one worker for each processor when we schedule Observables in the * Schedulers.computation(). Any leftovers here might lead to a congestion in a following * thread. To ensure all existing threads have completed we now schedule some observables * that will execute in distinct threads due to the latch.. */ int count = Runtime.getRuntime().availableProcessors(); final CountDownLatch latch = new CountDownLatch(count); ArrayList<Future<Boolean>> futures = new ArrayList<Future<Boolean>>(); for (int i = 0; i < count; ++i) { futures.add(Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> sub) { latch.countDown(); try { latch.await(); sub.onNext(true); sub.onCompleted(); } catch (InterruptedException e) { sub.onError(e); } } }).subscribeOn(Schedulers.computation()).toBlocking().toFuture()); } for (Future<Boolean> future : futures) { try { future.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } } //TODO commented out as it has issues when built from command-line even though it works from IDE // HystrixCommandKey key = Hystrix.getCurrentThreadExecutingCommand(); // if (key != null) { // throw new IllegalStateException("should be null but got: " + key); // } } class CompletableCommand extends HystrixObservableCommand<Integer> { CompletableCommand() { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("COMPLETABLE"))); } @Override protected Observable<Integer> construct() { return Completable.complete().toObservable(); } } @Test public void testCompletable() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final HystrixObservableCommand<Integer> command = new CompletableCommand(); command.observe().subscribe(new Subscriber<Integer>() { @Override public void onCompleted() { System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnCompleted"); latch.countDown(); } @Override public void onError(Throwable e) { System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnError : " + e); latch.countDown(); } @Override public void onNext(Integer integer) { System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnNext : " + integer); } }); latch.await(); assertEquals(null, command.getFailedExecutionException()); System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isSuccessfulExecution()); assertFalse(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertNull(command.getExecutionException()); } /** * Test a successful semaphore-isolated command execution. */ @Test public void testSemaphoreObserveSuccess() { testObserveSuccess(ExecutionIsolationStrategy.SEMAPHORE); } /** * Test a successful thread-isolated command execution. */ @Test public void testThreadObserveSuccess() { testObserveSuccess(ExecutionIsolationStrategy.THREAD); } private void testObserveSuccess(ExecutionIsolationStrategy isolationStrategy) { try { TestHystrixObservableCommand<Boolean> command = new SuccessfulTestCommand(isolationStrategy); assertEquals(true, command.observe().toBlocking().single()); assertEquals(null, command.getFailedExecutionException()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isSuccessfulExecution()); assertFalse(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertNull(command.getExecutionException()); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } } /** * Test that a semaphore command can not be executed multiple times. */ @Test public void testSemaphoreIsolatedObserveMultipleTimes() { testObserveMultipleTimes(ExecutionIsolationStrategy.SEMAPHORE); } /** * Test that a thread command can not be executed multiple times. */ @Test public void testThreadIsolatedObserveMultipleTimes() { testObserveMultipleTimes(ExecutionIsolationStrategy.THREAD); } private void testObserveMultipleTimes(ExecutionIsolationStrategy isolationStrategy) { SuccessfulTestCommand command = new SuccessfulTestCommand(isolationStrategy); assertFalse(command.isExecutionComplete()); // first should succeed assertEquals(true, command.observe().toBlocking().single()); assertTrue(command.isExecutionComplete()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isSuccessfulExecution()); assertFalse(command.isResponseFromFallback()); assertNull(command.getExecutionException()); try { // second should fail command.observe().toBlocking().single(); fail("we should not allow this ... it breaks the state of request logs"); } catch (HystrixRuntimeException e) { e.printStackTrace(); // we want to get here } assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); assertSaneHystrixRequestLog(1); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); } /** * Test a semaphore command execution that throws an HystrixException synchronously and didn't implement getFallback. */ @Test public void testSemaphoreIsolatedObserveKnownSyncFailureWithNoFallback() { testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, false); } /** * Test a semaphore command execution that throws an HystrixException asynchronously and didn't implement getFallback. */ @Test public void testSemaphoreIsolatedObserveKnownAsyncFailureWithNoFallback() { testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, true); } /** * Test a thread command execution that throws an HystrixException synchronously and didn't implement getFallback. */ @Test public void testThreadIsolatedObserveKnownSyncFailureWithNoFallback() { testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, false); } /** * Test a thread command execution that throws an HystrixException asynchronously and didn't implement getFallback. */ @Test public void testThreadIsolatedObserveKnownAsyncFailureWithNoFallback() { testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, true); } private void testObserveKnownFailureWithNoFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); TestHystrixObservableCommand<Boolean> command = new KnownFailureTestCommandWithoutFallback(circuitBreaker, isolationStrategy, asyncException); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (HystrixRuntimeException e) { e.printStackTrace(); assertNotNull(e.getFallbackException()); assertNotNull(e.getImplementingClass()); } catch (Exception e) { e.printStackTrace(); fail("We should always get an HystrixRuntimeException when an error occurs."); } assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertFalse(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertNotNull(command.getExecutionException()); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a semaphore command execution that throws an unknown exception (not HystrixException) synchronously and didn't implement getFallback. */ @Test public void testSemaphoreIsolatedObserveUnknownSyncFailureWithNoFallback() { testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, false); } /** * Test a semaphore command execution that throws an unknown exception (not HystrixException) asynchronously and didn't implement getFallback. */ @Test public void testSemaphoreIsolatedObserveUnknownAsyncFailureWithNoFallback() { testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE, true); } /** * Test a thread command execution that throws an unknown exception (not HystrixException) synchronously and didn't implement getFallback. */ @Test public void testThreadIsolatedObserveUnknownSyncFailureWithNoFallback() { testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, false); } /** * Test a thread command execution that throws an unknown exception (not HystrixException) asynchronously and didn't implement getFallback. */ @Test public void testThreadIsolatedObserveUnknownAsyncFailureWithNoFallback() { testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy.THREAD, true); } private void testObserveUnknownFailureWithNoFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { TestHystrixObservableCommand<Boolean> command = new UnknownFailureTestCommandWithoutFallback(isolationStrategy, asyncException); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (HystrixRuntimeException e) { e.printStackTrace(); assertNotNull(e.getFallbackException()); assertNotNull(e.getImplementingClass()); } catch (Exception e) { e.printStackTrace(); fail("We should always get an HystrixRuntimeException when an error occurs."); } assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertFalse(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertNotNull(command.getExecutionException()); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a semaphore command execution that fails synchronously but has a fallback. */ @Test public void testSemaphoreIsolatedObserveSyncFailureWithFallback() { testObserveFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, false); } /** * Test a semaphore command execution that fails asynchronously but has a fallback. */ @Test public void testSemaphoreIsolatedObserveAsyncFailureWithFallback() { testObserveFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, true); } /** * Test a thread command execution that fails synchronously but has a fallback. */ @Test public void testThreadIsolatedObserveSyncFailureWithFallback() { testObserveFailureWithFallback(ExecutionIsolationStrategy.THREAD, false); } /** * Test a thread command execution that fails asynchronously but has a fallback. */ @Test public void testThreadIsolatedObserveAsyncFailureWithFallback() { testObserveFailureWithFallback(ExecutionIsolationStrategy.THREAD, true); } private void testObserveFailureWithFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { TestHystrixObservableCommand<Boolean> command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), isolationStrategy, asyncException); try { assertEquals(false, command.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); } assertEquals("we failed with a simulated issue", command.getFailedExecutionException().getMessage()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertTrue(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertNotNull(command.getExecutionException()); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (synchronously). */ @Test public void testSemaphoreIsolatedObserveSyncFailureWithSyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, false, false); } /** * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (asynchronously). */ @Test public void testSemaphoreIsolatedObserveSyncFailureWithAsyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, false, true); } /** * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (synchronously). */ @Test public void testSemaphoreIsolatedObserveAyncFailureWithSyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, true, false); } /** * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (asynchronously). */ @Test public void testSemaphoreIsolatedObserveAsyncFailureWithAsyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, true, true); } /** * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (synchronously). */ @Test public void testThreadIsolatedObserveSyncFailureWithSyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, false, false); } /** * Test a command execution that fails synchronously, has getFallback implemented but that fails as well (asynchronously). */ @Test public void testThreadIsolatedObserveSyncFailureWithAsyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, true, false); } /** * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (synchronously). */ @Test public void testThreadIsolatedObserveAyncFailureWithSyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, false, true); } /** * Test a command execution that fails asynchronously, has getFallback implemented but that fails as well (asynchronously). */ @Test public void testThreadIsolatedObserveAsyncFailureWithAsyncFallbackFailure() { testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy.THREAD, true, true); } private void testObserveFailureWithFallbackFailure(ExecutionIsolationStrategy isolationStrategy, boolean asyncFallbackException, boolean asyncConstructException) { TestHystrixObservableCommand<Boolean> command = new KnownFailureTestCommandWithFallbackFailure(new TestCircuitBreaker(), isolationStrategy, asyncConstructException, asyncFallbackException); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (HystrixRuntimeException e) { System.out.println("------------------------------------------------"); e.printStackTrace(); System.out.println("------------------------------------------------"); assertNotNull(e.getFallbackException()); } assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertFalse(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertNotNull(command.getExecutionException()); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a semaphore command execution that times out with a fallback and eventually succeeds. */ @Test public void testSemaphoreIsolatedObserveTimeoutWithSuccessAndFallback() { testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.SEMAPHORE, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS); } /** * Test a semaphore command execution that times out with a fallback and eventually fails. */ @Test public void testSemaphoreIsolatedObserveTimeoutWithFailureAndFallback() { testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.SEMAPHORE, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE); } /** * Test a thread command execution that times out with a fallback and eventually succeeds. */ @Test public void testThreadIsolatedObserveTimeoutWithSuccessAndFallback() { testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.THREAD, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS); } /** * Test a thread command execution that times out with a fallback and eventually fails. */ @Test public void testThreadIsolatedObserveTimeoutWithFailureAndFallback() { testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy.THREAD, TestHystrixObservableCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE); } private void testObserveFailureWithTimeoutAndFallback(ExecutionIsolationStrategy isolationStrategy, TestHystrixObservableCommand.ExecutionResult executionResult) { TestHystrixObservableCommand<Integer> command = getCommand(isolationStrategy, executionResult, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 100); long observedCommandDuration = 0; try { long startTime = System.currentTimeMillis(); assertEquals(FlexibleTestHystrixObservableCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); observedCommandDuration = System.currentTimeMillis() - startTime; } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); } assertNull(command.getFailedExecutionException()); assertNotNull(command.getExecutionException()); System.out.println("Command time : " + command.getExecutionTimeInMilliseconds()); System.out.println("Observed command time : " + observedCommandDuration); assertTrue(command.getExecutionTimeInMilliseconds() >= 100); assertTrue(observedCommandDuration >= 100); assertTrue(command.getExecutionTimeInMilliseconds() < 1000); assertTrue(observedCommandDuration < 1000); assertFalse(command.isFailedExecution()); assertTrue(command.isResponseFromFallback()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a successful command execution. */ @Test public void testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation() throws Exception { final AtomicReference<Thread> commandThread = new AtomicReference<Thread>(); final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder() .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))) { @Override protected Observable<Boolean> construct() { commandThread.set(Thread.currentThread()); return Observable.just(true); } }; final CountDownLatch latch = new CountDownLatch(1); command.toObservable().subscribe(new Observer<Boolean>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { latch.countDown(); e.printStackTrace(); } @Override public void onNext(Boolean args) { subscribeThread.set(Thread.currentThread()); } }); if (!latch.await(2000, TimeUnit.MILLISECONDS)) { fail("timed out"); } assertNotNull(commandThread.get()); assertNotNull(subscribeThread.get()); System.out.println("Command Thread: " + commandThread.get()); System.out.println("Subscribe Thread: " + subscribeThread.get()); String mainThreadName = Thread.currentThread().getName(); // semaphore should be on the calling thread assertTrue(commandThread.get().getName().equals(mainThreadName)); System.out.println("testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation: " + subscribeThread.get() + " => " + mainThreadName); assertTrue(subscribeThread.get().getName().equals(mainThreadName)); // semaphore isolated assertFalse(command.isExecutedInThread()); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertSaneHystrixRequestLog(1); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertNull(command.getExecutionException()); assertFalse(command.isResponseFromFallback()); } /** * Test that the circuit-breaker will 'trip' and prevent command execution on subsequent calls. */ @Test public void testCircuitBreakerTripsAfterFailures() throws InterruptedException { HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("KnownFailureTestCommandWithFallback"); TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(commandKey); /* fail 3 times and then it should trip the circuit and stop executing */ // failure 1 KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); attempt1.observe().toBlocking().single(); Thread.sleep(100); assertTrue(attempt1.isResponseFromFallback()); assertFalse(attempt1.isCircuitBreakerOpen()); assertFalse(attempt1.isResponseShortCircuited()); // failure 2 KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); attempt2.observe().toBlocking().single(); Thread.sleep(100); assertTrue(attempt2.isResponseFromFallback()); assertFalse(attempt2.isCircuitBreakerOpen()); assertFalse(attempt2.isResponseShortCircuited()); // failure 3 KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); attempt3.observe().toBlocking().single(); Thread.sleep(100); assertTrue(attempt3.isResponseFromFallback()); assertFalse(attempt3.isResponseShortCircuited()); // it should now be 'open' and prevent further executions assertTrue(attempt3.isCircuitBreakerOpen()); // attempt 4 KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); attempt4.observe().toBlocking().single(); Thread.sleep(100); assertTrue(attempt4.isResponseFromFallback()); // this should now be true as the response will be short-circuited assertTrue(attempt4.isResponseShortCircuited()); // this should remain open assertTrue(attempt4.isCircuitBreakerOpen()); assertCommandExecutionEvents(attempt1, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertCommandExecutionEvents(attempt2, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertCommandExecutionEvents(attempt3, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertCommandExecutionEvents(attempt4, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); } /** * Test that the circuit-breaker being disabled doesn't wreak havoc. */ @Test public void testExecutionSuccessWithCircuitBreakerDisabled() { TestHystrixObservableCommand<Boolean> command = new TestCommandWithoutCircuitBreaker(); try { assertEquals(true, command.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertNull(command.getExecutionException()); } /** * Test a command execution timeout where the command didn't implement getFallback. */ @Test public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { TestHystrixObservableCommand<Integer> command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 100); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { e.printStackTrace(); if (e instanceof HystrixRuntimeException) { HystrixRuntimeException de = (HystrixRuntimeException) e; assertNotNull(de.getFallbackException()); assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); assertNotNull(de.getImplementingClass()); assertNotNull(de.getCause()); assertTrue(de.getCause() instanceof TimeoutException); } else { fail("the exception should be HystrixRuntimeException"); } } // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertTrue(command.isResponseTimedOut()); assertFalse(command.isResponseFromFallback()); assertFalse(command.isResponseRejected()); assertNotNull(command.getExecutionException()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); // semaphore isolated assertFalse(command.isExecutedInThread()); } /** * Test a command execution timeout where the command implemented getFallback. */ @Test public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { TestHystrixObservableCommand<Integer> command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50); try { assertEquals(FlexibleTestHystrixObservableCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertTrue(command.isResponseTimedOut()); assertTrue(command.isResponseFromFallback()); assertNotNull(command.getExecutionException()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); } assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); // semaphore isolated assertFalse(command.isExecutedInThread()); } /** * Test a command execution timeout where the command implemented getFallback but it fails. */ @Test public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { TestHystrixObservableCommand<Integer> command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, 200); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { if (e instanceof HystrixRuntimeException) { e.printStackTrace(); HystrixRuntimeException de = (HystrixRuntimeException) e; assertNotNull(de.getFallbackException()); assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); assertNotNull(de.getImplementingClass()); assertNotNull(de.getCause()); assertTrue(de.getCause() instanceof TimeoutException); assertNotNull(command.getExecutionException()); } else { fail("the exception should be HystrixRuntimeException"); } } // the time should be 200+ since we timeout at 200ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 200); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); // semaphore isolated assertFalse(command.isExecutedInThread()); } /** * Test a semaphore command execution timeout where the command didn't implement getFallback. */ @Test public void testSemaphoreExecutionTimeoutWithNoFallback() { testExecutionTimeoutWithNoFallback(ExecutionIsolationStrategy.SEMAPHORE); } /** * Test a thread command execution timeout where the command didn't implement getFallback. */ @Test public void testThreadExecutionTimeoutWithNoFallback() { testExecutionTimeoutWithNoFallback(ExecutionIsolationStrategy.THREAD); } private void testExecutionTimeoutWithNoFallback(ExecutionIsolationStrategy isolationStrategy) { TestHystrixObservableCommand<Integer> command = getCommand(isolationStrategy, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { e.printStackTrace(); if (e instanceof HystrixRuntimeException) { HystrixRuntimeException de = (HystrixRuntimeException) e; assertNotNull(de.getFallbackException()); assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); assertNotNull(de.getImplementingClass()); assertNotNull(de.getCause()); assertTrue(de.getCause() instanceof TimeoutException); } else { fail("the exception should be HystrixRuntimeException"); } } // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertTrue(command.isResponseTimedOut()); assertFalse(command.isResponseFromFallback()); assertFalse(command.isResponseRejected()); assertNotNull(command.getExecutionException()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a semaphore command execution timeout where the command implemented getFallback. */ @Test public void testSemaphoreIsolatedExecutionTimeoutWithSuccessfulFallback() { testExecutionTimeoutWithSuccessfulFallback(ExecutionIsolationStrategy.SEMAPHORE); } /** * Test a thread command execution timeout where the command implemented getFallback. */ @Test public void testThreadIsolatedExecutionTimeoutWithSuccessfulFallback() { testExecutionTimeoutWithSuccessfulFallback(ExecutionIsolationStrategy.THREAD); } private void testExecutionTimeoutWithSuccessfulFallback(ExecutionIsolationStrategy isolationStrategy) { TestHystrixObservableCommand<Integer> command = getCommand(isolationStrategy, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 100); try { assertEquals(FlexibleTestHystrixObservableCommand.FALLBACK_VALUE, command.observe().toBlocking().single()); // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertTrue(command.isResponseTimedOut()); assertTrue(command.isResponseFromFallback()); assertNotNull(command.getExecutionException()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); } assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test a semaphore command execution timeout where the command implemented getFallback but it fails synchronously. */ @Test public void testSemaphoreExecutionTimeoutSyncFallbackFailure() { testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, false); } /** * Test a semaphore command execution timeout where the command implemented getFallback but it fails asynchronously. */ @Test public void testSemaphoreExecutionTimeoutAsyncFallbackFailure() { testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.SEMAPHORE, true); } /** * Test a thread command execution timeout where the command implemented getFallback but it fails synchronously. */ @Test public void testThreadExecutionTimeoutSyncFallbackFailure() { testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.THREAD, false); } /** * Test a thread command execution timeout where the command implemented getFallback but it fails asynchronously. */ @Test public void testThreadExecutionTimeoutAsyncFallbackFailure() { testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy.THREAD, true); } private void testExecutionTimeoutFallbackFailure(ExecutionIsolationStrategy isolationStrategy, boolean asyncFallbackException) { TestHystrixObservableCommand<Integer> command = getCommand(isolationStrategy, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 100); try { command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { if (e instanceof HystrixRuntimeException) { HystrixRuntimeException de = (HystrixRuntimeException) e; assertNotNull(de.getFallbackException()); assertFalse(de.getFallbackException() instanceof UnsupportedOperationException); assertNotNull(de.getImplementingClass()); assertNotNull(de.getCause()); assertTrue(de.getCause() instanceof TimeoutException); assertNotNull(command.getExecutionException()); } else { fail("the exception should be HystrixRuntimeException"); } } // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertEquals(isolationStrategy.equals(ExecutionIsolationStrategy.THREAD), command.isExecutedInThread()); } /** * Test that the circuit-breaker counts a command execution timeout as a 'timeout' and not just failure. */ @Test public void testShortCircuitFallbackCounter() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); KnownFailureTestCommandWithFallback command1 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); KnownFailureTestCommandWithFallback command2 = new KnownFailureTestCommandWithFallback(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE, true); try { command1.observe().toBlocking().single(); command2.observe().toBlocking().single(); // will be -1 because it never attempted execution assertEquals(-1, command2.getExecutionTimeInMilliseconds()); assertTrue(command2.isResponseShortCircuited()); assertFalse(command2.isResponseTimedOut()); assertNotNull(command2.getExecutionException()); // semaphore isolated assertFalse(command2.isExecutedInThread()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); } assertCommandExecutionEvents(command1, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertSaneHystrixRequestLog(2); } @Test public void testExecutionSemaphoreWithObserve() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); TestSemaphoreCommand command1 = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); // single thread should work try { boolean result = command1.observe().toBlocking().toFuture().get(); assertTrue(result); } catch (Exception e) { // we shouldn't fail on this one throw new RuntimeException(e); } final AtomicBoolean exceptionReceived = new AtomicBoolean(); final TryableSemaphoreActual semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); final TestSemaphoreCommand command2 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { try { command2.observe().toBlocking().toFuture().get(); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); final TestSemaphoreCommand command3 = new TestSemaphoreCommand(circuitBreaker, semaphore, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED); Runnable r3 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { try { command3.observe().toBlocking().toFuture().get(); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); // 2 threads, the second should be rejected by the semaphore Thread t2 = new Thread(r2); Thread t3 = new Thread(r3); t2.start(); try { Thread.sleep(100); } catch (Throwable ex) { fail(ex.getMessage()); } t3.start(); try { t2.join(); t3.join(); } catch (Exception e) { e.printStackTrace(); fail("failed waiting on threads"); } if (!exceptionReceived.get()) { fail("We expected an exception on the 2nd get"); } System.out.println("CMD1 : " + command1.getExecutionEvents()); System.out.println("CMD2 : " + command2.getExecutionEvents()); System.out.println("CMD3 : " + command3.getExecutionEvents()); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command3, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_MISSING); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(3); } @Test public void testRejectedExecutionSemaphoreWithFallback() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); final ArrayBlockingQueue<Boolean> results = new ArrayBlockingQueue<Boolean>(2); final AtomicBoolean exceptionReceived = new AtomicBoolean(); final TestSemaphoreCommandWithFallback command1 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); Runnable r1 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { try { results.add(command1.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); final TestSemaphoreCommandWithFallback command2 = new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false); Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { try { results.add(command2.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); // 2 threads, the second should be rejected by the semaphore and return fallback Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); try { //give t1 a headstart Thread.sleep(50); } catch (InterruptedException ex) { fail(ex.getMessage()); } t2.start(); try { t1.join(); t2.join(); } catch (Exception e) { e.printStackTrace(); fail("failed waiting on threads"); } if (exceptionReceived.get()) { fail("We should have received a fallback response"); } // both threads should have returned values assertEquals(2, results.size()); // should contain both a true and false result assertTrue(results.contains(Boolean.TRUE)); assertTrue(results.contains(Boolean.FALSE)); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command1.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(2); } @Test public void testSemaphorePermitsInUse() { // this semaphore will be shared across multiple command instances final TryableSemaphoreActual sharedSemaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(3)); // creates thread using isolated semaphore final TryableSemaphoreActual isolatedSemaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); //used to wait until all commands are started final CountDownLatch startLatch = new CountDownLatch((sharedSemaphore.numberOfPermits.get()) * 2 + 1); // used to signal that all command can finish final CountDownLatch sharedLatch = new CountDownLatch(1); final CountDownLatch isolatedLatch = new CountDownLatch(1); final List<HystrixObservableCommand<Boolean>> commands = new ArrayList<HystrixObservableCommand<Boolean>>(); final List<Observable<Boolean>> results = new ArrayList<Observable<Boolean>>(); HystrixObservableCommand<Boolean> isolated = new LatchedSemaphoreCommand("ObservableCommand-Isolated", circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch); commands.add(isolated); for (int s = 0; s < sharedSemaphore.numberOfPermits.get() * 2; s++) { HystrixObservableCommand<Boolean> shared = new LatchedSemaphoreCommand("ObservableCommand-Shared", circuitBreaker, sharedSemaphore, startLatch, sharedLatch); commands.add(shared); Observable<Boolean> result = shared.toObservable(); results.add(result); } Observable<Boolean> isolatedResult = isolated.toObservable(); results.add(isolatedResult); // verifies no permits in use before starting commands assertEquals("before commands start, shared semaphore should be unused", 0, sharedSemaphore.getNumberOfPermitsUsed()); assertEquals("before commands start, isolated semaphore should be unused", 0, isolatedSemaphore.getNumberOfPermitsUsed()); final CountDownLatch allTerminal = new CountDownLatch(1); Observable.merge(results) .subscribeOn(Schedulers.computation()) .subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { System.out.println(Thread.currentThread().getName() + " OnCompleted"); allTerminal.countDown(); } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " OnError : " + e); allTerminal.countDown(); } @Override public void onNext(Boolean b) { System.out.println(Thread.currentThread().getName() + " OnNext : " + b); } }); try { assertTrue(startLatch.await(1000, TimeUnit.MILLISECONDS)); } catch (Throwable ex) { fail(ex.getMessage()); } // verifies that all semaphores are in use assertEquals("immediately after command start, all shared semaphores should be in-use", sharedSemaphore.numberOfPermits.get().longValue(), sharedSemaphore.getNumberOfPermitsUsed()); assertEquals("immediately after command start, isolated semaphore should be in-use", isolatedSemaphore.numberOfPermits.get().longValue(), isolatedSemaphore.getNumberOfPermitsUsed()); // signals commands to finish sharedLatch.countDown(); isolatedLatch.countDown(); try { assertTrue(allTerminal.await(1000, TimeUnit.MILLISECONDS)); } catch (Exception e) { e.printStackTrace(); fail("failed waiting on commands"); } // verifies no permits in use after finishing threads assertEquals("after all threads have finished, no shared semaphores should be in-use", 0, sharedSemaphore.getNumberOfPermitsUsed()); assertEquals("after all threads have finished, isolated semaphore not in-use", 0, isolatedSemaphore.getNumberOfPermitsUsed()); // verifies that some executions failed int numSemaphoreRejected = 0; for (HystrixObservableCommand<Boolean> cmd: commands) { if (cmd.isResponseSemaphoreRejected()) { numSemaphoreRejected++; } } assertEquals("expected some of shared semaphore commands to get rejected", sharedSemaphore.numberOfPermits.get().longValue(), numSemaphoreRejected); } /** * Test that HystrixOwner can be passed in dynamically. */ @Test public void testDynamicOwner() { try { TestHystrixObservableCommand<Boolean> command = new DynamicOwnerTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE); assertEquals(true, command.observe().toBlocking().single()); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); // semaphore isolated assertFalse(command.isExecutedInThread()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } } /** * Test a successful command execution. */ @Test public void testDynamicOwnerFails() { try { TestHystrixObservableCommand<Boolean> command = new DynamicOwnerTestCommand(null); assertEquals(true, command.observe().toBlocking().single()); fail("we should have thrown an exception as we need an owner"); // semaphore isolated assertFalse(command.isExecutedInThread()); assertEquals(0, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); } catch (Exception e) { // success if we get here } } /** * Test that HystrixCommandKey can be passed in dynamically. */ @Test public void testDynamicKey() { try { DynamicOwnerAndKeyTestCommand command1 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_ONE); assertEquals(true, command1.observe().toBlocking().single()); DynamicOwnerAndKeyTestCommand command2 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_TWO); assertEquals(true, command2.observe().toBlocking().single()); // 2 different circuit breakers should be created assertNotSame(command1.getCircuitBreaker(), command2.getCircuitBreaker()); // semaphore isolated assertFalse(command1.isExecutedInThread()); assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } } /** * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future */ @Test public void testRequestCache1UsingThreadIsolation() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SuccessfulCacheableCommand<String> command1 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "A"); SuccessfulCacheableCommand<String> command2 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "A"); assertTrue(command1.isCommandRunningInThread()); Future<String> f1 = command1.observe().toBlocking().toFuture(); Future<String> f2 = command2.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); assertEquals("A", f2.get()); } catch (Exception e) { throw new RuntimeException(e); } assertTrue(command1.executed); // the second one should not have executed as it should have received the cached value instead assertFalse(command2.executed); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertTrue(command1.getExecutionTimeInMilliseconds() > -1); assertFalse(command1.isResponseFromCache()); assertNull(command1.getExecutionException()); // the execution log for command2 should show it came from cache assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertTrue(command2.getExecutionTimeInMilliseconds() == -1); assertTrue(command2.isResponseFromCache()); assertNull(command2.getExecutionException()); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(2); } /** * Test Request scoped caching doesn't prevent different ones from executing */ @Test public void testRequestCache2UsingThreadIsolation() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SuccessfulCacheableCommand<String> command1 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "A"); SuccessfulCacheableCommand<String> command2 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "B"); assertTrue(command1.isCommandRunningInThread()); Future<String> f1 = command1.observe().toBlocking().toFuture(); Future<String> f2 = command2.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); assertEquals("B", f2.get()); } catch (Exception e) { throw new RuntimeException(e); } assertTrue(command1.executed); // both should execute as they are different assertTrue(command2.executed); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertTrue(command2.getExecutionTimeInMilliseconds() > -1); assertFalse(command2.isResponseFromCache()); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(2); } /** * Test Request scoped caching with a mixture of commands */ @Test public void testRequestCache3UsingThreadIsolation() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SuccessfulCacheableCommand<String> command1 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "A"); SuccessfulCacheableCommand<String> command2 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "B"); SuccessfulCacheableCommand<String> command3 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "A"); assertTrue(command1.isCommandRunningInThread()); Future<String> f1 = command1.observe().toBlocking().toFuture(); Future<String> f2 = command2.observe().toBlocking().toFuture(); Future<String> f3 = command3.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); assertEquals("B", f2.get()); assertEquals("A", f3.get()); } catch (Exception e) { throw new RuntimeException(e); } assertTrue(command1.executed); // both should execute as they are different assertTrue(command2.executed); // but the 3rd should come from cache assertFalse(command3.executed); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command3, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertTrue(command3.getExecutionTimeInMilliseconds() == -1); assertTrue(command3.isResponseFromCache()); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(3); } /** * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future */ @Test public void testRequestCacheWithSlowExecution() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SlowCacheableCommand command1 = new SlowCacheableCommand(circuitBreaker, "A", 200); SlowCacheableCommand command2 = new SlowCacheableCommand(circuitBreaker, "A", 100); SlowCacheableCommand command3 = new SlowCacheableCommand(circuitBreaker, "A", 100); SlowCacheableCommand command4 = new SlowCacheableCommand(circuitBreaker, "A", 100); Future<String> f1 = command1.observe().toBlocking().toFuture(); Future<String> f2 = command2.observe().toBlocking().toFuture(); Future<String> f3 = command3.observe().toBlocking().toFuture(); Future<String> f4 = command4.observe().toBlocking().toFuture(); try { assertEquals("A", f2.get()); assertEquals("A", f3.get()); assertEquals("A", f4.get()); assertEquals("A", f1.get()); } catch (Exception e) { throw new RuntimeException(e); } assertTrue(command1.executed); // the second one should not have executed as it should have received the cached value instead assertFalse(command2.executed); assertFalse(command3.executed); assertFalse(command4.executed); // the execution log for command1 should show an EMIT and a SUCCESS assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertTrue(command1.getExecutionTimeInMilliseconds() > -1); assertFalse(command1.isResponseFromCache()); // the execution log for command2 should show it came from cache assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertTrue(command2.getExecutionTimeInMilliseconds() == -1); assertTrue(command2.isResponseFromCache()); assertCommandExecutionEvents(command3, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertTrue(command3.isResponseFromCache()); assertTrue(command3.getExecutionTimeInMilliseconds() == -1); assertCommandExecutionEvents(command4, HystrixEventType.EMIT, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertTrue(command4.isResponseFromCache()); assertTrue(command4.getExecutionTimeInMilliseconds() == -1); assertSaneHystrixRequestLog(4); // semaphore isolated assertFalse(command1.isExecutedInThread()); assertFalse(command2.isExecutedInThread()); assertFalse(command3.isExecutedInThread()); assertFalse(command4.isExecutedInThread()); } /** * Test Request scoped caching with a mixture of commands */ @Test public void testNoRequestCache3UsingThreadIsolation() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SuccessfulCacheableCommand<String> command1 = new SuccessfulCacheableCommand<String>(circuitBreaker, false, "A"); SuccessfulCacheableCommand<String> command2 = new SuccessfulCacheableCommand<String>(circuitBreaker, false, "B"); SuccessfulCacheableCommand<String> command3 = new SuccessfulCacheableCommand<String>(circuitBreaker, false, "A"); assertTrue(command1.isCommandRunningInThread()); Future<String> f1 = command1.observe().toBlocking().toFuture(); Future<String> f2 = command2.observe().toBlocking().toFuture(); Future<String> f3 = command3.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); assertEquals("B", f2.get()); assertEquals("A", f3.get()); } catch (Exception e) { throw new RuntimeException(e); } assertTrue(command1.executed); // both should execute as they are different assertTrue(command2.executed); // this should also execute since we disabled the cache assertTrue(command3.executed); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command3, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(3); // thread isolated assertTrue(command1.isExecutedInThread()); assertTrue(command2.isExecutedInThread()); assertTrue(command3.isExecutedInThread()); } @Test public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); NoRequestCacheTimeoutWithoutFallback r1 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); try { System.out.println("r1 value: " + r1.observe().toBlocking().single()); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { assertTrue(r1.isResponseTimedOut()); // what we want } NoRequestCacheTimeoutWithoutFallback r2 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); try { r2.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { assertTrue(r2.isResponseTimedOut()); // what we want } NoRequestCacheTimeoutWithoutFallback r3 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); Future<Boolean> f3 = r3.observe().toBlocking().toFuture(); try { f3.get(); // we should have thrown an exception fail("expected a timeout"); } catch (ExecutionException e) { e.printStackTrace(); assertTrue(r3.isResponseTimedOut()); // what we want } Thread.sleep(500); // timeout on command is set to 200ms NoRequestCacheTimeoutWithoutFallback r4 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); try { r4.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { e.printStackTrace(); assertTrue(r4.isResponseTimedOut()); assertFalse(r4.isResponseFromFallback()); // what we want } assertCommandExecutionEvents(r1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertCommandExecutionEvents(r2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertCommandExecutionEvents(r3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertCommandExecutionEvents(r4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(4); } @Test public void testRequestCacheOnTimeoutCausesNullPointerException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); RequestCacheNullPointerExceptionCase command1 = new RequestCacheNullPointerExceptionCase(circuitBreaker); RequestCacheNullPointerExceptionCase command2 = new RequestCacheNullPointerExceptionCase(circuitBreaker); RequestCacheNullPointerExceptionCase command3 = new RequestCacheNullPointerExceptionCase(circuitBreaker); RequestCacheNullPointerExceptionCase command4 = new RequestCacheNullPointerExceptionCase(circuitBreaker); RequestCacheNullPointerExceptionCase command5 = new RequestCacheNullPointerExceptionCase(circuitBreaker); // Expect it to time out - all results should be false assertFalse(command1.observe().toBlocking().single()); assertFalse(command2.observe().toBlocking().single()); // return from cache #1 assertFalse(command3.observe().toBlocking().single()); // return from cache #2 Thread.sleep(500); // timeout on command is set to 200ms Boolean value = command4.observe().toBlocking().single(); // return from cache #3 assertFalse(value); Future<Boolean> f = command5.observe().toBlocking().toFuture(); // return from cache #4 // the bug is that we're getting a null Future back, rather than a Future that returns false assertNotNull(f); assertFalse(f.get()); assertTrue(command5.isResponseFromFallback()); assertTrue(command5.isResponseTimedOut()); assertFalse(command5.isFailedExecution()); assertFalse(command5.isResponseShortCircuited()); assertNotNull(command5.getExecutionException()); assertCommandExecutionEvents(command1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(command3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(command4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(command5, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(5); } @Test public void testRequestCacheOnTimeoutThrowsException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); RequestCacheTimeoutWithoutFallback r1 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); try { System.out.println("r1 value: " + r1.observe().toBlocking().single()); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { assertTrue(r1.isResponseTimedOut()); assertNotNull(r1.getExecutionException()); // what we want } RequestCacheTimeoutWithoutFallback r2 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); try { r2.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { assertTrue(r2.isResponseTimedOut()); assertNotNull(r2.getExecutionException()); // what we want } RequestCacheTimeoutWithoutFallback r3 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); Future<Boolean> f3 = r3.observe().toBlocking().toFuture(); try { f3.get(); // we should have thrown an exception fail("expected a timeout"); } catch (ExecutionException e) { e.printStackTrace(); assertTrue(r3.isResponseTimedOut()); assertNotNull(r3.getExecutionException()); // what we want } Thread.sleep(500); // timeout on command is set to 200ms RequestCacheTimeoutWithoutFallback r4 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); try { r4.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { assertTrue(r4.isResponseTimedOut()); assertFalse(r4.isResponseFromFallback()); assertNotNull(r4.getExecutionException()); } assertCommandExecutionEvents(r1, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertCommandExecutionEvents(r2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(r3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(r4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(4); } @Test public void testRequestCacheOnThreadRejectionThrowsException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CountDownLatch completionLatch = new CountDownLatch(1); RequestCacheThreadRejectionWithoutFallback r1 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { System.out.println("r1: " + r1.observe().toBlocking().single()); // we should have thrown an exception fail("expected a rejection"); } catch (HystrixRuntimeException e) { e.printStackTrace(); assertTrue(r1.isResponseRejected()); assertNotNull(r1.getExecutionException()); // what we want } RequestCacheThreadRejectionWithoutFallback r2 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { System.out.println("r2: " + r2.observe().toBlocking().single()); // we should have thrown an exception fail("expected a rejection"); } catch (HystrixRuntimeException e) { // e.printStackTrace(); assertTrue(r2.isResponseRejected()); assertNotNull(r2.getExecutionException()); // what we want } RequestCacheThreadRejectionWithoutFallback r3 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { System.out.println("f3: " + r3.observe().toBlocking().toFuture().get()); // we should have thrown an exception fail("expected a rejection"); } catch (ExecutionException e) { assertTrue(r3.isResponseRejected()); assertTrue(e.getCause() instanceof HystrixRuntimeException); assertNotNull(r3.getExecutionException()); } // let the command finish (only 1 should actually be blocked on this due to the response cache) completionLatch.countDown(); // then another after the command has completed RequestCacheThreadRejectionWithoutFallback r4 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { System.out.println("r4: " + r4.observe().toBlocking().single()); // we should have thrown an exception fail("expected a rejection"); } catch (HystrixRuntimeException e) { // e.printStackTrace(); assertTrue(r4.isResponseRejected()); assertFalse(r4.isResponseFromFallback()); assertNotNull(r4.getExecutionException()); // what we want } assertCommandExecutionEvents(r1, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); assertCommandExecutionEvents(r2, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(r3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); assertCommandExecutionEvents(r4, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING, HystrixEventType.RESPONSE_FROM_CACHE); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(4); } /** * Test that we can do basic execution without a RequestVariable being initialized. */ @Test public void testBasicExecutionWorksWithoutRequestVariable() { try { /* force the RequestVariable to not be initialized */ HystrixRequestContext.setContextOnCurrentThread(null); TestHystrixObservableCommand<Boolean> command = new SuccessfulTestCommand(ExecutionIsolationStrategy.SEMAPHORE); assertEquals(true, command.observe().toBlocking().single()); TestHystrixObservableCommand<Boolean> command2 = new SuccessfulTestCommand(ExecutionIsolationStrategy.SEMAPHORE); assertEquals(true, command2.observe().toBlocking().toFuture().get()); // we should be able to execute without a RequestVariable if ... // 1) We don't have a cacheKey // 2) We don't ask for the RequestLog // 3) We don't do collapsing // semaphore isolated assertFalse(command.isExecutedInThread()); assertNull(command.getExecutionException()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception => " + e.getMessage()); } assertNull(HystrixRequestLog.getCurrentRequest()); } /** * Test that if we try and execute a command with a cacheKey without initializing RequestVariable that it gives an error. */ @Test public void testCacheKeyExecutionRequiresRequestVariable() { try { /* force the RequestVariable to not be initialized */ HystrixRequestContext.setContextOnCurrentThread(null); TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SuccessfulCacheableCommand<String> command = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "one"); assertEquals("one", command.observe().toBlocking().single()); SuccessfulCacheableCommand<String> command2 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "two"); assertEquals("two", command2.observe().toBlocking().toFuture().get()); fail("We expect an exception because cacheKey requires RequestVariable."); // semaphore isolated assertFalse(command.isExecutedInThread()); assertNull(command.getExecutionException()); } catch (Exception e) { e.printStackTrace(); } } /** * Test that a BadRequestException can be synchronously thrown from a semaphore-isolated command and not count towards errors and bypasses fallback. */ @Test public void testSemaphoreIsolatedBadRequestSyncExceptionObserve() { testBadRequestExceptionObserve(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); } /** * Test that a BadRequestException can be asynchronously thrown from a semaphore-isolated command and not count towards errors and bypasses fallback. */ @Test public void testSemaphoreIsolatedBadRequestAsyncExceptionObserve() { testBadRequestExceptionObserve(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); } /** * Test that a BadRequestException can be synchronously thrown from a thread-isolated command and not count towards errors and bypasses fallback. */ @Test public void testThreadIsolatedBadRequestSyncExceptionObserve() { testBadRequestExceptionObserve(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); } /** * Test that a BadRequestException can be asynchronously thrown from a thread-isolated command and not count towards errors and bypasses fallback. */ @Test public void testThreadIsolatedBadRequestAsyncExceptionObserve() { testBadRequestExceptionObserve(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); } private void testBadRequestExceptionObserve(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); KnownHystrixBadRequestFailureTestCommand command1 = new KnownHystrixBadRequestFailureTestCommand(circuitBreaker, isolationStrategy, asyncException); try { command1.observe().toBlocking().single(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (HystrixBadRequestException e) { // success e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); } assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); assertSaneHystrixRequestLog(1); assertNotNull(command1.getExecutionException()); } /** * Test that synchronous BadRequestException behavior works the same on a cached response for a semaphore-isolated command. */ @Test public void testSyncBadRequestExceptionOnResponseFromCacheInSempahore() { testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); } /** * Test that asynchronous BadRequestException behavior works the same on a cached response for a semaphore-isolated command. */ @Test public void testAsyncBadRequestExceptionOnResponseFromCacheInSemaphore() { testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.SEMAPHORE, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); } /** * Test that synchronous BadRequestException behavior works the same on a cached response for a thread-isolated command. */ @Test public void testSyncBadRequestExceptionOnResponseFromCacheInThread() { testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.SYNC_EXCEPTION); } /** * Test that asynchronous BadRequestException behavior works the same on a cached response for a thread-isolated command. */ @Test public void testAsyncBadRequestExceptionOnResponseFromCacheInThread() { testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy.THREAD, KnownHystrixBadRequestFailureTestCommand.ASYNC_EXCEPTION); } private void testBadRequestExceptionOnResponseFromCache(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); KnownHystrixBadRequestFailureTestCommand command1 = new KnownHystrixBadRequestFailureTestCommand(circuitBreaker, isolationStrategy, asyncException); // execute once to cache the value try { command1.observe().toBlocking().single(); } catch (Throwable e) { // ignore } KnownHystrixBadRequestFailureTestCommand command2 = new KnownHystrixBadRequestFailureTestCommand(circuitBreaker, isolationStrategy, asyncException); try { command2.observe().toBlocking().toFuture().get(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (ExecutionException e) { e.printStackTrace(); if (e.getCause() instanceof HystrixBadRequestException) { // success } else { fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); } } catch (Exception e) { e.printStackTrace(); fail(); } assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST); assertCommandExecutionEvents(command2, HystrixEventType.BAD_REQUEST); assertSaneHystrixRequestLog(2); assertNotNull(command1.getExecutionException()); assertNotNull(command2.getExecutionException()); } /** * Test a checked Exception being thrown */ @Test public void testCheckedExceptionViaExecute() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); try { command.observe().toBlocking().single(); fail("we expect to receive a " + Exception.class.getSimpleName()); } catch (Exception e) { assertEquals("simulated checked exception message", e.getCause().getMessage()); } assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertNotNull(command.getExecutionException()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertSaneHystrixRequestLog(1); } /** * Test a java.lang.Error being thrown * * @throws InterruptedException */ @Test public void testCheckedExceptionViaObserve() throws InterruptedException { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); final AtomicReference<Throwable> t = new AtomicReference<Throwable>(); final CountDownLatch latch = new CountDownLatch(1); try { command.observe().subscribe(new Observer<Boolean>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { t.set(e); latch.countDown(); } @Override public void onNext(Boolean args) { } }); } catch (Exception e) { e.printStackTrace(); fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); } latch.await(1, TimeUnit.SECONDS); assertNotNull(t.get()); t.get().printStackTrace(); assertTrue(t.get() instanceof HystrixRuntimeException); assertEquals("simulated checked exception message", t.get().getCause().getMessage()); assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertNotNull(command.getExecutionException()); // semaphore isolated assertFalse(command.isExecutedInThread()); assertSaneHystrixRequestLog(1); } /** * Test a java.lang.Error being thrown * * @throws InterruptedException */ @Test public void testErrorThrownViaObserve() throws InterruptedException { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker, true); final AtomicReference<Throwable> t = new AtomicReference<Throwable>(); final CountDownLatch latch = new CountDownLatch(1); try { command.observe().subscribe(new Observer<Boolean>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { t.set(e); latch.countDown(); } @Override public void onNext(Boolean args) { } }); } catch (Exception e) { e.printStackTrace(); fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); } latch.await(1, TimeUnit.SECONDS); assertNotNull(t.get()); t.get().printStackTrace(); assertTrue(t.get() instanceof HystrixRuntimeException); // the actual error is an extra cause level deep because Hystrix needs to wrap Throwable/Error as it's public // methods only support Exception and it's not a strong enough reason to break backwards compatibility and jump to version 2.x assertEquals("simulated java.lang.Error message", t.get().getCause().getCause().getMessage()); assertEquals("simulated java.lang.Error message", command.getFailedExecutionException().getCause().getMessage()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isFailedExecution()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertNotNull(command.getExecutionException()); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertFalse(command.isExecutedInThread()); assertSaneHystrixRequestLog(1); } @Test public void testInterruptObserveOnTimeout() throws InterruptedException { // given InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); // when cmd.observe().subscribe(); // then Thread.sleep(500); assertTrue(cmd.hasBeenInterrupted()); } @Test public void testInterruptToObservableOnTimeout() throws InterruptedException { // given InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true); // when cmd.toObservable().subscribe(); // then Thread.sleep(500); assertTrue(cmd.hasBeenInterrupted()); } @Override protected void assertHooksOnSuccess(Func0<TestHystrixObservableCommand<Integer>> ctor, Action1<TestHystrixObservableCommand<Integer>> assertion) { assertBlockingObserve(ctor.call(), assertion, true); assertNonBlockingObserve(ctor.call(), assertion, true); } @Override protected void assertHooksOnFailure(Func0<TestHystrixObservableCommand<Integer>> ctor, Action1<TestHystrixObservableCommand<Integer>> assertion) { assertBlockingObserve(ctor.call(), assertion, false); assertNonBlockingObserve(ctor.call(), assertion, false); } @Override void assertHooksOnFailure(Func0<TestHystrixObservableCommand<Integer>> ctor, Action1<TestHystrixObservableCommand<Integer>> assertion, boolean failFast) { } /** *********************** HystrixObservableCommand-specific THREAD-ISOLATED Execution Hook Tests ************************************** */ /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: EMITx4, SUCCESS */ @Test public void testExecutionHookThreadMultipleEmitsAndThenSuccess() { assertHooksOnSuccess( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(4, 0, 1)); assertTrue(hook.executionEventsMatch(4, 0, 1)); assertTrue(hook.fallbackEventsMatch(0, 0, 0)); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionSuccess - onThreadComplete - onSuccess - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: EMITx4, FAILURE, FALLBACK_EMITx4, FALLBACK_SUCCESS */ @Test public void testExecutionHookThreadMultipleEmitsThenErrorThenMultipleFallbackEmitsAndThenFallbackSuccess() { assertHooksOnSuccess( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.MULTIPLE_EMITS_THEN_SUCCESS); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(8, 0, 1)); assertTrue(hook.executionEventsMatch(4, 1, 0)); assertTrue(hook.fallbackEventsMatch(4, 0, 1)); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - " + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + "onExecutionEmit - !onRunSuccess - !onComplete - onEmit - " + "onExecutionError - !onRunError - onThreadComplete - onFallbackStart - " + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + "onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - " + "onFallbackSuccess - onSuccess - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: asynchronous HystrixBadRequestException */ @Test public void testExecutionHookThreadAsyncBadRequestException() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_BAD_REQUEST); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(0, 0, 0)); assertEquals(HystrixBadRequestException.class, hook.getCommandException().getClass()); assertEquals(HystrixBadRequestException.class, hook.getExecutionException().getClass()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: async HystrixRuntimeException * Fallback: UnsupportedOperationException */ @Test public void testExecutionHookThreadAsyncExceptionNoFallback() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(0, 0, 0)); assertEquals(RuntimeException.class, hook.getCommandException().getClass()); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertNull(hook.getFallbackException()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: async HystrixRuntimeException * Fallback: SUCCESS */ @Test public void testExecutionHookThreadAsyncExceptionSuccessfulFallback() { assertHooksOnSuccess( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(1, 0, 1)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(1, 0, 1)); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: sync HystrixRuntimeException * Fallback: async HystrixRuntimeException */ @Test public void testExecutionHookThreadSyncExceptionAsyncUnsuccessfulFallback() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(0, 1, 0)); assertEquals(RuntimeException.class, hook.getCommandException().getClass()); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: async HystrixRuntimeException * Fallback: sync HystrixRuntimeException */ @Test public void testExecutionHookThreadAsyncExceptionSyncUnsuccessfulFallback() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.FAILURE); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(0, 1, 0)); assertEquals(RuntimeException.class, hook.getCommandException().getClass()); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : NO * Thread/semaphore: THREAD * Thread Pool fullInteger : NO * Thread Pool Queue fullInteger: NO * Timeout: NO * Execution Result: async HystrixRuntimeException * Fallback: async HystrixRuntimeException */ @Test public void testExecutionHookThreadAsyncExceptionAsyncUnsuccessfulFallback() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(0, 1, 0)); assertEquals(RuntimeException.class, hook.getCommandException().getClass()); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onThreadComplete - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** * Short-circuitInteger : YES * Thread/semaphore: THREAD * Fallback: async HystrixRuntimeException */ @Test public void testExecutionHookThreadShortCircuitAsyncUnsuccessfulFallback() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 0, 0)); assertTrue(hook.fallbackEventsMatch(0, 1, 0)); assertEquals(RuntimeException.class, hook.getCommandException().getClass()); assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** *********************** END HystrixObservableCommand-specific THREAD-ISOLATED Execution Hook Tests ************************************** */ /** ********************* HystrixObservableCommand-specific SEMAPHORE-ISOLATED Execution Hook Tests *********************************** */ /** * Short-circuitInteger : NO * Thread/semaphore: SEMAPHORE * Semaphore Permit reachedInteger : NO * Execution Result: HystrixRuntimeException * Fallback: asynchronous HystrixRuntimeException */ @Test public void testExecutionHookSemaphoreExceptionUnsuccessfulAsynchronousFallback() { assertHooksOnFailure( new Func0<TestHystrixObservableCommand<Integer>>() { @Override public TestHystrixObservableCommand<Integer> call() { return getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE); } }, new Action1<TestHystrixObservableCommand<Integer>>() { @Override public void call(TestHystrixObservableCommand<Integer> command) { TestableExecutionHook hook = command.getBuilder().executionHook; assertTrue(hook.commandEmissionsMatch(0, 1, 0)); assertTrue(hook.executionEventsMatch(0, 1, 0)); assertTrue(hook.fallbackEventsMatch(0, 1, 0)); assertEquals(RuntimeException.class, hook.getCommandException().getClass()); assertEquals(RuntimeException.class, hook.getExecutionException().getClass()); assertEquals(RuntimeException.class, hook.getFallbackException().getClass()); assertEquals("onStart - !onRunStart - onExecutionStart - onExecutionError - !onRunError - onFallbackStart - onFallbackError - onError - ", command.getBuilder().executionHook.executionSequence.toString()); } }); } /** ********************* END HystrixObservableCommand-specific SEMAPHORE-ISOLATED Execution Hook Tests *********************************** */ /** * Test a command execution that fails but has a fallback. */ @Test public void testExecutionFailureWithFallbackImplementedButDisabled() { TestHystrixObservableCommand<Boolean> commandEnabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), true, true); try { assertEquals(false, commandEnabled.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); } TestHystrixObservableCommand<Boolean> commandDisabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), false, true); try { assertEquals(false, commandDisabled.observe().toBlocking().single()); fail("expect exception thrown"); } catch (Exception e) { // expected } assertEquals("we failed with a simulated issue", commandDisabled.getFailedExecutionException().getMessage()); assertTrue(commandDisabled.isFailedExecution()); assertNotNull(commandDisabled.getExecutionException()); assertCommandExecutionEvents(commandEnabled, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertCommandExecutionEvents(commandDisabled, HystrixEventType.FAILURE); assertEquals(0, commandDisabled.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(2); } /** * Test that we can still use thread isolation if desired. */ @Test public void testSynchronousExecutionTimeoutValueViaExecute() { HystrixObservableCommand.Setter properties = HystrixObservableCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) .withExecutionTimeoutInMilliseconds(50)); System.out.println(">>>>> Begin: " + System.currentTimeMillis()); HystrixObservableCommand<String> command = new HystrixObservableCommand<String>(properties) { @Override protected Observable<String> construct() { return Observable.create(new OnSubscribe<String>() { @Override public void call(Subscriber<? super String> t1) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } t1.onNext("hello"); t1.onCompleted(); } }); } @Override protected Observable<String> resumeWithFallback() { if (isResponseTimedOut()) { return Observable.just("timed-out"); } else { return Observable.just("abc"); } } }; System.out.println(">>>>> Start: " + System.currentTimeMillis()); String value = command.observe().toBlocking().single(); System.out.println(">>>>> End: " + System.currentTimeMillis()); assertTrue(command.isResponseTimedOut()); assertEquals("expected fallback value", "timed-out", value); // Thread isolated assertTrue(command.isExecutedInThread()); assertNotNull(command.getExecutionException()); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); } @Test public void testSynchronousExecutionUsingThreadIsolationTimeoutValueViaObserve() { HystrixObservableCommand.Setter properties = HystrixObservableCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) .withExecutionTimeoutInMilliseconds(50)); HystrixObservableCommand<String> command = new HystrixObservableCommand<String>(properties) { @Override protected Observable<String> construct() { return Observable.create(new OnSubscribe<String>() { @Override public void call(Subscriber<? super String> t1) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } t1.onNext("hello"); t1.onCompleted(); } }); } @Override protected Observable<String> resumeWithFallback() { if (isResponseTimedOut()) { return Observable.just("timed-out"); } else { return Observable.just("abc"); } } }; String value = command.observe().toBlocking().last(); assertTrue(command.isResponseTimedOut()); assertEquals("expected fallback value", "timed-out", value); // Thread isolated assertTrue(command.isExecutedInThread()); assertNotNull(command.getExecutionException()); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); } @Test public void testAsyncExecutionTimeoutValueViaObserve() { HystrixObservableCommand.Setter properties = HystrixObservableCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionTimeoutInMilliseconds(50)); HystrixObservableCommand<String> command = new HystrixObservableCommand<String>(properties) { @Override protected Observable<String> construct() { return Observable.create(new OnSubscribe<String>() { @Override public void call(Subscriber<? super String> t1) { try { Thread.sleep(2000); } catch (InterruptedException e) { System.out.println("********** interrupted on timeout"); e.printStackTrace(); } // should never reach here t1.onNext("hello"); t1.onCompleted(); } }).subscribeOn(Schedulers.newThread()); } @Override protected Observable<String> resumeWithFallback() { if (isResponseTimedOut()) { return Observable.just("timed-out"); } else { return Observable.just("abc"); } } }; String value = command.observe().toBlocking().last(); assertTrue(command.isResponseTimedOut()); assertEquals("expected fallback value", "timed-out", value); // semaphore isolated assertFalse(command.isExecutedInThread()); assertNotNull(command.getExecutionException()); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); } /** * See https://github.com/Netflix/Hystrix/issues/212 */ @Test public void testObservableTimeoutNoFallbackThreadContext() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final AtomicReference<Thread> onErrorThread = new AtomicReference<Thread>(); final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); TestHystrixObservableCommand<Integer> command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 100); command.toObservable().doOnError(new Action1<Throwable>() { @Override public void call(Throwable t1) { System.out.println("onError: " + t1); System.out.println("onError Thread: " + Thread.currentThread()); System.out.println("ThreadContext in onError: " + HystrixRequestContext.isCurrentThreadInitialized()); onErrorThread.set(Thread.currentThread()); isRequestContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); } }).subscribe(ts); ts.awaitTerminalEvent(); assertTrue(isRequestContextInitialized.get()); assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); List<Throwable> errors = ts.getOnErrorEvents(); assertEquals(1, errors.size()); Throwable e = errors.get(0); if (errors.get(0) instanceof HystrixRuntimeException) { HystrixRuntimeException de = (HystrixRuntimeException) e; assertNotNull(de.getFallbackException()); assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); assertNotNull(de.getImplementingClass()); assertNotNull(de.getCause()); assertTrue(de.getCause() instanceof TimeoutException); } else { fail("the exception should be ExecutionException with cause as HystrixRuntimeException"); } assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isResponseTimedOut()); assertNotNull(command.getExecutionException()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertFalse(command.isExecutedInThread()); } /** * See https://github.com/Netflix/Hystrix/issues/212 */ @Test public void testObservableTimeoutFallbackThreadContext() { TestSubscriber<Object> ts = new TestSubscriber<Object>(); final AtomicReference<Thread> onErrorThread = new AtomicReference<Thread>(); final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); TestHystrixObservableCommand<Integer> command = getCommand(ExecutionIsolationStrategy.SEMAPHORE, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 100); command.toObservable().doOnNext(new Action1<Object>() { @Override public void call(Object t1) { System.out.println("onNext: " + t1); System.out.println("onNext Thread: " + Thread.currentThread()); System.out.println("ThreadContext in onNext: " + HystrixRequestContext.isCurrentThreadInitialized()); onErrorThread.set(Thread.currentThread()); isRequestContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); } }).subscribe(ts); ts.awaitTerminalEvent(); System.out.println("events: " + ts.getOnNextEvents()); assertTrue(isRequestContextInitialized.get()); assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); List<Object> onNexts = ts.getOnNextEvents(); assertEquals(1, onNexts.size()); //assertFalse( onNexts.get(0)); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isResponseTimedOut()); assertNotNull(command.getExecutionException()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); assertFalse(command.isExecutedInThread()); } @Test public void testRejectedViaSemaphoreIsolation() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); final ArrayBlockingQueue<Boolean> results = new ArrayBlockingQueue<Boolean>(2); final TryableSemaphoreActual semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); //used to wait until all commands have started final CountDownLatch startLatch = new CountDownLatch(2); // used to signal that all command can finish final CountDownLatch sharedLatch = new CountDownLatch(1); final LatchedSemaphoreCommand command1 = new LatchedSemaphoreCommand(circuitBreaker, semaphore, startLatch, sharedLatch); final LatchedSemaphoreCommand command2 = new LatchedSemaphoreCommand(circuitBreaker, semaphore, startLatch, sharedLatch); Observable<Boolean> merged = Observable.merge(command1.toObservable(), command2.toObservable()) .subscribeOn(Schedulers.computation()); final CountDownLatch terminal = new CountDownLatch(1); merged.subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { System.out.println(Thread.currentThread().getName() + " OnCompleted"); terminal.countDown(); } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " OnError : " + e); terminal.countDown(); } @Override public void onNext(Boolean b) { System.out.println(Thread.currentThread().getName() + " OnNext : " + b); results.offer(b); } }); try { assertTrue(startLatch.await(1000, TimeUnit.MILLISECONDS)); sharedLatch.countDown(); assertTrue(terminal.await(1000, TimeUnit.MILLISECONDS)); } catch (Throwable ex) { ex.printStackTrace(); fail(ex.getMessage()); } // one thread should have returned values assertEquals(2, results.size()); //1 should have gotten the normal value, the other - the fallback assertTrue(results.contains(Boolean.TRUE)); assertTrue(results.contains(Boolean.FALSE)); System.out.println("REQ LOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); assertCommandExecutionEvents(command1, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(2); } @Test public void testRejectedViaThreadIsolation() throws InterruptedException { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); final ArrayBlockingQueue<Boolean> results = new ArrayBlockingQueue<Boolean>(10); final List<Thread> executionThreads = Collections.synchronizedList(new ArrayList<Thread>(20)); final List<Thread> responseThreads = Collections.synchronizedList(new ArrayList<Thread>(10)); final AtomicBoolean exceptionReceived = new AtomicBoolean(); final CountDownLatch scheduleLatch = new CountDownLatch(2); final CountDownLatch successLatch = new CountDownLatch(1); final AtomicInteger count = new AtomicInteger(); final AtomicReference<TestThreadIsolationWithSemaphoreSetSmallCommand> command1Ref = new AtomicReference<TestThreadIsolationWithSemaphoreSetSmallCommand>(); final AtomicReference<TestThreadIsolationWithSemaphoreSetSmallCommand> command2Ref = new AtomicReference<TestThreadIsolationWithSemaphoreSetSmallCommand>(); final AtomicReference<TestThreadIsolationWithSemaphoreSetSmallCommand> command3Ref = new AtomicReference<TestThreadIsolationWithSemaphoreSetSmallCommand>(); Runnable r1 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { final boolean shouldExecute = count.incrementAndGet() < 3; try { executionThreads.add(Thread.currentThread()); TestThreadIsolationWithSemaphoreSetSmallCommand command1 = new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { @Override public void call() { // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted if (shouldExecute) { try { scheduleLatch.countDown(); successLatch.await(); } catch (InterruptedException e) { } } } }); command1Ref.set(command1); results.add(command1.toObservable().map(new Func1<Boolean, Boolean>() { @Override public Boolean call(Boolean b) { responseThreads.add(Thread.currentThread()); return b; } }).doAfterTerminate(new Action0() { @Override public void call() { if (!shouldExecute) { // the final thread that shouldn't execute releases the latch once it has run // so it is deterministic that the other two fill the thread pool until this one rejects successLatch.countDown(); } } }).toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); Runnable r2 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { final boolean shouldExecute = count.incrementAndGet() < 3; try { executionThreads.add(Thread.currentThread()); TestThreadIsolationWithSemaphoreSetSmallCommand command2 = new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { @Override public void call() { // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted if (shouldExecute) { try { scheduleLatch.countDown(); successLatch.await(); } catch (InterruptedException e) { } } } }); command2Ref.set(command2); results.add(command2.toObservable().map(new Func1<Boolean, Boolean>() { @Override public Boolean call(Boolean b) { responseThreads.add(Thread.currentThread()); return b; } }).doAfterTerminate(new Action0() { @Override public void call() { if (!shouldExecute) { // the final thread that shouldn't execute releases the latch once it has run // so it is deterministic that the other two fill the thread pool until this one rejects successLatch.countDown(); } } }).toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); Runnable r3 = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { @Override public void run() { final boolean shouldExecute = count.incrementAndGet() < 3; try { executionThreads.add(Thread.currentThread()); TestThreadIsolationWithSemaphoreSetSmallCommand command3 = new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { @Override public void call() { // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted if (shouldExecute) { try { scheduleLatch.countDown(); successLatch.await(); } catch (InterruptedException e) { } } } }); command3Ref.set(command3); results.add(command3.toObservable().map(new Func1<Boolean, Boolean>() { @Override public Boolean call(Boolean b) { responseThreads.add(Thread.currentThread()); return b; } }).doAfterTerminate(new Action0() { @Override public void call() { if (!shouldExecute) { // the final thread that shouldn't execute releases the latch once it has run // so it is deterministic that the other two fill the thread pool until this one rejects successLatch.countDown(); } } }).toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); } } }); // 2 threads, the second should be rejected by the semaphore and return fallback Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); Thread t3 = new Thread(r3); t1.start(); t2.start(); // wait for the previous 2 thread to be running before starting otherwise it can race scheduleLatch.await(500, TimeUnit.MILLISECONDS); t3.start(); try { t1.join(); t2.join(); t3.join(); } catch (Exception e) { e.printStackTrace(); fail("failed waiting on threads"); } // we should have 2 of the 3 return results assertEquals(2, results.size()); // the other thread should have thrown an Exception assertTrue(exceptionReceived.get()); assertCommandExecutionEvents(command1Ref.get(), HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command2Ref.get(), HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertCommandExecutionEvents(command3Ref.get(), HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING); assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(3); } /* ******************************************************************************************************** */ /* *************************************** Request Context Testing Below ********************************** */ /* ******************************************************************************************************** */ private RequestContextTestResults testRequestContextOnSuccess(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder() .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); s.onNext(true); s.onCompleted(); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(1, results.ts.getOnNextEvents().size()); assertTrue(results.ts.getOnNextEvents().get(0)); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isSuccessfulExecution()); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnGracefulFailure(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(circuitBreaker) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); s.onError(new RuntimeException("graceful onError")); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(1, results.ts.getOnErrorEvents().size()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isFailedExecution()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnBadFailure(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); throw new RuntimeException("bad onError"); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(1, results.ts.getOnErrorEvents().size()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isFailedExecution()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { s.onError(new RuntimeException("onError")); } }).subscribeOn(userScheduler); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); s.onNext(false); s.onCompleted(); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(0, results.ts.getOnErrorEvents().size()); assertEquals(1, results.ts.getOnNextEvents().size()); assertEquals(false, results.ts.getOnNextEvents().get(0)); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isFailedExecution()); assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(isolation) .withExecutionIsolationSemaphoreMaxConcurrentRequests(0)) .setThreadPool(new HystrixThreadPool() { @Override public ThreadPoolExecutor getExecutor() { return null; } @Override public void markThreadExecution() { } @Override public void markThreadCompletion() { } @Override public void markThreadRejection() { } @Override public boolean isQueueSpaceAvailable() { // always return false so we reject everything return false; } @Override public Scheduler getScheduler() { return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); } @Override public Scheduler getScheduler(Func0<Boolean> shouldInterruptThread) { return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); } })) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { s.onError(new RuntimeException("onError")); } }).subscribeOn(userScheduler); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); s.onNext(false); s.onCompleted(); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(0, results.ts.getOnErrorEvents().size()); assertEquals(1, results.ts.getOnNextEvents().size()); assertEquals(false, results.ts.getOnNextEvents().get(0)); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isResponseRejected()); if (isolation == ExecutionIsolationStrategy.SEMAPHORE) { assertCommandExecutionEvents(command, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); } else { assertCommandExecutionEvents(command, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); } assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(isolation)) .setCircuitBreaker(new TestCircuitBreaker().setForceShortCircuit(true))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { s.onError(new RuntimeException("onError")); } }).subscribeOn(userScheduler); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); s.onNext(false); s.onCompleted(); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(0, results.ts.getOnErrorEvents().size()); assertEquals(1, results.ts.getOnNextEvents().size()); assertEquals(false, results.ts.getOnNextEvents().get(0)); assertTrue(command.getExecutionTimeInMilliseconds() == -1); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isResponseShortCircuited()); assertCommandExecutionEvents(command, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnTimeout(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionTimeoutInMilliseconds(50))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); try { Thread.sleep(500); } catch (InterruptedException e) { // ignore the interrupted exception } } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(1, results.ts.getOnErrorEvents().size()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isResponseTimedOut()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private RequestContextTestResults testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { final RequestContextTestResults results = new RequestContextTestResults(); TestHystrixObservableCommand<Boolean> command = new TestHystrixObservableCommand<Boolean>(TestHystrixObservableCommand.testPropsBuilder(new TestCircuitBreaker()) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionTimeoutInMilliseconds(50))) { @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { try { Thread.sleep(500); } catch (InterruptedException e) { // ignore the interrupted exception } } }).subscribeOn(userScheduler); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); results.originThread.set(Thread.currentThread()); s.onNext(false); s.onCompleted(); } }).subscribeOn(userScheduler); } }; results.command = command; command.toObservable().doOnEach(new Action1<Notification<? super Boolean>>() { @Override public void call(Notification<? super Boolean> n) { System.out.println("timeoutWithFallback notification: " + n + " " + Thread.currentThread()); results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); results.observeOnThread.set(Thread.currentThread()); } }).subscribe(results.ts); results.ts.awaitTerminalEvent(); System.out.println("Fallback => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); assertEquals(1, results.ts.getOnNextEvents().size()); assertEquals(false, results.ts.getOnNextEvents().get(0)); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isResponseTimedOut()); assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); return results; } private final class RequestContextTestResults { volatile TestHystrixObservableCommand<Boolean> command; final AtomicReference<Thread> originThread = new AtomicReference<Thread>(); final AtomicBoolean isContextInitialized = new AtomicBoolean(); TestSubscriber<Boolean> ts = new TestSubscriber<Boolean>(); final AtomicBoolean isContextInitializedObserveOn = new AtomicBoolean(); final AtomicReference<Thread> observeOnThread = new AtomicReference<Thread>(); } /* *************************************** testSuccessfulRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testSuccessfulRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testSuccessfulRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testSuccessfulRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] */ @Test public void testSuccessfulRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testSuccessfulRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testSuccessfulRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /* *************************************** testGracefulFailureRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testGracefulFailureRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testGracefulFailureRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testGracefulFailureRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] */ @Test public void testGracefulFailureRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testGracefulFailureRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testGracefulFailureRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /* *************************************** testBadFailureRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testBadFailureRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testBadFailureRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testBadFailureRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] */ @Test public void testBadFailureRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testBadFailureRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testBadFailureRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /* *************************************** testFailureWithFallbackRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] */ @Test public void testFailureWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testFailureWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testFailureWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated assertTrue(results.command.isExecutedInThread()); } /* *************************************** testRejectionWithFallbackRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] */ @Test public void testRejectionWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // fallback is performed by the calling thread assertTrue(results.isContextInitializedObserveOn.get()); System.out.println("results.observeOnThread.get(): " + results.observeOnThread.get() + " " + Thread.currentThread()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // rejected so we stay on calling thread // thread isolated, but rejected, so this is false assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testRejectionWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // thread isolated, but rejected, so this is false assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testRejectionWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler for getFallback // thread isolated, but rejected, so this is false assertFalse(results.command.isExecutedInThread()); } /* *************************************** testShortCircuitedWithFallbackRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] */ @Test public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // fallback is performed by the calling thread assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // rejected so we stay on calling thread // thread isolated ... but rejected so not executed in a thread assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler from getFallback // thread isolated ... but rejected so not executed in a thread assertFalse(results.command.isExecutedInThread()); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler from getFallback // thread isolated ... but rejected so not executed in a thread assertFalse(results.command.isExecutedInThread()); } /* *************************************** testTimeoutRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. */ @Test public void testTimeoutRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // semaphore isolated assertFalse(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // semaphore isolated assertFalse(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // semaphore isolated assertFalse(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /* *************************************** testTimeoutWithFallbackRequestContext *********************************** */ /** * Synchronous Observable and semaphore isolation. */ @Test public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread //(this use case is a little odd as it should generally not be the case that we are "timing out" a synchronous observable on semaphore isolation) assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread // semaphore isolated assertFalse(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // semaphore isolated assertFalse(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [HystrixTimer] */ @Test public void testTimeoutWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread for fallback assertTrue(results.isContextInitializedObserveOn.get()); assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // fallback uses the timeout thread // thread isolated assertTrue(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. * * NOTE: RequestContext will NOT exist on that thread. * * An async Observable running on its own thread will not have access to the request context unless the user manages the context. */ @Test public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // thread isolated assertTrue(results.command.isExecutedInThread()); HystrixCircuitBreaker.Factory.reset(); } /** * Async Observable and semaphore isolation WITH functioning RequestContext * * Use HystrixContextScheduler to make the user provided scheduler capture context. */ @Test public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); assertTrue(results.isContextInitialized.get()); // the user scheduler captures context assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler // thread isolated assertTrue(results.command.isExecutedInThread()); try { Thread.sleep(100); } catch (InterruptedException ex) { } //HystrixCircuitBreaker.Factory.reset(); } /** * Test support of multiple onNext events. */ @Test public void testExecutionSuccessWithMultipleEvents() { try { TestCommandWithMultipleValues command = new TestCommandWithMultipleValues(); assertEquals(Arrays.asList(true, false, true), command.observe().toList().toBlocking().single()); assertEquals(null, command.getFailedExecutionException()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertTrue(command.isSuccessfulExecution()); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); // semaphore isolated assertFalse(command.isExecutedInThread()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } } /** * Test behavior when some onNext are received and then a failure. */ @Test public void testExecutionPartialSuccess() { try { TestPartialSuccess command = new TestPartialSuccess(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); command.toObservable().subscribe(ts); ts.awaitTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3)); assertEquals(1, ts.getOnErrorEvents().size()); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isFailedExecution()); // we will have an exception assertNotNull(command.getFailedExecutionException()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING); assertSaneHystrixRequestLog(1); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); // semaphore isolated assertFalse(command.isExecutedInThread()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } } /** * Test behavior when some onNext are received and then a failure. */ @Test public void testExecutionPartialSuccessWithFallback() { try { TestPartialSuccessWithFallback command = new TestPartialSuccessWithFallback(); TestSubscriber<Boolean> ts = new TestSubscriber<Boolean>(); command.toObservable().subscribe(ts); ts.awaitTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(false, true, false, true, false, true, false)); ts.assertNoErrors(); assertFalse(command.isSuccessfulExecution()); assertTrue(command.isFailedExecution()); assertNotNull(command.getFailedExecutionException()); assertTrue(command.getExecutionTimeInMilliseconds() > -1); assertCommandExecutionEvents(command, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.EMIT, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_EMIT, HystrixEventType.FALLBACK_SUCCESS); assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount()); assertSaneHystrixRequestLog(1); // semaphore isolated assertFalse(command.isExecutedInThread()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); } } @Test public void testEarlyUnsubscribeDuringExecutionViaToObservable() { class AsyncCommand extends HystrixObservableCommand<Boolean> { public AsyncCommand() { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); } @Override protected Observable<Boolean> construct() { return Observable.defer(new Func0<Observable<Boolean>>() { @Override public Observable<Boolean> call() { try { Thread.sleep(100); return Observable.just(true); } catch (InterruptedException ex) { return Observable.error(ex); } } }).subscribeOn(Schedulers.io()); } } HystrixObservableCommand<Boolean> cmd = new AsyncCommand(); final CountDownLatch latch = new CountDownLatch(1); Observable<Boolean> o = cmd.toObservable(); Subscription s = o. doOnUnsubscribe(new Action0() { @Override public void call() { System.out.println("OnUnsubscribe"); latch.countDown(); } }). subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { System.out.println("OnCompleted"); latch.countDown(); } @Override public void onError(Throwable e) { System.out.println("OnError : " + e); } @Override public void onNext(Boolean b) { System.out.println("OnNext : " + b); } }); try { s.unsubscribe(); assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); assertFalse(cmd.isExecutionComplete()); assertFalse(cmd.isExecutedInThread()); System.out.println("EventCounts : " + cmd.getEventCounts()); System.out.println("Execution Time : " + cmd.getExecutionTimeInMilliseconds()); System.out.println("Is Successful : " + cmd.isSuccessfulExecution()); } catch (InterruptedException ex) { ex.printStackTrace(); } } @Test public void testEarlyUnsubscribeDuringExecutionViaObserve() { class AsyncCommand extends HystrixObservableCommand<Boolean> { public AsyncCommand() { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); } @Override protected Observable<Boolean> construct() { return Observable.defer(new Func0<Observable<Boolean>>() { @Override public Observable<Boolean> call() { try { Thread.sleep(100); return Observable.just(true); } catch (InterruptedException ex) { return Observable.error(ex); } } }).subscribeOn(Schedulers.io()); } } HystrixObservableCommand<Boolean> cmd = new AsyncCommand(); final CountDownLatch latch = new CountDownLatch(1); Observable<Boolean> o = cmd.observe(); Subscription s = o. doOnUnsubscribe(new Action0() { @Override public void call() { System.out.println("OnUnsubscribe"); latch.countDown(); } }). subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { System.out.println("OnCompleted"); latch.countDown(); } @Override public void onError(Throwable e) { System.out.println("OnError : " + e); } @Override public void onNext(Boolean b) { System.out.println("OnNext : " + b); } }); try { s.unsubscribe(); assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); assertFalse(cmd.isExecutionComplete()); assertFalse(cmd.isExecutedInThread()); System.out.println("EventCounts : " + cmd.getEventCounts()); System.out.println("Execution Time : " + cmd.getExecutionTimeInMilliseconds()); System.out.println("Is Successful : " + cmd.isSuccessfulExecution()); } catch (InterruptedException ex) { ex.printStackTrace(); } } @Test public void testEarlyUnsubscribeDuringFallback() { class AsyncCommand extends HystrixObservableCommand<Boolean> { public AsyncCommand() { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC"))); } @Override protected Observable<Boolean> construct() { return Observable.error(new RuntimeException("construct failure")); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.defer(new Func0<Observable<Boolean>>() { @Override public Observable<Boolean> call() { try { Thread.sleep(100); return Observable.just(false); } catch (InterruptedException ex) { return Observable.error(ex); } } }).subscribeOn(Schedulers.io()); } } HystrixObservableCommand<Boolean> cmd = new AsyncCommand(); final CountDownLatch latch = new CountDownLatch(1); Observable<Boolean> o = cmd.toObservable(); Subscription s = o. doOnUnsubscribe(new Action0() { @Override public void call() { System.out.println("OnUnsubscribe"); latch.countDown(); } }). subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { System.out.println("OnCompleted"); latch.countDown(); } @Override public void onError(Throwable e) { System.out.println("OnError : " + e); } @Override public void onNext(Boolean b) { System.out.println("OnNext : " + b); } }); try { Thread.sleep(10); //give fallback a chance to fire s.unsubscribe(); assertTrue(latch.await(200, TimeUnit.MILLISECONDS)); assertEquals("Number of execution semaphores in use", 0, cmd.getExecutionSemaphore().getNumberOfPermitsUsed()); assertEquals("Number of fallback semaphores in use", 0, cmd.getFallbackSemaphore().getNumberOfPermitsUsed()); assertFalse(cmd.isExecutionComplete()); assertFalse(cmd.isExecutedInThread()); } catch (InterruptedException ex) { ex.printStackTrace(); } } /* ******************************************************************************** */ /* ******************************************************************************** */ /* private HystrixCommand class implementations for unit testing */ /* ******************************************************************************** */ /* ******************************************************************************** */ static AtomicInteger uniqueNameCounter = new AtomicInteger(0); @Override TestHystrixObservableCommand<Integer> getCommand(ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("FlexibleObservable-" + uniqueNameCounter.getAndIncrement()); return FlexibleTestHystrixObservableCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); } @Override TestHystrixObservableCommand<Integer> getCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { return FlexibleTestHystrixObservableCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); } private static class FlexibleTestHystrixObservableCommand { public static Integer EXECUTE_VALUE = 1; public static Integer FALLBACK_VALUE = 11; public static AbstractFlexibleTestHystrixObservableCommand from(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, AbstractTestHystrixCommand.FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, AbstractTestHystrixCommand.CacheEnabled cacheEnabled, Object value, AbstractCommand.TryableSemaphore executionSemaphore, AbstractCommand.TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { if (fallbackResult.equals(AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED)) { return new FlexibleTestHystrixObservableCommandNoFallback(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); } else { return new FlexibleTestHystrixObservableCommandWithFallback(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); } } } private static class AbstractFlexibleTestHystrixObservableCommand extends TestHystrixObservableCommand<Integer> { private final AbstractTestHystrixCommand.ExecutionResult executionResult; private final int executionLatency; private final CacheEnabled cacheEnabled; private final Object value; public AbstractFlexibleTestHystrixObservableCommand(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { super(testPropsBuilder(circuitBreaker) .setCommandKey(commandKey) .setCircuitBreaker(circuitBreaker) .setMetrics(circuitBreaker.metrics) .setThreadPool(threadPool) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(isolationStrategy) .withExecutionTimeoutInMilliseconds(timeout) .withCircuitBreakerEnabled(!circuitBreakerDisabled)) .setExecutionSemaphore(executionSemaphore) .setFallbackSemaphore(fallbackSemaphore)); this.executionResult = executionResult; this.executionLatency = executionLatency; this.cacheEnabled = cacheEnabled; this.value = value; } @Override protected Observable<Integer> construct() { if (executionResult == AbstractTestHystrixCommand.ExecutionResult.FAILURE) { addLatency(executionLatency); throw new RuntimeException("Execution Sync Failure for TestHystrixObservableCommand"); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE) { addLatency(executionLatency); throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixObservableCommand.class, "Execution Hystrix Failure for TestHystrixObservableCommand", new RuntimeException("Execution Failure for TestHystrixObservableCommand"), new RuntimeException("Fallback Failure for TestHystrixObservableCommand")); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE) { addLatency(executionLatency); throw new NotWrappedByHystrixTestRuntimeException(); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.RECOVERABLE_ERROR) { addLatency(executionLatency); throw new java.lang.Error("Execution Sync Error for TestHystrixObservableCommand"); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.UNRECOVERABLE_ERROR) { addLatency(executionLatency); throw new OutOfMemoryError("Execution Sync OOME for TestHystrixObservableCommand"); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST) { addLatency(executionLatency); throw new HystrixBadRequestException("Execution Bad Request Exception for TestHystrixObservableCommand"); } return Observable.create(new OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " construct() method has been subscribed to"); addLatency(executionLatency); if (executionResult == AbstractTestHystrixCommand.ExecutionResult.SUCCESS) { subscriber.onNext(1); subscriber.onCompleted(); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_SUCCESS) { subscriber.onNext(2); subscriber.onNext(3); subscriber.onNext(4); subscriber.onNext(5); subscriber.onCompleted(); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.MULTIPLE_EMITS_THEN_FAILURE) { subscriber.onNext(6); subscriber.onNext(7); subscriber.onNext(8); subscriber.onNext(9); subscriber.onError(new RuntimeException("Execution Async Failure For TestHystrixObservableCommand after 4 emits")); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_FAILURE) { subscriber.onError(new RuntimeException("Execution Async Failure for TestHystrixObservableCommand after 0 emits")); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_HYSTRIX_FAILURE) { subscriber.onError(new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixObservableCommand.class, "Execution Hystrix Failure for TestHystrixObservableCommand", new RuntimeException("Execution Failure for TestHystrixObservableCommand"), new RuntimeException("Fallback Failure for TestHystrixObservableCommand"))); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_RECOVERABLE_ERROR) { subscriber.onError(new java.lang.Error("Execution Async Error for TestHystrixObservableCommand")); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_UNRECOVERABLE_ERROR) { subscriber.onError(new OutOfMemoryError("Execution Async OOME for TestHystrixObservableCommand")); } else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.ASYNC_BAD_REQUEST) { subscriber.onError(new HystrixBadRequestException("Execution Async Bad Request Exception for TestHystrixObservableCommand")); } else { subscriber.onError(new RuntimeException("You passed in a executionResult enum that can't be represented in HystrixObservableCommand: " + executionResult)); } } }); } @Override public String getCacheKey() { if (cacheEnabled == CacheEnabled.YES) return value.toString(); else return null; } protected void addLatency(int latency) { if (latency > 0) { try { System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " About to sleep for : " + latency); Thread.sleep(latency); System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Woke up from sleep!"); } catch (InterruptedException e) { e.printStackTrace(); // ignore and sleep some more to simulate a dependency that doesn't obey interrupts try { Thread.sleep(latency); } catch (Exception e2) { // ignore } System.out.println("after interruption with extra sleep"); } } } } private static class FlexibleTestHystrixObservableCommandWithFallback extends AbstractFlexibleTestHystrixObservableCommand { private final AbstractTestHystrixCommand.FallbackResult fallbackResult; private final int fallbackLatency; public FlexibleTestHystrixObservableCommandWithFallback(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, FallbackResult fallbackResult, int fallbackLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { super(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); this.fallbackResult = fallbackResult; this.fallbackLatency = fallbackLatency; } @Override protected Observable<Integer> resumeWithFallback() { if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.FAILURE) { addLatency(fallbackLatency); throw new RuntimeException("Fallback Sync Failure for TestHystrixCommand"); } else if (fallbackResult == FallbackResult.UNIMPLEMENTED) { addLatency(fallbackLatency); return super.resumeWithFallback(); } return Observable.create(new OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { addLatency(fallbackLatency); if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.SUCCESS) { subscriber.onNext(11); subscriber.onCompleted(); } else if (fallbackResult == FallbackResult.MULTIPLE_EMITS_THEN_SUCCESS) { subscriber.onNext(12); subscriber.onNext(13); subscriber.onNext(14); subscriber.onNext(15); subscriber.onCompleted(); } else if (fallbackResult == FallbackResult.MULTIPLE_EMITS_THEN_FAILURE) { subscriber.onNext(16); subscriber.onNext(17); subscriber.onNext(18); subscriber.onNext(19); subscriber.onError(new RuntimeException("Fallback Async Failure For TestHystrixObservableCommand after 4 emits")); } else if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.ASYNC_FAILURE) { subscriber.onError(new RuntimeException("Fallback Async Failure for TestHystrixCommand after 0 fallback emits")); } else { subscriber.onError(new RuntimeException("You passed in a fallbackResult enum that can't be represented in HystrixObservableCommand: " + fallbackResult)); } } }); } } private static class FlexibleTestHystrixObservableCommandNoFallback extends AbstractFlexibleTestHystrixObservableCommand { public FlexibleTestHystrixObservableCommandNoFallback(HystrixCommandKey commandKey, ExecutionIsolationStrategy isolationStrategy, AbstractTestHystrixCommand.ExecutionResult executionResult, int executionLatency, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int timeout, CacheEnabled cacheEnabled, Object value, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) { super(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled); } } /** * Successful execution - no fallback implementation. */ private static class SuccessfulTestCommand extends TestHystrixObservableCommand<Boolean> { public SuccessfulTestCommand(ExecutionIsolationStrategy isolationStrategy) { this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)); } public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) { super(testPropsBuilder().setCommandPropertiesDefaults(properties)); } @Override protected Observable<Boolean> construct() { return Observable.just(true).subscribeOn(Schedulers.computation()); } } /** * Successful execution - no fallback implementation. */ private static class TestCommandWithMultipleValues extends TestHystrixObservableCommand<Boolean> { public TestCommandWithMultipleValues() { this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)); } public TestCommandWithMultipleValues(HystrixCommandProperties.Setter properties) { super(testPropsBuilder().setCommandPropertiesDefaults(properties)); } @Override protected Observable<Boolean> construct() { return Observable.just(true, false, true).subscribeOn(Schedulers.computation()); } } private static class TestPartialSuccess extends TestHystrixObservableCommand<Integer> { TestPartialSuccess() { super(TestHystrixObservableCommand.testPropsBuilder()); } @Override protected Observable<Integer> construct() { return Observable.just(1, 2, 3) .concatWith(Observable.<Integer> error(new RuntimeException("forced error"))) .subscribeOn(Schedulers.computation()); } } private static class TestPartialSuccessWithFallback extends TestHystrixObservableCommand<Boolean> { TestPartialSuccessWithFallback() { super(TestHystrixObservableCommand.testPropsBuilder()); } public TestPartialSuccessWithFallback(ExecutionIsolationStrategy isolationStrategy) { this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)); } public TestPartialSuccessWithFallback(HystrixCommandProperties.Setter properties) { super(testPropsBuilder().setCommandPropertiesDefaults(properties)); } @Override protected Observable<Boolean> construct() { return Observable.just(false, true, false) .concatWith(Observable.<Boolean>error(new RuntimeException("forced error"))) .subscribeOn(Schedulers.computation()); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.just(true, false, true, false); } } /** * Successful execution - no fallback implementation. */ private static class DynamicOwnerTestCommand extends TestHystrixObservableCommand<Boolean> { public DynamicOwnerTestCommand(HystrixCommandGroupKey owner) { super(testPropsBuilder().setOwner(owner)); } @Override protected Observable<Boolean> construct() { System.out.println("successfully executed"); return Observable.just(true).subscribeOn(Schedulers.computation()); } } /** * Successful execution - no fallback implementation. */ private static class DynamicOwnerAndKeyTestCommand extends TestHystrixObservableCommand<Boolean> { public DynamicOwnerAndKeyTestCommand(HystrixCommandGroupKey owner, HystrixCommandKey key) { super(testPropsBuilder().setOwner(owner).setCommandKey(key).setCircuitBreaker(null).setMetrics(null)); // we specifically are NOT passing in a circuit breaker here so we test that it creates a new one correctly based on the dynamic key } @Override protected Observable<Boolean> construct() { System.out.println("successfully executed"); return Observable.just(true).subscribeOn(Schedulers.computation()); } } /** * Failed execution with unknown exception (not HystrixException) - no fallback implementation. */ private static class UnknownFailureTestCommandWithoutFallback extends TestHystrixObservableCommand<Boolean> { private final boolean asyncException; private UnknownFailureTestCommandWithoutFallback(ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { super(testPropsBuilder(isolationStrategy, new TestCircuitBreaker())); this.asyncException = asyncException; } @Override protected Observable<Boolean> construct() { System.out.println("*** simulated failed execution ***"); RuntimeException ex = new RuntimeException("we failed with an unknown issue"); if (asyncException) { return Observable.error(ex); } else { throw ex; } } } /** * Failed execution with known exception (HystrixException) - no fallback implementation. */ private static class KnownFailureTestCommandWithoutFallback extends TestHystrixObservableCommand<Boolean> { final boolean asyncException; private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); this.asyncException = asyncException; } @Override protected Observable<Boolean> construct() { System.out.println("*** simulated failed execution ***"); RuntimeException ex = new RuntimeException("we failed with a simulated issue"); if (asyncException) { return Observable.error(ex); } else { throw ex; } } } /** * Failed execution - fallback implementation successfully returns value. */ private static class KnownFailureTestCommandWithFallback extends TestHystrixObservableCommand<Boolean> { private final boolean asyncException; public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); this.asyncException = asyncException; } public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, boolean fallbackEnabled, boolean asyncException) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackEnabled(fallbackEnabled).withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); this.asyncException = asyncException; } @Override protected Observable<Boolean> construct() { System.out.println("*** simulated failed execution ***"); RuntimeException ex = new RuntimeException("we failed with a simulated issue"); if (asyncException) { return Observable.error(ex); } else { throw ex; } } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.just(false).subscribeOn(Schedulers.computation()); } } /** * Failed execution with {@link HystrixBadRequestException} */ private static class KnownHystrixBadRequestFailureTestCommand extends TestHystrixObservableCommand<Boolean> { public final static boolean ASYNC_EXCEPTION = true; public final static boolean SYNC_EXCEPTION = false; private final boolean asyncException; public KnownHystrixBadRequestFailureTestCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncException) { super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); this.asyncException = asyncException; } @Override protected Observable<Boolean> construct() { System.out.println("*** simulated failed with HystrixBadRequestException ***"); RuntimeException ex = new HystrixBadRequestException("we failed with a simulated issue"); if (asyncException) { return Observable.error(ex); } else { throw ex; } } } /** * Failed execution - fallback implementation throws exception. */ private static class KnownFailureTestCommandWithFallbackFailure extends TestHystrixObservableCommand<Boolean> { private final boolean asyncConstructException; private final boolean asyncFallbackException; private KnownFailureTestCommandWithFallbackFailure(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationStrategy, boolean asyncConstructException, boolean asyncFallbackException) { super(testPropsBuilder(isolationStrategy, circuitBreaker).setMetrics(circuitBreaker.metrics)); this.asyncConstructException = asyncConstructException; this.asyncFallbackException = asyncFallbackException; } @Override protected Observable<Boolean> construct() { RuntimeException ex = new RuntimeException("we failed with a simulated issue"); System.out.println("*** simulated failed execution ***"); if (asyncConstructException) { return Observable.error(ex); } else { throw ex; } } @Override protected Observable<Boolean> resumeWithFallback() { RuntimeException ex = new RuntimeException("failed while getting fallback"); if (asyncFallbackException) { return Observable.error(ex); } else { throw ex; } } } /** * A Command implementation that supports caching. */ private static class SuccessfulCacheableCommand<T> extends TestHystrixObservableCommand<T> { private final boolean cacheEnabled; private volatile boolean executed = false; private final T value; public SuccessfulCacheableCommand(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, T value) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD))); this.value = value; this.cacheEnabled = cacheEnabled; } @Override protected Observable<T> construct() { executed = true; System.out.println("successfully executed"); return Observable.just(value).subscribeOn(Schedulers.computation()); } public boolean isCommandRunningInThread() { return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); } @Override public String getCacheKey() { if (cacheEnabled) return value.toString(); else return null; } } /** * A Command implementation that supports caching. */ private static class SuccessfulCacheableCommandViaSemaphore extends TestHystrixObservableCommand<String> { private final boolean cacheEnabled; private volatile boolean executed = false; private final String value; public SuccessfulCacheableCommandViaSemaphore(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, String value) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); this.value = value; this.cacheEnabled = cacheEnabled; } @Override protected Observable<String> construct() { executed = true; System.out.println("successfully executed"); return Observable.just(value).subscribeOn(Schedulers.computation()); } public boolean isCommandRunningInThread() { return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); } @Override public String getCacheKey() { if (cacheEnabled) return value; else return null; } } /** * A Command implementation that supports caching and execution takes a while. * <p> * Used to test scenario where Futures are returned with a backing call still executing. */ private static class SlowCacheableCommand extends TestHystrixObservableCommand<String> { private final String value; private final int duration; private volatile boolean executed = false; public SlowCacheableCommand(TestCircuitBreaker circuitBreaker, String value, int duration) { super(testPropsBuilder() .setCommandKey(HystrixCommandKey.Factory.asKey("ObservableSlowCacheable")) .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); this.value = value; this.duration = duration; } @Override protected Observable<String> construct() { executed = true; return Observable.just(value).delay(duration, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()) .doOnNext(new Action1<String>() { @Override public void call(String t1) { System.out.println("successfully executed"); } }); } @Override public String getCacheKey() { return value; } } /** * Successful execution - no fallback implementation, circuit-breaker disabled. */ private static class TestCommandWithoutCircuitBreaker extends TestHystrixObservableCommand<Boolean> { private TestCommandWithoutCircuitBreaker() { super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withCircuitBreakerEnabled(false))); } @Override protected Observable<Boolean> construct() { System.out.println("successfully executed"); return Observable.just(true).subscribeOn(Schedulers.computation()); } } private static class NoRequestCacheTimeoutWithoutFallback extends TestHystrixObservableCommand<Boolean> { public NoRequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { super(testPropsBuilder(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionTimeoutInMilliseconds(200).withCircuitBreakerEnabled(false))); // we want it to timeout } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); // e.printStackTrace(); } s.onNext(true); s.onCompleted(); } }).subscribeOn(Schedulers.computation()); } @Override public String getCacheKey() { return null; } } /** * The run() will take time. Configurable fallback implementation. */ private static class TestSemaphoreCommand extends TestHystrixObservableCommand<Boolean> { private final 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 final static boolean FALLBACK_FAILURE_SYNC = false; private final static boolean FALLBACK_FAILURE_ASYNC = true; private final boolean asyncFallbackException; 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; this.asyncFallbackException = FALLBACK_FAILURE_ASYNC; } 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; this.asyncFallbackException = FALLBACK_FAILURE_ASYNC; } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> subscriber) { try { Thread.sleep(executionSleep); } catch (InterruptedException e) { e.printStackTrace(); } 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")); } } }); } @Override protected Observable<Boolean> resumeWithFallback() { if (fallbackBehavior == FALLBACK_SUCCESS) { return Observable.just(false); } else if (fallbackBehavior == FALLBACK_FAILURE) { RuntimeException ex = new RuntimeException("fallback failure"); if (asyncFallbackException) { return Observable.error(ex); } else { throw ex; } } else { //FALLBACK_NOT_IMPLEMENTED return super.resumeWithFallback(); } } } /** * The construct() will take time once subscribed to. No fallback implementation. * * Used for making sure Thread and Semaphore isolation are separated from each other. */ private static class TestThreadIsolationWithSemaphoreSetSmallCommand extends TestHystrixObservableCommand<Boolean> { private final Action0 action; private TestThreadIsolationWithSemaphoreSetSmallCommand(TestCircuitBreaker circuitBreaker, int poolSize, Action0 action) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(TestThreadIsolationWithSemaphoreSetSmallCommand.class.getSimpleName())) .setThreadPoolPropertiesDefaults(HystrixThreadPoolPropertiesTest.getUnitTestPropertiesBuilder() .withCoreSize(poolSize).withMaximumSize(poolSize).withMaxQueueSize(0)) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) .withExecutionIsolationSemaphoreMaxConcurrentRequests(1))); this.action = action; } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { action.call(); s.onNext(true); s.onCompleted(); } }); } } /** * Semaphore based command that allows caller to use latches to know when it has started and signal when it * would like the command to finish */ private static class LatchedSemaphoreCommand extends TestHystrixObservableCommand<Boolean> { private final CountDownLatch startLatch, waitLatch; /** * * @param circuitBreaker circuit breaker (passed in so it may be shared) * @param semaphore semaphore (passed in so it may be shared) * @param startLatch * this command calls {@link CountDownLatch#countDown()} immediately upon running * @param waitLatch * this command calls {@link CountDownLatch#await()} once it starts * to run. The caller can use the latch to signal the command to finish */ private LatchedSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphoreActual semaphore, CountDownLatch startLatch, CountDownLatch waitLatch) { this("Latched", circuitBreaker, semaphore, startLatch, waitLatch); } private LatchedSemaphoreCommand(String commandName, TestCircuitBreaker circuitBreaker, TryableSemaphoreActual semaphore, CountDownLatch startLatch, CountDownLatch waitLatch) { super(testPropsBuilder() .setCommandKey(HystrixCommandKey.Factory.asKey(commandName)) .setCircuitBreaker(circuitBreaker) .setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) .withCircuitBreakerEnabled(false)) .setExecutionSemaphore(semaphore)); this.startLatch = startLatch; this.waitLatch = waitLatch; } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { //signals caller that run has started startLatch.countDown(); try { // waits for caller to countDown latch waitLatch.await(); s.onNext(true); s.onCompleted(); } catch (InterruptedException e) { e.printStackTrace(); s.onNext(false); s.onCompleted(); } } }).subscribeOn(Schedulers.computation()); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.defer(new Func0<Observable<Boolean>>() { @Override public Observable<Boolean> call() { startLatch.countDown(); return Observable.just(false); } }); } } /** * The construct() will take time once subscribed to. Contains fallback. */ private static class TestSemaphoreCommandWithFallback extends TestHystrixObservableCommand<Boolean> { private final long executionSleep; private final Observable<Boolean> fallback; private TestSemaphoreCommandWithFallback(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, Boolean fallback) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); this.executionSleep = executionSleep; this.fallback = Observable.just(fallback); } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { try { Thread.sleep(executionSleep); } catch (InterruptedException e) { e.printStackTrace(); } s.onNext(true); s.onCompleted(); } }).subscribeOn(Schedulers.io()); } @Override protected Observable<Boolean> resumeWithFallback() { return fallback; } } private static class InterruptibleCommand extends TestHystrixObservableCommand<Boolean> { public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt) { super(testPropsBuilder() .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() .withExecutionIsolationThreadInterruptOnTimeout(shouldInterrupt) .withExecutionTimeoutInMilliseconds(100))); } private volatile boolean hasBeenInterrupted; public boolean hasBeenInterrupted() { return hasBeenInterrupted; } @Override protected Observable<Boolean> construct() { return Observable.defer(new Func0<Observable<Boolean>>() { @Override public Observable<Boolean> call() { try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupted!"); e.printStackTrace(); hasBeenInterrupted = true; } return Observable.just(hasBeenInterrupted); } }).subscribeOn(Schedulers.io()); } } private static class RequestCacheNullPointerExceptionCase extends TestHystrixObservableCommand<Boolean> { public RequestCacheNullPointerExceptionCase(TestCircuitBreaker circuitBreaker) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionTimeoutInMilliseconds(200))); // we want it to timeout } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } s.onNext(true); s.onCompleted(); } }).subscribeOn(Schedulers.computation()); } @Override protected Observable<Boolean> resumeWithFallback() { return Observable.just(false).subscribeOn(Schedulers.computation()); } @Override public String getCacheKey() { return "A"; } } private static class RequestCacheTimeoutWithoutFallback extends TestHystrixObservableCommand<Boolean> { public RequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionTimeoutInMilliseconds(200))); // we want it to timeout } @Override protected Observable<Boolean> construct() { return Observable.create(new OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> s) { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); // e.printStackTrace(); } s.onNext(true); s.onCompleted(); } }).subscribeOn(Schedulers.computation()); } @Override public String getCacheKey() { return "A"; } } private static class RequestCacheThreadRejectionWithoutFallback extends TestHystrixObservableCommand<Boolean> { final CountDownLatch completionLatch; public RequestCacheThreadRejectionWithoutFallback(TestCircuitBreaker circuitBreaker, CountDownLatch completionLatch) { super(testPropsBuilder() .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)) .setCircuitBreaker(circuitBreaker) .setMetrics(circuitBreaker.metrics) .setThreadPool(new HystrixThreadPool() { @Override public ThreadPoolExecutor getExecutor() { return null; } @Override public void markThreadExecution() { } @Override public void markThreadCompletion() { } @Override public void markThreadRejection() { } @Override public boolean isQueueSpaceAvailable() { // always return false so we reject everything return false; } @Override public Scheduler getScheduler() { return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); } @Override public Scheduler getScheduler(Func0<Boolean> shouldInterruptThread) { return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread); } })); this.completionLatch = completionLatch; } @Override protected Observable<Boolean> construct() { try { if (completionLatch.await(1000, TimeUnit.MILLISECONDS)) { throw new RuntimeException("timed out waiting on completionLatch"); } } catch (InterruptedException e) { throw new RuntimeException(e); } return Observable.just(true); } @Override public String getCacheKey() { return "A"; } } private static class CommandWithErrorThrown extends TestHystrixObservableCommand<Boolean> { private final boolean asyncException; public CommandWithErrorThrown(TestCircuitBreaker circuitBreaker, boolean asyncException) { super(testPropsBuilder() .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); this.asyncException = asyncException; } @Override protected Observable<Boolean> construct() { Error error = new Error("simulated java.lang.Error message"); if (asyncException) { return Observable.error(error); } else { throw error; } } } private static class CommandWithCheckedException extends TestHystrixObservableCommand<Boolean> { public CommandWithCheckedException(TestCircuitBreaker circuitBreaker) { super(testPropsBuilder() .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); } @Override protected Observable<Boolean> construct() { return Observable.error(new IOException("simulated checked exception message")); } } }