/**
* 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.TryableSemaphore;
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.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import rx.Observable;
import rx.Observer;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
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 HystrixCommandTest extends CommonHystrixCommandTests<TestHystrixCommand<Integer>> {
@Rule
public HystrixRequestContextRule ctx = new HystrixRequestContextRule();
@After
public void cleanup() {
// force properties to be clean as well
ConfigurationManager.getConfigInstance().clear();
HystrixCommandKey key = Hystrix.getCurrentThreadExecutingCommand();
if (key != null) {
System.out.println("WARNING: Hystrix.getCurrentThreadExecutingCommand() should be null but got: " + key + ". Can occur when calling queue() and never retrieving.");
}
}
/**
* Test a successful command execution.
*/
@Test
public void testExecutionSuccess() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS);
assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.execute());
assertEquals(null, command.getFailedExecutionException());
assertNull(command.getExecutionException());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isSuccessfulExecution());
assertCommandExecutionEvents(command, HystrixEventType.SUCCESS);
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertSaneHystrixRequestLog(1);
}
/**
* Test that a command can not be executed multiple times.
*/
@Test
public void testExecutionMultipleTimes() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS);
assertFalse(command.isExecutionComplete());
// first should succeed
assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.execute());
assertTrue(command.isExecutionComplete());
assertTrue(command.isExecutedInThread());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isSuccessfulExecution());
assertNull(command.getExecutionException());
try {
// second should fail
command.execute();
fail("we should not allow this ... it breaks the state of request logs");
} catch (HystrixRuntimeException e) {
e.printStackTrace();
// we want to get here
}
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
assertCommandExecutionEvents(command, HystrixEventType.SUCCESS);
}
/**
* Test a command execution that throws an HystrixException and didn't implement getFallback.
*/
@Test
public void testExecutionHystrixFailureWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.execute();
fail("we shouldn't get here");
} catch (HystrixRuntimeException e) {
e.printStackTrace();
assertNotNull(e.getFallbackException());
assertNotNull(e.getImplementingClass());
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution that throws an unknown exception (not HystrixException) and didn't implement getFallback.
*/
@Test
public void testExecutionFailureWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.execute();
fail("we shouldn't get here");
} catch (HystrixRuntimeException e) {
e.printStackTrace();
assertNotNull(e.getFallbackException());
assertNotNull(e.getImplementingClass());
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution that throws an exception that should not be wrapped.
*/
@Test
public void testNotWrappedExceptionWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.execute();
fail("we shouldn't get here");
} catch (HystrixRuntimeException e) {
e.printStackTrace();
fail("we shouldn't get a HystrixRuntimeException");
} catch (RuntimeException e) {
assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException);
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
assertNotNull(command.getExecutionException());
assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestRuntimeException);
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution that throws an exception that should not be wrapped.
*/
@Test
public void testNotWrappedBadRequestWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST_NOT_WRAPPED, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.execute();
fail("we shouldn't get here");
} catch (HystrixRuntimeException e) {
e.printStackTrace();
fail("we shouldn't get a HystrixRuntimeException");
} catch (RuntimeException e) {
assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException);
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.getEventCounts().contains(HystrixEventType.BAD_REQUEST));
assertCommandExecutionEvents(command, HystrixEventType.BAD_REQUEST);
assertNotNull(command.getExecutionException());
assertTrue(command.getExecutionException() instanceof HystrixBadRequestException);
assertTrue(command.getExecutionException().getCause() instanceof NotWrappedByHystrixTestRuntimeException);
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution that fails but has a fallback.
*/
@Test
public void testExecutionFailureWithFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute());
assertEquals("Execution Failure for TestHystrixCommand", command.getFailedExecutionException().getMessage());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution that throws exception that should not be wrapped but has a fallback.
*/
@Test
public void testNotWrappedExceptionWithFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
try {
command.execute();
fail("we shouldn't get here");
} catch (HystrixRuntimeException e) {
e.printStackTrace();
fail("we shouldn't get a HystrixRuntimeException");
} catch (RuntimeException e) {
assertTrue(e instanceof NotWrappedByHystrixTestRuntimeException);
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
assertNotNull(command.getExecutionException());
assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestRuntimeException);
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution that fails, has getFallback implemented but that fails as well.
*/
@Test
public void testExecutionFailureWithFallbackFailure() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.FAILURE);
try {
command.execute();
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());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a successful command execution (asynchronously).
*/
@Test
public void testQueueSuccess() throws Exception {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS);
Future<Integer> future = command.queue();
assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, future.get());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isSuccessfulExecution());
assertCommandExecutionEvents(command, HystrixEventType.SUCCESS);
assertNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution (asynchronously) that throws an HystrixException and didn't implement getFallback.
*/
@Test
public void testQueueKnownFailureWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.queue().get();
fail("we shouldn't get here");
} catch (Exception e) {
e.printStackTrace();
if (e.getCause() instanceof HystrixRuntimeException) {
HystrixRuntimeException de = (HystrixRuntimeException) e.getCause();
assertNotNull(de.getFallbackException());
assertNotNull(de.getImplementingClass());
} else {
fail("the cause should be HystrixRuntimeException");
}
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution (asynchronously) that throws an unknown exception (not HystrixException) and didn't implement getFallback.
*/
@Test
public void testQueueUnknownFailureWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.queue().get();
fail("we shouldn't get here");
} catch (Exception e) {
e.printStackTrace();
if (e.getCause() instanceof HystrixRuntimeException) {
HystrixRuntimeException de = (HystrixRuntimeException) e.getCause();
assertNotNull(de.getFallbackException());
assertNotNull(de.getImplementingClass());
} else {
fail("the cause should be HystrixRuntimeException");
}
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution (asynchronously) that fails but has a fallback.
*/
@Test
public void testQueueFailureWithFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
try {
Future<Integer> future = command.queue();
assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, future.get());
} catch (Exception e) {
e.printStackTrace();
fail("We should have received a response from the fallback.");
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution (asynchronously) that fails, has getFallback implemented but that fails as well.
*/
@Test
public void testQueueFailureWithFallbackFailure() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, AbstractTestHystrixCommand.FallbackResult.FAILURE);
try {
command.queue().get();
fail("we shouldn't get here");
} catch (Exception e) {
if (e.getCause() instanceof HystrixRuntimeException) {
HystrixRuntimeException de = (HystrixRuntimeException) e.getCause();
e.printStackTrace();
assertNotNull(de.getFallbackException());
} else {
fail("the cause should be HystrixRuntimeException");
}
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_FAILURE);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a successful command execution.
*/
@Test
public void testObserveSuccess() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS);
assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.observe().toBlocking().single());
assertEquals(null, command.getFailedExecutionException());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isSuccessfulExecution());
assertCommandExecutionEvents(command, HystrixEventType.SUCCESS);
assertNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a successful command execution.
*/
@Test
public void testCallbackThreadForThreadIsolation() throws Exception {
final AtomicReference<Thread> commandThread = new AtomicReference<Thread>();
final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>();
TestHystrixCommand<Boolean> command = new TestHystrixCommand<Boolean>(TestHystrixCommand.testPropsBuilder()) {
@Override
protected Boolean run() {
commandThread.set(Thread.currentThread());
return 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());
assertTrue(commandThread.get().getName().startsWith("hystrix-"));
assertTrue(subscribeThread.get().getName().startsWith("hystrix-"));
}
/**
* Test a successful command execution.
*/
@Test
public void testCallbackThreadForSemaphoreIsolation() throws Exception {
final AtomicReference<Thread> commandThread = new AtomicReference<Thread>();
final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>();
TestHystrixCommand<Boolean> command = new TestHystrixCommand<Boolean>(TestHystrixCommand.testPropsBuilder()
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))) {
@Override
protected Boolean run() {
commandThread.set(Thread.currentThread());
return 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));
assertTrue(subscribeThread.get().getName().equals(mainThreadName));
}
/**
* Tests that the circuit-breaker reports itself as "OPEN" if set as forced-open
*/
@Test
public void testCircuitBreakerReportsOpenIfForcedOpen() {
HystrixCommand<Boolean> cmd = new HystrixCommand<Boolean>(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")).andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(true))) {
@Override
protected Boolean run() throws Exception {
return true;
}
@Override
protected Boolean getFallback() {
return false;
}
};
assertFalse(cmd.execute()); //fallback should fire
System.out.println("RESULT : " + cmd.getExecutionEvents());
assertTrue(cmd.isCircuitBreakerOpen());
}
/**
* Tests that the circuit-breaker reports itself as "CLOSED" if set as forced-closed
*/
@Test
public void testCircuitBreakerReportsClosedIfForcedClosed() {
HystrixCommand<Boolean> cmd = new HystrixCommand<Boolean>(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GROUP")).andCommandPropertiesDefaults(
new HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(false).withCircuitBreakerForceClosed(true))) {
@Override
protected Boolean run() throws Exception {
return true;
}
@Override
protected Boolean getFallback() {
return false;
}
};
assertTrue(cmd.execute());
System.out.println("RESULT : " + cmd.getExecutionEvents());
assertFalse(cmd.isCircuitBreakerOpen());
}
/**
* Test that the circuit-breaker is shared across HystrixCommand objects with the same CommandKey.
* <p>
* This will test HystrixCommand objects with a single circuit-breaker (as if each injected with same CommandKey)
* <p>
* Multiple HystrixCommand objects with the same dependency use the same circuit-breaker.
*/
@Test
public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() throws InterruptedException {
HystrixCommandKey key = HystrixCommandKey.Factory.asKey("SharedCircuitBreaker");
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(key);
/* fail 3 times and then it should trip the circuit and stop executing */
// failure 1
TestHystrixCommand<Integer> attempt1 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker);
System.out.println("COMMAND KEY (from cmd): " + attempt1.commandKey.name());
attempt1.execute();
Thread.sleep(100);
assertTrue(attempt1.isResponseFromFallback());
assertFalse(attempt1.isCircuitBreakerOpen());
assertFalse(attempt1.isResponseShortCircuited());
// failure 2 with a different command, same circuit breaker
TestHystrixCommand<Integer> attempt2 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker);
attempt2.execute();
Thread.sleep(100);
assertTrue(attempt2.isFailedExecution());
assertTrue(attempt2.isResponseFromFallback());
assertFalse(attempt2.isCircuitBreakerOpen());
assertFalse(attempt2.isResponseShortCircuited());
// failure 3 of the Hystrix, 2nd for this particular HystrixCommand
TestHystrixCommand<Integer> attempt3 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker);
attempt3.execute();
Thread.sleep(100);
assertTrue(attempt3.isFailedExecution());
assertTrue(attempt3.isResponseFromFallback());
assertFalse(attempt3.isResponseShortCircuited());
// it should now be 'open' and prevent further executions
// after having 3 failures on the Hystrix that these 2 different HystrixCommand objects are for
assertTrue(attempt3.isCircuitBreakerOpen());
// attempt 4
TestHystrixCommand<Integer> attempt4 = getSharedCircuitBreakerCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker);
attempt4.execute();
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());
assertSaneHystrixRequestLog(4);
assertCommandExecutionEvents(attempt1, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(attempt2, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(attempt3, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(attempt4, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS);
}
/**
* Test that the circuit-breaker being disabled doesn't wreak havoc.
*/
@Test
public void testExecutionSuccessWithCircuitBreakerDisabled() {
TestHystrixCommand<Integer> command = getCircuitBreakerDisabledCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS);
assertEquals(FlexibleTestHystrixCommand.EXECUTE_VALUE, command.execute());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
// we'll still get metrics ... just not the circuit breaker opening/closing
assertCommandExecutionEvents(command, HystrixEventType.SUCCESS);
}
/**
* Test a command execution timeout where the command didn't implement getFallback.
*/
@Test
public void testExecutionTimeoutWithNoFallback() {
TestHystrixCommand<Integer> command = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50);
try {
command.execute();
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());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution timeout where the command implemented getFallback.
*/
@Test
public void testExecutionTimeoutWithFallback() {
TestHystrixCommand<Integer> command = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50);
assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute());
// the time should be 50+ since we timeout at 50ms
assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50);
assertFalse(command.isCircuitBreakerOpen());
assertFalse(command.isResponseShortCircuited());
assertTrue(command.isResponseTimedOut());
assertTrue(command.isResponseFromFallback());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a command execution timeout where the command implemented getFallback but it fails.
*/
@Test
public void testExecutionTimeoutFallbackFailure() {
TestHystrixCommand<Integer> command = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 50);
try {
command.execute();
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);
} else {
fail("the exception should be HystrixRuntimeException");
}
}
assertNotNull(command.getExecutionException());
// 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.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test that the command finishing AFTER a timeout (because thread continues in background) does not register a SUCCESS
*/
@Test
public void testCountersOnExecutionTimeout() throws Exception {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50);
command.execute();
/* wait long enough for the command to have finished */
Thread.sleep(200);
/* response should still be the same as 'testCircuitBreakerOnExecutionTimeout' */
assertTrue(command.isResponseFromFallback());
assertFalse(command.isCircuitBreakerOpen());
assertFalse(command.isResponseShortCircuited());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isResponseTimedOut());
assertFalse(command.isSuccessfulExecution());
assertNotNull(command.getExecutionException());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS);
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a queued command execution timeout where the command didn't implement getFallback.
* <p>
* We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking
* indefinitely by skipping the timeout protection of the execute() command.
*/
@Test
public void testQueuedExecutionTimeoutWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50);
try {
command.queue().get();
fail("we shouldn't get here");
} catch (Exception e) {
e.printStackTrace();
if (e instanceof ExecutionException && e.getCause() instanceof HystrixRuntimeException) {
HystrixRuntimeException de = (HystrixRuntimeException) e.getCause();
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());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a queued command execution timeout where the command implemented getFallback.
* <p>
* We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking
* indefinitely by skipping the timeout protection of the execute() command.
*/
@Test
public void testQueuedExecutionTimeoutWithFallback() throws Exception {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50);
assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.queue().get());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a queued command execution timeout where the command implemented getFallback but it fails.
* <p>
* We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking
* indefinitely by skipping the timeout protection of the execute() command.
*/
@Test
public void testQueuedExecutionTimeoutFallbackFailure() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 50);
try {
command.queue().get();
fail("we shouldn't get here");
} catch (Exception e) {
if (e instanceof ExecutionException && e.getCause() instanceof HystrixRuntimeException) {
HystrixRuntimeException de = (HystrixRuntimeException) e.getCause();
assertNotNull(de.getFallbackException());
assertFalse(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");
}
}
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a queued command execution timeout where the command didn't implement getFallback.
* <p>
* We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking
* indefinitely by skipping the timeout protection of the execute() command.
*/
@Test
public void testObservedExecutionTimeoutWithNoFallback() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, 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 ExecutionException with cause as HystrixRuntimeException");
}
}
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isResponseTimedOut());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a queued command execution timeout where the command implemented getFallback.
* <p>
* We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking
* indefinitely by skipping the timeout protection of the execute() command.
*/
@Test
public void testObservedExecutionTimeoutWithFallback() throws Exception {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50);
assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.observe().toBlocking().single());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a queued command execution timeout where the command implemented getFallback but it fails.
* <p>
* We specifically want to protect against developers queuing commands and using queue().get() without a timeout (such as queue().get(3000, TimeUnit.Milliseconds)) and ending up blocking
* indefinitely by skipping the timeout protection of the execute() command.
*/
@Test
public void testObservedExecutionTimeoutFallbackFailure() {
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.FAILURE, 50);
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);
} else {
fail("the exception should be ExecutionException with cause as HystrixRuntimeException");
}
}
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_FAILURE);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testShortCircuitFallbackCounter() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true);
KnownFailureTestCommandWithFallback command1 = new KnownFailureTestCommandWithFallback(circuitBreaker);
command1.execute();
KnownFailureTestCommandWithFallback command2 = new KnownFailureTestCommandWithFallback(circuitBreaker);
command2.execute();
// will be -1 because it never attempted execution
assertTrue(command1.getExecutionTimeInMilliseconds() == -1);
assertTrue(command1.isResponseShortCircuited());
assertFalse(command1.isResponseTimedOut());
assertNotNull(command1.getExecutionException());
assertCommandExecutionEvents(command1, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SHORT_CIRCUITED, HystrixEventType.FALLBACK_SUCCESS);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
/**
* Test when a command fails to get queued up in the threadpool where the command didn't implement getFallback.
* <p>
* We specifically want to protect against developers getting random thread exceptions and instead just correctly receiving HystrixRuntimeException when no fallback exists.
*/
@Test
public void testRejectedThreadWithNoFallback() throws Exception {
HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-NoFallback");
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1);
// fill up the queue
pool.queue.add(new Runnable() {
@Override
public void run() {
System.out.println("**** queue filler1 ****");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Future<Boolean> f = null;
TestCommandRejection command1 = null;
TestCommandRejection command2 = null;
try {
command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED);
f = command1.queue();
command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED);
command2.queue();
fail("we shouldn't get here");
} catch (Exception e) {
e.printStackTrace();
System.out.println("command.getExecutionTimeInMilliseconds(): " + command2.getExecutionTimeInMilliseconds());
// will be -1 because it never attempted execution
assertTrue(command2.isResponseRejected());
assertFalse(command2.isResponseShortCircuited());
assertFalse(command2.isResponseTimedOut());
assertNotNull(command2.getExecutionException());
if (e instanceof HystrixRuntimeException && e.getCause() instanceof RejectedExecutionException) {
HystrixRuntimeException de = (HystrixRuntimeException) e;
assertNotNull(de.getFallbackException());
assertTrue(de.getFallbackException() instanceof UnsupportedOperationException);
assertNotNull(de.getImplementingClass());
assertNotNull(de.getCause());
assertTrue(de.getCause() instanceof RejectedExecutionException);
} else {
fail("the exception should be HystrixRuntimeException with cause as RejectedExecutionException");
}
}
f.get();
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
/**
* Test when a command fails to get queued up in the threadpool where the command implemented getFallback.
* <p>
* We specifically want to protect against developers getting random thread exceptions and instead just correctly receives a fallback.
*/
@Test
public void testRejectedThreadWithFallback() throws InterruptedException {
HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-Fallback");
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1);
//command 1 will execute in threadpool (passing through the queue)
//command 2 will execute after spending time in the queue (after command1 completes)
//command 3 will get rejected, since it finds pool and queue both full
TestCommandRejection command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS);
TestCommandRejection command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS);
TestCommandRejection command3 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS);
Observable<Boolean> result1 = command1.observe();
Observable<Boolean> result2 = command2.observe();
Thread.sleep(100);
//command3 should find queue filled, and get rejected
assertFalse(command3.execute());
assertTrue(command3.isResponseRejected());
assertFalse(command1.isResponseRejected());
assertFalse(command2.isResponseRejected());
assertTrue(command3.isResponseFromFallback());
assertNotNull(command3.getExecutionException());
assertCommandExecutionEvents(command3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_SUCCESS);
Observable.merge(result1, result2).toList().toBlocking().single(); //await the 2 latent commands
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertSaneHystrixRequestLog(3);
}
/**
* Test when a command fails to get queued up in the threadpool where the command implemented getFallback but it fails.
* <p>
* We specifically want to protect against developers getting random thread exceptions and instead just correctly receives an HystrixRuntimeException.
*/
@Test
public void testRejectedThreadWithFallbackFailure() throws ExecutionException, InterruptedException {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(1);
HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-A");
TestCommandRejection command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); //this should pass through the queue and sit in the pool
TestCommandRejection command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); //this should sit in the queue
TestCommandRejection command3 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_FAILURE); //this should observe full queue and get rejected
Future<Boolean> f1 = null;
Future<Boolean> f2 = null;
try {
f1 = command1.queue();
f2 = command2.queue();
assertEquals(false, command3.queue().get()); //should get thread-pool rejected
fail("we shouldn't get here");
} catch (Exception e) {
e.printStackTrace();
if (e instanceof HystrixRuntimeException && e.getCause() instanceof RejectedExecutionException) {
HystrixRuntimeException de = (HystrixRuntimeException) e;
assertNotNull(de.getFallbackException());
assertFalse(de.getFallbackException() instanceof UnsupportedOperationException);
assertNotNull(de.getImplementingClass());
assertNotNull(de.getCause());
assertTrue(de.getCause() instanceof RejectedExecutionException);
} else {
fail("the exception should be HystrixRuntimeException with cause as RejectedExecutionException");
}
}
assertCommandExecutionEvents(command1); //still in-flight, no events yet
assertCommandExecutionEvents(command2); //still in-flight, no events yet
assertCommandExecutionEvents(command3, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_FAILURE);
int numInFlight = circuitBreaker.metrics.getCurrentConcurrentExecutionCount();
assertTrue("Expected at most 1 in flight but got : " + numInFlight, numInFlight <= 1); //pool-filler still going
//This is a case where we knowingly walk away from executing Hystrix threads. They should have an in-flight status ("Executed"). You should avoid this in a production environment
HystrixRequestLog requestLog = HystrixRequestLog.getCurrentRequest();
assertEquals(3, requestLog.getAllExecutedCommands().size());
assertTrue(requestLog.getExecutedCommandsAsString().contains("Executed"));
//block on the outstanding work, so we don't inadvertently affect any other tests
long startTime = System.currentTimeMillis();
f1.get();
f2.get();
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
System.out.println("Time blocked : " + (System.currentTimeMillis() - startTime));
}
/**
* Test that we can reject a thread using isQueueSpaceAvailable() instead of just when the pool rejects.
* <p>
* For example, we have queue size set to 100 but want to reject when we hit 10.
* <p>
* This allows us to use FastProperties to control our rejection point whereas we can't resize a queue after it's created.
*/
@Test
public void testRejectedThreadUsingQueueSize() {
HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Rejection-B");
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(10, 1);
// put 1 item in the queue
// the thread pool won't pick it up because we're bypassing the pool and adding to the queue directly so this will keep the queue full
pool.queue.add(new Runnable() {
@Override
public void run() {
System.out.println("**** queue filler1 ****");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
TestCommandRejection command = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED);
try {
// this should fail as we already have 1 in the queue
command.queue();
fail("we shouldn't get here");
} catch (Exception e) {
e.printStackTrace();
assertTrue(command.isResponseRejected());
assertFalse(command.isResponseShortCircuited());
assertFalse(command.isResponseTimedOut());
assertNotNull(command.getExecutionException());
if (e instanceof HystrixRuntimeException && e.getCause() instanceof RejectedExecutionException) {
HystrixRuntimeException de = (HystrixRuntimeException) e;
assertNotNull(de.getFallbackException());
assertTrue(de.getFallbackException() instanceof UnsupportedOperationException);
assertNotNull(de.getImplementingClass());
assertNotNull(de.getCause());
assertTrue(de.getCause() instanceof RejectedExecutionException);
} else {
fail("the exception should be HystrixRuntimeException with cause as RejectedExecutionException");
}
}
assertCommandExecutionEvents(command, HystrixEventType.THREAD_POOL_REJECTED, HystrixEventType.FALLBACK_MISSING);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testDisabledTimeoutWorks() {
CommandWithDisabledTimeout cmd = new CommandWithDisabledTimeout(100, 900);
boolean result = cmd.execute();
assertEquals(true, result);
assertFalse(cmd.isResponseTimedOut());
assertNull(cmd.getExecutionException());
System.out.println("CMD : " + cmd.currentRequestLog.getExecutedCommandsAsString());
assertTrue(cmd.executionResult.getExecutionLatency() >= 900);
assertCommandExecutionEvents(cmd, HystrixEventType.SUCCESS);
}
@Test
public void testFallbackSemaphore() throws Exception {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
// single thread should work
TestSemaphoreCommandWithSlowFallback command1 = new TestSemaphoreCommandWithSlowFallback(circuitBreaker, 1, 200);
boolean result = command1.queue().get();
assertTrue(result);
// 2 threads, the second should be rejected by the fallback semaphore
boolean exceptionReceived = false;
Future<Boolean> result2 = null;
TestSemaphoreCommandWithSlowFallback command2 = null;
TestSemaphoreCommandWithSlowFallback command3 = null;
try {
System.out.println("c2 start: " + System.currentTimeMillis());
command2 = new TestSemaphoreCommandWithSlowFallback(circuitBreaker, 1, 800);
result2 = command2.queue();
System.out.println("c2 after queue: " + System.currentTimeMillis());
// make sure that thread gets a chance to run before queuing the next one
Thread.sleep(50);
System.out.println("c3 start: " + System.currentTimeMillis());
command3 = new TestSemaphoreCommandWithSlowFallback(circuitBreaker, 1, 200);
Future<Boolean> result3 = command3.queue();
System.out.println("c3 after queue: " + System.currentTimeMillis());
result3.get();
} catch (Exception e) {
e.printStackTrace();
exceptionReceived = true;
}
assertTrue(result2.get());
if (!exceptionReceived) {
fail("We expected an exception on the 2nd get");
}
assertCommandExecutionEvents(command1, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_REJECTION);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
@Test
public void testExecutionSemaphoreWithQueue() throws Exception {
final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
// single thread should work
TestSemaphoreCommand command1 = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED);
boolean result = command1.queue().get();
assertTrue(result);
final AtomicBoolean exceptionReceived = new AtomicBoolean();
final TryableSemaphore 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.queue().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.queue().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();
// make sure that t2 gets a chance to run before queuing the next one
Thread.sleep(50);
t3.start();
t2.join();
t3.join();
if (!exceptionReceived.get()) {
fail("We expected an exception on the 2nd get");
}
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_MISSING);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
@Test
public void testExecutionSemaphoreWithExecution() throws Exception {
final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
// single thread should work
TestSemaphoreCommand command1 = new TestSemaphoreCommand(circuitBreaker, 1, 200, TestSemaphoreCommand.RESULT_SUCCESS, TestSemaphoreCommand.FALLBACK_NOT_IMPLEMENTED);
boolean result = command1.execute();
assertFalse(command1.isExecutedInThread());
assertTrue(result);
final ArrayBlockingQueue<Boolean> results = new ArrayBlockingQueue<Boolean>(2);
final AtomicBoolean exceptionReceived = new AtomicBoolean();
final TryableSemaphore 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 {
results.add(command2.execute());
} 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 {
results.add(command3.execute());
} 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();
// make sure that t2 gets a chance to run before queuing the next one
Thread.sleep(50);
t3.start();
t2.join();
t3.join();
if (!exceptionReceived.get()) {
fail("We expected an exception on the 2nd get");
}
// only 1 value is expected as the other should have thrown an exception
assertEquals(1, results.size());
// should contain only a true result
assertTrue(results.contains(Boolean.TRUE));
assertFalse(results.contains(Boolean.FALSE));
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_MISSING);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
@Test
public void testRejectedExecutionSemaphoreWithFallbackViaExecute() throws Exception {
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.execute());
} 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.execute());
} 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();
// make sure that t2 gets a chance to run before queuing the next one
Thread.sleep(50);
t2.start();
t1.join();
t2.join();
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.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_SUCCESS);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
@Test
public void testRejectedExecutionSemaphoreWithFallbackViaObserve() throws Exception {
final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
final ArrayBlockingQueue<Observable<Boolean>> results = new ArrayBlockingQueue<Observable<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());
} 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());
} 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();
// make sure that t2 gets a chance to run before queuing the next one
Thread.sleep(50);
t2.start();
t1.join();
t2.join();
if (exceptionReceived.get()) {
fail("We should have received a fallback response");
}
final List<Boolean> blockingList = Observable.merge(results).toList().toBlocking().single();
// both threads should have returned values
assertEquals(2, blockingList.size());
// should contain both a true and false result
assertTrue(blockingList.contains(Boolean.TRUE));
assertTrue(blockingList.contains(Boolean.FALSE));
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SEMAPHORE_REJECTED, HystrixEventType.FALLBACK_SUCCESS);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
/**
* Tests that semaphores are counted separately for commands with unique keys
*/
@Test
public void testSemaphorePermitsInUse() throws Exception {
final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
// this semaphore will be shared across multiple command instances
final TryableSemaphoreActual sharedSemaphore =
new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(3));
// used to wait until all commands have 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);
// tracks failures to obtain semaphores
final AtomicInteger failureCount = new AtomicInteger();
final Runnable sharedSemaphoreRunnable = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() {
public void run() {
try {
new LatchedSemaphoreCommand("Command-Shared", circuitBreaker, sharedSemaphore, startLatch, sharedLatch).execute();
} catch (Exception e) {
startLatch.countDown();
e.printStackTrace();
failureCount.incrementAndGet();
}
}
});
// creates group of threads each using command sharing a single semaphore
// I create extra threads and commands so that I can verify that some of them fail to obtain a semaphore
final int sharedThreadCount = sharedSemaphore.numberOfPermits.get() * 2;
final Thread[] sharedSemaphoreThreads = new Thread[sharedThreadCount];
for (int i = 0; i < sharedThreadCount; i++) {
sharedSemaphoreThreads[i] = new Thread(sharedSemaphoreRunnable);
}
// creates thread using isolated semaphore
final TryableSemaphoreActual isolatedSemaphore =
new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1));
final CountDownLatch isolatedLatch = new CountDownLatch(1);
final Thread isolatedThread = new Thread(new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() {
public void run() {
try {
new LatchedSemaphoreCommand("Command-Isolated", circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch).execute();
} catch (Exception e) {
startLatch.countDown();
e.printStackTrace();
failureCount.incrementAndGet();
}
}
}));
// verifies no permits in use before starting threads
assertEquals("before threads start, shared semaphore should be unused", 0, sharedSemaphore.getNumberOfPermitsUsed());
assertEquals("before threads start, isolated semaphore should be unused", 0, isolatedSemaphore.getNumberOfPermitsUsed());
for (int i = 0; i < sharedThreadCount; i++) {
sharedSemaphoreThreads[i].start();
}
isolatedThread.start();
// waits until all commands have started
startLatch.await(1000, TimeUnit.MILLISECONDS);
// 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();
for (int i = 0; i < sharedThreadCount; i++) {
sharedSemaphoreThreads[i].join();
}
isolatedThread.join();
// verifies no permits in use after finishing threads
System.out.println("REQLOG : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
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
assertEquals("expected some of shared semaphore commands to get rejected", sharedSemaphore.numberOfPermits.get().longValue(), failureCount.get());
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
}
/**
* Test that HystrixOwner can be passed in dynamically.
*/
@Test
public void testDynamicOwner() {
TestHystrixCommand<Boolean> command = new DynamicOwnerTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE);
assertEquals(true, command.execute());
assertCommandExecutionEvents(command, HystrixEventType.SUCCESS);
}
/**
* Test a successful command execution.
*/
@Test(expected = IllegalStateException.class)
public void testDynamicOwnerFails() {
TestHystrixCommand<Boolean> command = new DynamicOwnerTestCommand(null);
assertEquals(true, command.execute());
}
/**
* Test that HystrixCommandKey can be passed in dynamically.
*/
@Test
public void testDynamicKey() throws Exception {
DynamicOwnerAndKeyTestCommand command1 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_ONE);
assertEquals(true, command1.execute());
DynamicOwnerAndKeyTestCommand command2 = new DynamicOwnerAndKeyTestCommand(InspectableBuilder.CommandGroupForUnitTest.OWNER_ONE, InspectableBuilder.CommandKeyForUnitTest.KEY_TWO);
assertEquals(true, command2.execute());
// 2 different circuit breakers should be created
assertNotSame(command1.getCircuitBreaker(), command2.getCircuitBreaker());
}
/**
* Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future
*/
@Test
public void testRequestCache1() throws Exception {
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.queue();
Future<String> f2 = command2.queue();
assertEquals("A", f1.get());
assertEquals("A", f2.get());
assertTrue(command1.executed);
// the second one should not have executed as it should have received the cached value instead
assertFalse(command2.executed);
assertTrue(command1.getExecutionTimeInMilliseconds() > -1);
assertFalse(command1.isResponseFromCache());
assertTrue(command2.isResponseFromCache());
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
/**
* Test Request scoped caching doesn't prevent different ones from executing
*/
@Test
public void testRequestCache2() throws Exception {
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.queue();
Future<String> f2 = command2.queue();
assertEquals("A", f1.get());
assertEquals("B", f2.get());
assertTrue(command1.executed);
// both should execute as they are different
assertTrue(command2.executed);
assertTrue(command2.getExecutionTimeInMilliseconds() > -1);
assertFalse(command2.isResponseFromCache());
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertNull(command1.getExecutionException());
assertFalse(command2.isResponseFromCache());
assertNull(command2.getExecutionException());
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
/**
* Test Request scoped caching with a mixture of commands
*/
@Test
public void testRequestCache3() throws Exception {
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.queue();
Future<String> f2 = command2.queue();
Future<String> f3 = command3.queue();
assertEquals("A", f1.get());
assertEquals("B", f2.get());
assertEquals("A", f3.get());
assertTrue(command1.executed);
// both should execute as they are different
assertTrue(command2.executed);
// but the 3rd should come from cache
assertFalse(command3.executed);
assertTrue(command3.isResponseFromCache());
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
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() throws Exception {
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.queue();
Future<String> f2 = command2.queue();
Future<String> f3 = command3.queue();
Future<String> f4 = command4.queue();
assertEquals("A", f2.get());
assertEquals("A", f3.get());
assertEquals("A", f4.get());
assertEquals("A", f1.get());
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);
assertTrue(command1.getExecutionTimeInMilliseconds() > -1);
assertFalse(command1.isResponseFromCache());
assertTrue(command2.getExecutionTimeInMilliseconds() == -1);
assertTrue(command2.isResponseFromCache());
assertTrue(command3.isResponseFromCache());
assertTrue(command3.getExecutionTimeInMilliseconds() == -1);
assertTrue(command4.isResponseFromCache());
assertTrue(command4.getExecutionTimeInMilliseconds() == -1);
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertCommandExecutionEvents(command4, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(4);
System.out.println("HystrixRequestLog: " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
}
/**
* Test Request scoped caching with a mixture of commands
*/
@Test
public void testNoRequestCache3() throws Exception {
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.queue();
Future<String> f2 = command2.queue();
Future<String> f3 = command3.queue();
assertEquals("A", f1.get());
assertEquals("B", f2.get());
assertEquals("A", f3.get());
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.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
/**
* Test Request scoped caching with a mixture of commands
*/
@Test
public void testRequestCacheViaQueueSemaphore1() throws Exception {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A");
SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "B");
SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A");
assertFalse(command1.isCommandRunningInThread());
Future<String> f1 = command1.queue();
Future<String> f2 = command2.queue();
Future<String> f3 = command3.queue();
assertEquals("A", f1.get());
assertEquals("B", f2.get());
assertEquals("A", f3.get());
assertTrue(command1.executed);
// both should execute as they are different
assertTrue(command2.executed);
// but the 3rd should come from cache
assertFalse(command3.executed);
assertTrue(command3.isResponseFromCache());
assertTrue(command3.getExecutionTimeInMilliseconds() == -1);
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
/**
* Test Request scoped caching with a mixture of commands
*/
@Test
public void testNoRequestCacheViaQueueSemaphore1() throws Exception {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A");
SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "B");
SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A");
assertFalse(command1.isCommandRunningInThread());
Future<String> f1 = command1.queue();
Future<String> f2 = command2.queue();
Future<String> f3 = command3.queue();
assertEquals("A", f1.get());
assertEquals("B", f2.get());
assertEquals("A", f3.get());
assertTrue(command1.executed);
// both should execute as they are different
assertTrue(command2.executed);
// this should also execute because caching is disabled
assertTrue(command3.executed);
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
/**
* Test Request scoped caching with a mixture of commands
*/
@Test
public void testRequestCacheViaExecuteSemaphore1() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A");
SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "B");
SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, true, "A");
assertFalse(command1.isCommandRunningInThread());
String f1 = command1.execute();
String f2 = command2.execute();
String f3 = command3.execute();
assertEquals("A", f1);
assertEquals("B", f2);
assertEquals("A", f3);
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.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
/**
* Test Request scoped caching with a mixture of commands
*/
@Test
public void testNoRequestCacheViaExecuteSemaphore1() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SuccessfulCacheableCommandViaSemaphore command1 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A");
SuccessfulCacheableCommandViaSemaphore command2 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "B");
SuccessfulCacheableCommandViaSemaphore command3 = new SuccessfulCacheableCommandViaSemaphore(circuitBreaker, false, "A");
assertFalse(command1.isCommandRunningInThread());
String f1 = command1.execute();
String f2 = command2.execute();
String f3 = command3.execute();
assertEquals("A", f1);
assertEquals("B", f2);
assertEquals("A", f3);
assertTrue(command1.executed);
// both should execute as they are different
assertTrue(command2.executed);
// this should also execute because caching is disabled
assertTrue(command3.executed);
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command3, HystrixEventType.SUCCESS);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
}
@Test
public void testNoRequestCacheOnTimeoutThrowsException() throws Exception {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
NoRequestCacheTimeoutWithoutFallback r1 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker);
try {
System.out.println("r1 value: " + r1.execute());
// 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.execute();
// 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.queue();
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.execute();
// we should have thrown an exception
fail("expected a timeout");
} catch (HystrixRuntimeException e) {
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);
// Expect it to time out - all results should be false
assertFalse(command1.execute());
assertFalse(command2.execute()); // return from cache #1
assertFalse(command3.execute()); // return from cache #2
Thread.sleep(500); // timeout on command is set to 200ms
RequestCacheNullPointerExceptionCase command4 = new RequestCacheNullPointerExceptionCase(circuitBreaker);
Boolean value = command4.execute(); // return from cache #3
assertFalse(value);
RequestCacheNullPointerExceptionCase command5 = new RequestCacheNullPointerExceptionCase(circuitBreaker);
Future<Boolean> f = command5.queue(); // 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_SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertCommandExecutionEvents(command3, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertCommandExecutionEvents(command4, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertCommandExecutionEvents(command5, HystrixEventType.TIMEOUT, 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.execute());
// we should have thrown an exception
fail("expected a timeout");
} catch (HystrixRuntimeException e) {
assertTrue(r1.isResponseTimedOut());
// what we want
}
RequestCacheTimeoutWithoutFallback r2 = new RequestCacheTimeoutWithoutFallback(circuitBreaker);
try {
r2.execute();
// we should have thrown an exception
fail("expected a timeout");
} catch (HystrixRuntimeException e) {
assertTrue(r2.isResponseTimedOut());
// what we want
}
RequestCacheTimeoutWithoutFallback r3 = new RequestCacheTimeoutWithoutFallback(circuitBreaker);
Future<Boolean> f3 = r3.queue();
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
RequestCacheTimeoutWithoutFallback r4 = new RequestCacheTimeoutWithoutFallback(circuitBreaker);
try {
r4.execute();
// we should have thrown an exception
fail("expected a timeout");
} catch (HystrixRuntimeException e) {
assertTrue(r4.isResponseTimedOut());
assertFalse(r4.isResponseFromFallback());
// what we want
}
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.execute());
// we should have thrown an exception
fail("expected a rejection");
} catch (HystrixRuntimeException e) {
assertTrue(r1.isResponseRejected());
// what we want
}
RequestCacheThreadRejectionWithoutFallback r2 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch);
try {
System.out.println("r2: " + r2.execute());
// we should have thrown an exception
fail("expected a rejection");
} catch (HystrixRuntimeException e) {
// e.printStackTrace();
assertTrue(r2.isResponseRejected());
// what we want
}
RequestCacheThreadRejectionWithoutFallback r3 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch);
try {
System.out.println("f3: " + r3.queue().get());
// we should have thrown an exception
fail("expected a rejection");
} catch (HystrixRuntimeException e) {
// e.printStackTrace();
assertTrue(r3.isResponseRejected());
// what we want
}
// 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.execute());
// we should have thrown an exception
fail("expected a rejection");
} catch (HystrixRuntimeException e) {
// e.printStackTrace();
assertTrue(r4.isResponseRejected());
assertFalse(r4.isResponseFromFallback());
// 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() throws Exception {
/* force the RequestVariable to not be initialized */
HystrixRequestContext.setContextOnCurrentThread(null);
TestHystrixCommand<Boolean> command = new SuccessfulTestCommand();
assertEquals(true, command.execute());
TestHystrixCommand<Boolean> command2 = new SuccessfulTestCommand();
assertEquals(true, command2.queue().get());
}
/**
* Test that if we try and execute a command with a cacheKey without initializing RequestVariable that it gives an error.
*/
@Test(expected = HystrixRuntimeException.class)
public void testCacheKeyExecutionRequiresRequestVariable() throws Exception {
/* force the RequestVariable to not be initialized */
HystrixRequestContext.setContextOnCurrentThread(null);
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SuccessfulCacheableCommand command = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "one");
assertEquals("one", command.execute());
SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand<String>(circuitBreaker, true, "two");
assertEquals("two", command2.queue().get());
}
/**
* Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback.
*/
@Test
public void testBadRequestExceptionViaExecuteInThread() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
BadRequestCommand command1 = null;
try {
command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD);
command1.execute();
fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName());
} catch (HystrixBadRequestException e) {
// success
e.printStackTrace();
}
assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback.
*/
@Test
public void testBadRequestExceptionViaQueueInThread() throws Exception {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
BadRequestCommand command1 = null;
try {
command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD);
command1.queue().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());
}
}
assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST);
assertNotNull(command1.getExecutionException());
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test that BadRequestException behavior works the same on a cached response.
*/
@Test
public void testBadRequestExceptionViaQueueInThreadOnResponseFromCache() throws Exception {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
// execute once to cache the value
BadRequestCommand command1 = null;
try {
command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD);
command1.execute();
} catch (Throwable e) {
// ignore
}
BadRequestCommand command2 = null;
try {
command2 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD);
command2.queue().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());
}
}
assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST);
assertCommandExecutionEvents(command2, HystrixEventType.BAD_REQUEST, HystrixEventType.RESPONSE_FROM_CACHE);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
/**
* Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback.
*/
@Test
public void testBadRequestExceptionViaExecuteInSemaphore() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
BadRequestCommand command1 = new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE);
try {
command1.execute();
fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName());
} catch (HystrixBadRequestException e) {
// success
e.printStackTrace();
}
assertCommandExecutionEvents(command1, HystrixEventType.BAD_REQUEST);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test a checked Exception being thrown
*/
@Test
public void testCheckedExceptionViaExecute() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker);
try {
command.execute();
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());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
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());
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
/**
* Test an Exception implementing NotWrappedByHystrix being thrown
*
* @throws InterruptedException
*/
@Test
public void testNotWrappedExceptionViaObserve() throws InterruptedException {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
CommandWithNotWrappedByHystrixException command = new CommandWithNotWrappedByHystrixException(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 NotWrappedByHystrixTestException);
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
assertNotNull(command.getExecutionException());
assertTrue(command.getExecutionException() instanceof NotWrappedByHystrixTestException);
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testSemaphoreExecutionWithTimeout() {
TestHystrixCommand<Boolean> cmd = new InterruptibleCommand(new TestCircuitBreaker(), false);
System.out.println("Starting command");
long timeMillis = System.currentTimeMillis();
try {
cmd.execute();
fail("Should throw");
} catch (Throwable t) {
assertNotNull(cmd.getExecutionException());
System.out.println("Unsuccessful Execution took : " + (System.currentTimeMillis() - timeMillis));
assertCommandExecutionEvents(cmd, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING);
assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
}
/**
* Test a recoverable java.lang.Error being thrown with no fallback
*/
@Test
public void testRecoverableErrorWithNoFallbackThrowsError() {
TestHystrixCommand<Integer> command = getRecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.execute();
fail("we expect to receive a " + Error.class.getSimpleName());
} catch (Exception e) {
// 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
// so HystrixRuntimeException -> wrapper Exception -> actual Error
assertEquals("Execution ERROR for TestHystrixCommand", e.getCause().getCause().getMessage());
}
assertEquals("Execution ERROR for TestHystrixCommand", command.getFailedExecutionException().getCause().getMessage());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testRecoverableErrorMaskedByFallbackButLogged() {
TestHystrixCommand<Integer> command = getRecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
assertEquals(FlexibleTestHystrixCommand.FALLBACK_VALUE, command.execute());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertNotNull(command.getExecutionException());
assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testUnrecoverableErrorThrownWithNoFallback() {
TestHystrixCommand<Integer> command = getUnrecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
try {
command.execute();
fail("we expect to receive a " + Error.class.getSimpleName());
} catch (Exception e) {
// 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
// so HystrixRuntimeException -> wrapper Exception -> actual Error
assertEquals("Unrecoverable Error for TestHystrixCommand", e.getCause().getCause().getMessage());
}
assertEquals("Unrecoverable Error for TestHystrixCommand", command.getFailedExecutionException().getCause().getMessage());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
assertNotNull(command.getExecutionException());
assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test //even though fallback is implemented, that logic never fires, as this is an unrecoverable error and should be directly propagated to the caller
public void testUnrecoverableErrorThrownWithFallback() {
TestHystrixCommand<Integer> command = getUnrecoverableErrorCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
try {
command.execute();
fail("we expect to receive a " + Error.class.getSimpleName());
} catch (Exception e) {
// 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
// so HystrixRuntimeException -> wrapper Exception -> actual Error
assertEquals("Unrecoverable Error for TestHystrixCommand", e.getCause().getCause().getMessage());
}
assertEquals("Unrecoverable Error for TestHystrixCommand", command.getFailedExecutionException().getCause().getMessage());
assertTrue(command.getExecutionTimeInMilliseconds() > -1);
assertTrue(command.isFailedExecution());
assertCommandExecutionEvents(command, HystrixEventType.FAILURE);
assertNotNull(command.getExecutionException());
assertEquals(0, command.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
static class EventCommand extends HystrixCommand {
public EventCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("eventGroup")).andCommandPropertiesDefaults(new HystrixCommandProperties.Setter().withFallbackIsolationSemaphoreMaxConcurrentRequests(3)));
}
@Override
protected String run() throws Exception {
System.out.println(Thread.currentThread().getName() + " : In run()");
throw new RuntimeException("run_exception");
}
@Override
public String getFallback() {
try {
System.out.println(Thread.currentThread().getName() + " : In fallback => " + getExecutionEvents());
Thread.sleep(30000L);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " : Interruption occurred");
}
System.out.println(Thread.currentThread().getName() + " : CMD Success Result");
return "fallback";
}
}
@Test
public void testNonBlockingCommandQueueFiresTimeout() throws Exception { //see https://github.com/Netflix/Hystrix/issues/514
final TestHystrixCommand<Integer> cmd = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 50);
new Thread() {
@Override
public void run() {
cmd.queue();
}
}.start();
Thread.sleep(200);
//timeout should occur in 50ms, and underlying thread should run for 500ms
//therefore, after 200ms, the command should have finished with a fallback on timeout
assertTrue(cmd.isExecutionComplete());
assertTrue(cmd.isResponseTimedOut());
assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount());
}
@Override
protected void assertHooksOnSuccess(Func0<TestHystrixCommand<Integer>> ctor, Action1<TestHystrixCommand<Integer>> assertion) {
assertExecute(ctor.call(), assertion, true);
assertBlockingQueue(ctor.call(), assertion, true);
assertNonBlockingQueue(ctor.call(), assertion, true, false);
assertBlockingObserve(ctor.call(), assertion, true);
assertNonBlockingObserve(ctor.call(), assertion, true);
}
@Override
protected void assertHooksOnFailure(Func0<TestHystrixCommand<Integer>> ctor, Action1<TestHystrixCommand<Integer>> assertion) {
assertExecute(ctor.call(), assertion, false);
assertBlockingQueue(ctor.call(), assertion, false);
assertNonBlockingQueue(ctor.call(), assertion, false, false);
assertBlockingObserve(ctor.call(), assertion, false);
assertNonBlockingObserve(ctor.call(), assertion, false);
}
@Override
protected void assertHooksOnFailure(Func0<TestHystrixCommand<Integer>> ctor, Action1<TestHystrixCommand<Integer>> assertion, boolean failFast) {
assertExecute(ctor.call(), assertion, false);
assertBlockingQueue(ctor.call(), assertion, false);
assertNonBlockingQueue(ctor.call(), assertion, false, failFast);
assertBlockingObserve(ctor.call(), assertion, false);
assertNonBlockingObserve(ctor.call(), assertion, false);
}
/**
* Run the command via {@link com.netflix.hystrix.HystrixCommand#execute()} and then assert
* @param command command to run
* @param assertion assertions to check
* @param isSuccess should the command succeedInteger
*/
private void assertExecute(TestHystrixCommand<Integer> command, Action1<TestHystrixCommand<Integer>> assertion, boolean isSuccess) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : Running command.execute() and then assertions...");
if (isSuccess) {
command.execute();
} else {
try {
Object o = command.execute();
fail("Expected a command failure!");
} catch (Exception ex) {
System.out.println("Received expected ex : " + ex);
ex.printStackTrace();
}
}
assertion.call(command);
}
/**
* Run the command via {@link com.netflix.hystrix.HystrixCommand#queue()}, immediately block, and then assert
* @param command command to run
* @param assertion assertions to check
* @param isSuccess should the command succeedInteger
*/
private void assertBlockingQueue(TestHystrixCommand<Integer> command, Action1<TestHystrixCommand<Integer>> assertion, boolean isSuccess) {
System.out.println("Running command.queue(), immediately blocking and then running assertions...");
if (isSuccess) {
try {
command.queue().get();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
try {
command.queue().get();
fail("Expected a command failure!");
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
} catch (ExecutionException ee) {
System.out.println("Received expected ex : " + ee.getCause());
ee.getCause().printStackTrace();
} catch (Exception e) {
System.out.println("Received expected ex : " + e);
e.printStackTrace();
}
}
assertion.call(command);
}
/**
* Run the command via {@link com.netflix.hystrix.HystrixCommand#queue()}, then poll for the command to be finished.
* When it is finished, assert
* @param command command to run
* @param assertion assertions to check
* @param isSuccess should the command succeedInteger
*/
private void assertNonBlockingQueue(TestHystrixCommand<Integer> command, Action1<TestHystrixCommand<Integer>> assertion, boolean isSuccess, boolean failFast) {
System.out.println("Running command.queue(), sleeping the test thread until command is complete, and then running assertions...");
Future<Integer> f = null;
if (failFast) {
try {
f = command.queue();
fail("Expected a failure when queuing the command");
} catch (Exception ex) {
System.out.println("Received expected fail fast ex : " + ex);
ex.printStackTrace();
}
} else {
try {
f = command.queue();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
awaitCommandCompletion(command);
assertion.call(command);
if (isSuccess) {
try {
f.get();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
} else {
try {
f.get();
fail("Expected a command failure!");
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
} catch (ExecutionException ee) {
System.out.println("Received expected ex : " + ee.getCause());
ee.getCause().printStackTrace();
} catch (Exception e) {
System.out.println("Received expected ex : " + e);
e.printStackTrace();
}
}
}
private <T> void awaitCommandCompletion(TestHystrixCommand<T> command) {
while (!command.isExecutionComplete()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException("interrupted");
}
}
}
/**
* Test a command execution that fails but has a fallback.
*/
@Test
public void testExecutionFailureWithFallbackImplementedButDisabled() {
TestHystrixCommand<Boolean> commandEnabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), true);
try {
assertEquals(false, commandEnabled.execute());
} catch (Exception e) {
e.printStackTrace();
fail("We should have received a response from the fallback.");
}
TestHystrixCommand<Boolean> commandDisabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), false);
try {
assertEquals(false, commandDisabled.execute());
fail("expect exception thrown");
} catch (Exception e) {
// expected
}
assertEquals("we failed with a simulated issue", commandDisabled.getFailedExecutionException().getMessage());
assertTrue(commandDisabled.isFailedExecution());
assertCommandExecutionEvents(commandEnabled, HystrixEventType.FAILURE, HystrixEventType.FALLBACK_SUCCESS);
assertCommandExecutionEvents(commandDisabled, HystrixEventType.FAILURE);
assertNotNull(commandDisabled.getExecutionException());
assertEquals(0, commandDisabled.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(2);
}
@Test
public void testExecutionTimeoutValue() {
HystrixCommand.Setter properties = HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(50));
HystrixCommand<String> command = new HystrixCommand<String>(properties) {
@Override
protected String run() throws Exception {
Thread.sleep(3000);
// should never reach here
return "hello";
}
@Override
protected String getFallback() {
if (isResponseTimedOut()) {
return "timed-out";
} else {
return "abc";
}
}
};
String value = command.execute();
assertTrue(command.isResponseTimedOut());
assertEquals("expected fallback value", "timed-out", value);
}
/**
* See https://github.com/Netflix/Hystrix/issues/212
*/
@Test
public void testObservableTimeoutNoFallbackThreadContext() {
TestSubscriber<Object> ts = new TestSubscriber<Object>();
final AtomicReference<Thread> onErrorThread = new AtomicReference<Thread>();
final AtomicBoolean isRequestContextInitialized = new AtomicBoolean();
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 200, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 50);
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());
assertCommandExecutionEvents(command, HystrixEventType.TIMEOUT, HystrixEventType.FALLBACK_MISSING);
assertNotNull(command.getExecutionException());
assertEquals(0, command.getBuilder().metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testExceptionConvertedToBadRequestExceptionInExecutionHookBypassesCircuitBreaker() {
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
ExceptionToBadRequestByExecutionHookCommand command = new ExceptionToBadRequestByExecutionHookCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD);
try {
command.execute();
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(command, HystrixEventType.BAD_REQUEST);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
}
@Test
public void testInterruptFutureOnTimeout() throws InterruptedException, ExecutionException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true);
// when
Future<Boolean> f = cmd.queue();
// then
Thread.sleep(500);
assertTrue(cmd.hasBeenInterrupted());
}
@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());
}
@Test
public void testDoNotInterruptFutureOnTimeoutIfPropertySaysNotTo() throws InterruptedException, ExecutionException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false);
// when
Future<Boolean> f = cmd.queue();
// then
Thread.sleep(500);
assertFalse(cmd.hasBeenInterrupted());
}
@Test
public void testDoNotInterruptObserveOnTimeoutIfPropertySaysNotTo() throws InterruptedException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false);
// when
cmd.observe().subscribe();
// then
Thread.sleep(500);
assertFalse(cmd.hasBeenInterrupted());
}
@Test
public void testDoNotInterruptToObservableOnTimeoutIfPropertySaysNotTo() throws InterruptedException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), false);
// when
cmd.toObservable().subscribe();
// then
Thread.sleep(500);
assertFalse(cmd.hasBeenInterrupted());
}
@Test
public void testCancelFutureWithInterruptionWhenPropertySaysNotTo() throws InterruptedException, ExecutionException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true, false, 1000);
// when
Future<Boolean> f = cmd.queue();
Thread.sleep(500);
f.cancel(true);
Thread.sleep(500);
// then
try {
f.get();
fail("Should have thrown a CancellationException");
} catch (CancellationException e) {
assertFalse(cmd.hasBeenInterrupted());
}
}
@Test
public void testCancelFutureWithInterruption() throws InterruptedException, ExecutionException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true, true, 1000);
// when
Future<Boolean> f = cmd.queue();
Thread.sleep(500);
f.cancel(true);
Thread.sleep(500);
// then
try {
f.get();
fail("Should have thrown a CancellationException");
} catch (CancellationException e) {
assertTrue(cmd.hasBeenInterrupted());
}
}
@Test
public void testCancelFutureWithoutInterruption() throws InterruptedException, ExecutionException, TimeoutException {
// given
InterruptibleCommand cmd = new InterruptibleCommand(new TestCircuitBreaker(), true, true, 1000);
// when
Future<Boolean> f = cmd.queue();
Thread.sleep(500);
f.cancel(false);
Thread.sleep(500);
// then
try {
f.get();
fail("Should have thrown a CancellationException");
} catch (CancellationException e) {
assertFalse(cmd.hasBeenInterrupted());
}
}
@Test
public void testChainedCommand() {
class SubCommand extends TestHystrixCommand<Integer> {
public SubCommand(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
@Override
protected Integer run() throws Exception {
return 2;
}
}
class PrimaryCommand extends TestHystrixCommand<Integer> {
public PrimaryCommand(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
@Override
protected Integer run() throws Exception {
throw new RuntimeException("primary failure");
}
@Override
protected Integer getFallback() {
SubCommand subCmd = new SubCommand(new TestCircuitBreaker());
return subCmd.execute();
}
}
assertTrue(2 == new PrimaryCommand(new TestCircuitBreaker()).execute());
}
@Test
public void testSlowFallback() {
class PrimaryCommand extends TestHystrixCommand<Integer> {
public PrimaryCommand(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
@Override
protected Integer run() throws Exception {
throw new RuntimeException("primary failure");
}
@Override
protected Integer getFallback() {
try {
Thread.sleep(1500);
return 1;
} catch (InterruptedException ie) {
System.out.println("Caught Interrupted Exception");
ie.printStackTrace();
}
return -1;
}
}
assertTrue(1 == new PrimaryCommand(new TestCircuitBreaker()).execute());
}
@Test
public void testSemaphoreThreadSafety() {
final int NUM_PERMITS = 1;
final TryableSemaphoreActual s = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(NUM_PERMITS));
final int NUM_THREADS = 10;
ExecutorService threadPool = Executors.newFixedThreadPool(NUM_THREADS);
final int NUM_TRIALS = 100;
for (int t = 0; t < NUM_TRIALS; t++) {
System.out.println("TRIAL : " + t);
final AtomicInteger numAcquired = new AtomicInteger(0);
final CountDownLatch latch = new CountDownLatch(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
threadPool.submit(new Runnable() {
@Override
public void run() {
boolean acquired = s.tryAcquire();
if (acquired) {
try {
numAcquired.incrementAndGet();
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
} finally {
s.release();
}
}
latch.countDown();
}
});
}
try {
assertTrue(latch.await(10000, TimeUnit.MILLISECONDS));
} catch (InterruptedException ex) {
fail(ex.getMessage());
}
assertEquals("Number acquired should be equal to the number of permits", NUM_PERMITS, numAcquired.get());
assertEquals("Semaphore should always get released back to 0", 0, s.getNumberOfPermitsUsed());
}
}
@Test
public void testCancelledTasksInQueueGetRemoved() throws Exception {
HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Cancellation-A");
TestCircuitBreaker circuitBreaker = new TestCircuitBreaker();
SingleThreadedPoolWithQueue pool = new SingleThreadedPoolWithQueue(10, 1);
TestCommandRejection command1 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED);
TestCommandRejection command2 = new TestCommandRejection(key, circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_NOT_IMPLEMENTED);
// this should go through the queue and into the thread pool
Future<Boolean> poolFiller = command1.queue();
// this command will stay in the queue until the thread pool is empty
Observable<Boolean> cmdInQueue = command2.observe();
Subscription s = cmdInQueue.subscribe();
assertEquals(1, pool.queue.size());
s.unsubscribe();
assertEquals(0, pool.queue.size());
//make sure we wait for the command to finish so the state is clean for next test
poolFiller.get();
assertCommandExecutionEvents(command1, HystrixEventType.SUCCESS);
assertCommandExecutionEvents(command2, HystrixEventType.CANCELLED);
assertEquals(0, circuitBreaker.metrics.getCurrentConcurrentExecutionCount());
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertSaneHystrixRequestLog(2);
}
@Test
public void testOnRunStartHookThrowsSemaphoreIsolated() {
final AtomicBoolean exceptionEncountered = new AtomicBoolean(false);
final AtomicBoolean onThreadStartInvoked = new AtomicBoolean(false);
final AtomicBoolean onThreadCompleteInvoked = new AtomicBoolean(false);
final AtomicBoolean executionAttempted = new AtomicBoolean(false);
class FailureInjectionHook extends HystrixCommandExecutionHook {
@Override
public <T> void onExecutionStart(HystrixInvokable<T> commandInstance) {
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, commandInstance.getClass(), "Injected Failure", null, null);
}
@Override
public <T> void onThreadStart(HystrixInvokable<T> commandInstance) {
onThreadStartInvoked.set(true);
super.onThreadStart(commandInstance);
}
@Override
public <T> void onThreadComplete(HystrixInvokable<T> commandInstance) {
onThreadCompleteInvoked.set(true);
super.onThreadComplete(commandInstance);
}
}
final FailureInjectionHook failureInjectionHook = new FailureInjectionHook();
class FailureInjectedCommand extends TestHystrixCommand<Integer> {
public FailureInjectedCommand(ExecutionIsolationStrategy isolationStrategy) {
super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)), failureInjectionHook);
}
@Override
protected Integer run() throws Exception {
executionAttempted.set(true);
return 3;
}
}
TestHystrixCommand<Integer> semaphoreCmd = new FailureInjectedCommand(ExecutionIsolationStrategy.SEMAPHORE);
try {
int result = semaphoreCmd.execute();
System.out.println("RESULT : " + result);
} catch (Throwable ex) {
ex.printStackTrace();
exceptionEncountered.set(true);
}
assertTrue(exceptionEncountered.get());
assertFalse(onThreadStartInvoked.get());
assertFalse(onThreadCompleteInvoked.get());
assertFalse(executionAttempted.get());
assertEquals(0, semaphoreCmd.metrics.getCurrentConcurrentExecutionCount());
}
@Test
public void testOnRunStartHookThrowsThreadIsolated() {
final AtomicBoolean exceptionEncountered = new AtomicBoolean(false);
final AtomicBoolean onThreadStartInvoked = new AtomicBoolean(false);
final AtomicBoolean onThreadCompleteInvoked = new AtomicBoolean(false);
final AtomicBoolean executionAttempted = new AtomicBoolean(false);
class FailureInjectionHook extends HystrixCommandExecutionHook {
@Override
public <T> void onExecutionStart(HystrixInvokable<T> commandInstance) {
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, commandInstance.getClass(), "Injected Failure", null, null);
}
@Override
public <T> void onThreadStart(HystrixInvokable<T> commandInstance) {
onThreadStartInvoked.set(true);
super.onThreadStart(commandInstance);
}
@Override
public <T> void onThreadComplete(HystrixInvokable<T> commandInstance) {
onThreadCompleteInvoked.set(true);
super.onThreadComplete(commandInstance);
}
}
final FailureInjectionHook failureInjectionHook = new FailureInjectionHook();
class FailureInjectedCommand extends TestHystrixCommand<Integer> {
public FailureInjectedCommand(ExecutionIsolationStrategy isolationStrategy) {
super(testPropsBuilder(new TestCircuitBreaker()).setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy)), failureInjectionHook);
}
@Override
protected Integer run() throws Exception {
executionAttempted.set(true);
return 3;
}
}
TestHystrixCommand<Integer> threadCmd = new FailureInjectedCommand(ExecutionIsolationStrategy.THREAD);
try {
int result = threadCmd.execute();
System.out.println("RESULT : " + result);
} catch (Throwable ex) {
ex.printStackTrace();
exceptionEncountered.set(true);
}
assertTrue(exceptionEncountered.get());
assertTrue(onThreadStartInvoked.get());
assertTrue(onThreadCompleteInvoked.get());
assertFalse(executionAttempted.get());
assertEquals(0, threadCmd.metrics.getCurrentConcurrentExecutionCount());
}
@Test
public void testEarlyUnsubscribeDuringExecutionViaToObservable() {
class AsyncCommand extends HystrixCommand<Boolean> {
public AsyncCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC")));
}
@Override
protected Boolean run() {
try {
Thread.sleep(500);
return true;
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
HystrixCommand<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");
}
@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);
s.unsubscribe();
assertTrue(latch.await(200, TimeUnit.MILLISECONDS));
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
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());
assertEquals(null, cmd.getFailedExecutionException());
assertNull(cmd.getExecutionException());
System.out.println("Execution time : " + cmd.getExecutionTimeInMilliseconds());
assertTrue(cmd.getExecutionTimeInMilliseconds() > -1);
assertFalse(cmd.isSuccessfulExecution());
assertCommandExecutionEvents(cmd, HystrixEventType.CANCELLED);
assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Test
public void testEarlyUnsubscribeDuringExecutionViaObserve() {
class AsyncCommand extends HystrixCommand<Boolean> {
public AsyncCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC")));
}
@Override
protected Boolean run() {
try {
Thread.sleep(500);
return true;
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
HystrixCommand<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");
}
@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);
s.unsubscribe();
assertTrue(latch.await(200, TimeUnit.MILLISECONDS));
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
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());
assertEquals(null, cmd.getFailedExecutionException());
assertNull(cmd.getExecutionException());
assertTrue(cmd.getExecutionTimeInMilliseconds() > -1);
assertFalse(cmd.isSuccessfulExecution());
assertCommandExecutionEvents(cmd, HystrixEventType.CANCELLED);
assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(1);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Test
public void testEarlyUnsubscribeDuringFallback() {
class AsyncCommand extends HystrixCommand<Boolean> {
public AsyncCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC")));
}
@Override
protected Boolean run() {
throw new RuntimeException("run failure");
}
@Override
protected Boolean getFallback() {
try {
Thread.sleep(500);
return false;
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
HystrixCommand<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());
assertEquals(0, cmd.metrics.getCurrentConcurrentExecutionCount());
assertFalse(cmd.isExecutionComplete());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Test
public void testRequestThenCacheHitAndCacheHitUnsubscribed() {
AsyncCacheableCommand original = new AsyncCacheableCommand("foo");
AsyncCacheableCommand fromCache = new AsyncCacheableCommand("foo");
final AtomicReference<Boolean> originalValue = new AtomicReference<Boolean>(null);
final AtomicReference<Boolean> fromCacheValue = new AtomicReference<Boolean>(null);
final CountDownLatch originalLatch = new CountDownLatch(1);
final CountDownLatch fromCacheLatch = new CountDownLatch(1);
Observable<Boolean> originalObservable = original.toObservable();
Observable<Boolean> fromCacheObservable = fromCache.toObservable();
Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe");
originalLatch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted");
originalLatch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e);
originalLatch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b);
originalValue.set(b);
}
});
Subscription fromCacheSubscription = fromCacheObservable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache Unsubscribe");
fromCacheLatch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache OnCompleted");
fromCacheLatch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache OnError : " + e);
fromCacheLatch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " FromCache OnNext : " + b);
fromCacheValue.set(b);
}
});
try {
fromCacheSubscription.unsubscribe();
assertTrue(fromCacheLatch.await(600, TimeUnit.MILLISECONDS));
assertTrue(originalLatch.await(600, TimeUnit.MILLISECONDS));
assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed());
assertTrue(original.isExecutionComplete());
assertTrue(original.isExecutedInThread());
assertEquals(null, original.getFailedExecutionException());
assertNull(original.getExecutionException());
assertTrue(original.getExecutionTimeInMilliseconds() > -1);
assertTrue(original.isSuccessfulExecution());
assertCommandExecutionEvents(original, HystrixEventType.SUCCESS);
assertTrue(originalValue.get());
assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount());
assertEquals("Number of execution semaphores in use (fromCache)", 0, fromCache.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (fromCache)", 0, fromCache.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(fromCache.isExecutionComplete());
assertFalse(fromCache.isExecutedInThread());
assertEquals(null, fromCache.getFailedExecutionException());
assertNull(fromCache.getExecutionException());
assertCommandExecutionEvents(fromCache, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED);
assertTrue(fromCache.getExecutionTimeInMilliseconds() == -1);
assertFalse(fromCache.isSuccessfulExecution());
assertEquals(0, fromCache.metrics.getCurrentConcurrentExecutionCount());
assertFalse(original.isCancelled()); //underlying work
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertSaneHystrixRequestLog(2);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Test
public void testRequestThenCacheHitAndOriginalUnsubscribed() {
AsyncCacheableCommand original = new AsyncCacheableCommand("foo");
AsyncCacheableCommand fromCache = new AsyncCacheableCommand("foo");
final AtomicReference<Boolean> originalValue = new AtomicReference<Boolean>(null);
final AtomicReference<Boolean> fromCacheValue = new AtomicReference<Boolean>(null);
final CountDownLatch originalLatch = new CountDownLatch(1);
final CountDownLatch fromCacheLatch = new CountDownLatch(1);
Observable<Boolean> originalObservable = original.toObservable();
Observable<Boolean> fromCacheObservable = fromCache.toObservable();
Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe");
originalLatch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted");
originalLatch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e);
originalLatch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b);
originalValue.set(b);
}
});
Subscription fromCacheSubscription = fromCacheObservable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache Unsubscribe");
fromCacheLatch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache OnCompleted");
fromCacheLatch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache OnError : " + e);
fromCacheLatch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache OnNext : " + b);
fromCacheValue.set(b);
}
});
try {
Thread.sleep(10);
originalSubscription.unsubscribe();
assertTrue(originalLatch.await(600, TimeUnit.MILLISECONDS));
assertTrue(fromCacheLatch.await(600, TimeUnit.MILLISECONDS));
assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(original.isExecutionComplete());
assertTrue(original.isExecutedInThread());
assertEquals(null, original.getFailedExecutionException());
assertNull(original.getExecutionException());
assertTrue(original.getExecutionTimeInMilliseconds() > -1);
assertFalse(original.isSuccessfulExecution());
assertCommandExecutionEvents(original, HystrixEventType.CANCELLED);
assertNull(originalValue.get());
assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount());
assertEquals("Number of execution semaphores in use (fromCache)", 0, fromCache.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (fromCache)", 0, fromCache.getFallbackSemaphore().getNumberOfPermitsUsed());
assertTrue(fromCache.isExecutionComplete());
assertFalse(fromCache.isExecutedInThread());
assertEquals(null, fromCache.getFailedExecutionException());
assertNull(fromCache.getExecutionException());
assertCommandExecutionEvents(fromCache, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertTrue(fromCache.getExecutionTimeInMilliseconds() == -1);
assertTrue(fromCache.isSuccessfulExecution());
assertEquals(0, fromCache.metrics.getCurrentConcurrentExecutionCount());
assertFalse(original.isCancelled()); //underlying work
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertSaneHystrixRequestLog(2);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Test
public void testRequestThenTwoCacheHitsOriginalAndOneCacheHitUnsubscribed() {
AsyncCacheableCommand original = new AsyncCacheableCommand("foo");
AsyncCacheableCommand fromCache1 = new AsyncCacheableCommand("foo");
AsyncCacheableCommand fromCache2 = new AsyncCacheableCommand("foo");
final AtomicReference<Boolean> originalValue = new AtomicReference<Boolean>(null);
final AtomicReference<Boolean> fromCache1Value = new AtomicReference<Boolean>(null);
final AtomicReference<Boolean> fromCache2Value = new AtomicReference<Boolean>(null);
final CountDownLatch originalLatch = new CountDownLatch(1);
final CountDownLatch fromCache1Latch = new CountDownLatch(1);
final CountDownLatch fromCache2Latch = new CountDownLatch(1);
Observable<Boolean> originalObservable = original.toObservable();
Observable<Boolean> fromCache1Observable = fromCache1.toObservable();
Observable<Boolean> fromCache2Observable = fromCache2.toObservable();
Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe");
originalLatch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted");
originalLatch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e);
originalLatch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b);
originalValue.set(b);
}
});
Subscription fromCache1Subscription = fromCache1Observable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 Unsubscribe");
fromCache1Latch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnCompleted");
fromCache1Latch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnError : " + e);
fromCache1Latch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnNext : " + b);
fromCache1Value.set(b);
}
});
Subscription fromCache2Subscription = fromCache2Observable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 Unsubscribe");
fromCache2Latch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnCompleted");
fromCache2Latch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnError : " + e);
fromCache2Latch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnNext : " + b);
fromCache2Value.set(b);
}
});
try {
Thread.sleep(10);
originalSubscription.unsubscribe();
//fromCache1Subscription.unsubscribe();
fromCache2Subscription.unsubscribe();
assertTrue(originalLatch.await(600, TimeUnit.MILLISECONDS));
assertTrue(fromCache1Latch.await(600, TimeUnit.MILLISECONDS));
assertTrue(fromCache2Latch.await(600, TimeUnit.MILLISECONDS));
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(original.isExecutionComplete());
assertTrue(original.isExecutedInThread());
assertEquals(null, original.getFailedExecutionException());
assertNull(original.getExecutionException());
assertTrue(original.getExecutionTimeInMilliseconds() > -1);
assertFalse(original.isSuccessfulExecution());
assertCommandExecutionEvents(original, HystrixEventType.CANCELLED);
assertNull(originalValue.get());
assertFalse(original.isCancelled()); //underlying work
assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount());
assertEquals("Number of execution semaphores in use (fromCache1)", 0, fromCache1.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (fromCache1)", 0, fromCache1.getFallbackSemaphore().getNumberOfPermitsUsed());
assertTrue(fromCache1.isExecutionComplete());
assertFalse(fromCache1.isExecutedInThread());
assertEquals(null, fromCache1.getFailedExecutionException());
assertNull(fromCache1.getExecutionException());
assertCommandExecutionEvents(fromCache1, HystrixEventType.SUCCESS, HystrixEventType.RESPONSE_FROM_CACHE);
assertTrue(fromCache1.getExecutionTimeInMilliseconds() == -1);
assertTrue(fromCache1.isSuccessfulExecution());
assertTrue(fromCache1Value.get());
assertEquals(0, fromCache1.metrics.getCurrentConcurrentExecutionCount());
assertEquals("Number of execution semaphores in use (fromCache2)", 0, fromCache2.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (fromCache2)", 0, fromCache2.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(fromCache2.isExecutionComplete());
assertFalse(fromCache2.isExecutedInThread());
assertEquals(null, fromCache2.getFailedExecutionException());
assertNull(fromCache2.getExecutionException());
assertCommandExecutionEvents(fromCache2, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED);
assertTrue(fromCache2.getExecutionTimeInMilliseconds() == -1);
assertFalse(fromCache2.isSuccessfulExecution());
assertNull(fromCache2Value.get());
assertEquals(0, fromCache2.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Test
public void testRequestThenTwoCacheHitsAllUnsubscribed() {
AsyncCacheableCommand original = new AsyncCacheableCommand("foo");
AsyncCacheableCommand fromCache1 = new AsyncCacheableCommand("foo");
AsyncCacheableCommand fromCache2 = new AsyncCacheableCommand("foo");
final CountDownLatch originalLatch = new CountDownLatch(1);
final CountDownLatch fromCache1Latch = new CountDownLatch(1);
final CountDownLatch fromCache2Latch = new CountDownLatch(1);
Observable<Boolean> originalObservable = original.toObservable();
Observable<Boolean> fromCache1Observable = fromCache1.toObservable();
Observable<Boolean> fromCache2Observable = fromCache2.toObservable();
Subscription originalSubscription = originalObservable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original Unsubscribe");
originalLatch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnCompleted");
originalLatch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnError : " + e);
originalLatch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.Original OnNext : " + b);
}
});
Subscription fromCache1Subscription = fromCache1Observable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 Unsubscribe");
fromCache1Latch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnCompleted");
fromCache1Latch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnError : " + e);
fromCache1Latch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache1 OnNext : " + b);
}
});
Subscription fromCache2Subscription = fromCache2Observable.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 Unsubscribe");
fromCache2Latch.countDown();
}
}).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnCompleted");
fromCache2Latch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnError : " + e);
fromCache2Latch.countDown();
}
@Override
public void onNext(Boolean b) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " Test.FromCache2 OnNext : " + b);
}
});
try {
Thread.sleep(10);
originalSubscription.unsubscribe();
fromCache1Subscription.unsubscribe();
fromCache2Subscription.unsubscribe();
assertTrue(originalLatch.await(200, TimeUnit.MILLISECONDS));
assertTrue(fromCache1Latch.await(200, TimeUnit.MILLISECONDS));
assertTrue(fromCache2Latch.await(200, TimeUnit.MILLISECONDS));
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertEquals("Number of execution semaphores in use (original)", 0, original.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (original)", 0, original.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(original.isExecutionComplete());
assertTrue(original.isExecutedInThread());
System.out.println("FEE : " + original.getFailedExecutionException());
if (original.getFailedExecutionException() != null) {
original.getFailedExecutionException().printStackTrace();
}
assertNull(original.getFailedExecutionException());
assertNull(original.getExecutionException());
assertTrue(original.getExecutionTimeInMilliseconds() > -1);
assertFalse(original.isSuccessfulExecution());
assertCommandExecutionEvents(original, HystrixEventType.CANCELLED);
//assertTrue(original.isCancelled()); //underlying work This doesn't work yet
assertEquals(0, original.metrics.getCurrentConcurrentExecutionCount());
assertEquals("Number of execution semaphores in use (fromCache1)", 0, fromCache1.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (fromCache1)", 0, fromCache1.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(fromCache1.isExecutionComplete());
assertFalse(fromCache1.isExecutedInThread());
assertEquals(null, fromCache1.getFailedExecutionException());
assertNull(fromCache1.getExecutionException());
assertCommandExecutionEvents(fromCache1, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED);
assertTrue(fromCache1.getExecutionTimeInMilliseconds() == -1);
assertFalse(fromCache1.isSuccessfulExecution());
assertEquals(0, fromCache1.metrics.getCurrentConcurrentExecutionCount());
assertEquals("Number of execution semaphores in use (fromCache2)", 0, fromCache2.getExecutionSemaphore().getNumberOfPermitsUsed());
assertEquals("Number of fallback semaphores in use (fromCache2)", 0, fromCache2.getFallbackSemaphore().getNumberOfPermitsUsed());
assertFalse(fromCache2.isExecutionComplete());
assertFalse(fromCache2.isExecutedInThread());
assertEquals(null, fromCache2.getFailedExecutionException());
assertNull(fromCache2.getExecutionException());
assertCommandExecutionEvents(fromCache2, HystrixEventType.RESPONSE_FROM_CACHE, HystrixEventType.CANCELLED);
assertTrue(fromCache2.getExecutionTimeInMilliseconds() == -1);
assertFalse(fromCache2.isSuccessfulExecution());
assertEquals(0, fromCache2.metrics.getCurrentConcurrentExecutionCount());
assertSaneHystrixRequestLog(3);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
/**
* Some RxJava operators like take(n), zip receive data in an onNext from upstream and immediately unsubscribe.
* When upstream is a HystrixCommand, Hystrix may get that unsubscribe before it gets to its onCompleted.
* This should still be marked as a HystrixEventType.SUCCESS.
*/
@Test
public void testUnsubscribingDownstreamOperatorStillResultsInSuccessEventType() throws InterruptedException {
HystrixCommand<Integer> cmd = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 100, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
Observable<Integer> o = cmd.toObservable()
.doOnNext(new Action1<Integer>() {
@Override
public void call(Integer i) {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnNext : " + i);
}
})
.doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnError : " + throwable);
}
})
.doOnCompleted(new Action0() {
@Override
public void call() {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnCompleted");
}
})
.doOnSubscribe(new Action0() {
@Override
public void call() {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnSubscribe");
}
})
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " CMD OnUnsubscribe");
}
})
.take(1)
.observeOn(Schedulers.io())
.map(new Func1<Integer, Integer>() {
@Override
public Integer call(Integer i) {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : Doing some more computation in the onNext!!");
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return i;
}
});
final CountDownLatch latch = new CountDownLatch(1);
o.doOnSubscribe(new Action0() {
@Override
public void call() {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnSubscribe");
}
}).doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnUnsubscribe");
}
}).subscribe(new Subscriber<Integer>() {
@Override
public void onCompleted() {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnCompleted");
latch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnError : " + e);
latch.countDown();
}
@Override
public void onNext(Integer i) {
System.out.println(Thread.currentThread().getName() + " : " + System.currentTimeMillis() + " : OnNext : " + i);
}
});
latch.await(1000, TimeUnit.MILLISECONDS);
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertTrue(cmd.isExecutedInThread());
assertCommandExecutionEvents(cmd, HystrixEventType.SUCCESS);
}
@Test
public void testUnsubscribeBeforeSubscribe() throws Exception {
//this may happen in Observable chain, so Hystrix should make sure that command never executes/allocates in this situation
Observable<String> error = Observable.error(new RuntimeException("foo"));
HystrixCommand<Integer> cmd = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 100);
Observable<Integer> cmdResult = cmd.toObservable()
.doOnNext(new Action1<Integer>() {
@Override
public void call(Integer integer) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + integer);
}
})
.doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable ex) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnError : " + ex);
}
})
.doOnCompleted(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnCompleted");
}
})
.doOnSubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnSubscribe");
}
})
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnUnsubscribe");
}
});
//the zip operator will subscribe to each observable. there is a race between the error of the first
//zipped observable terminating the zip and the subscription to the command's observable
Observable<String> zipped = Observable.zip(error, cmdResult, new Func2<String, Integer, String>() {
@Override
public String call(String s, Integer integer) {
return s + integer;
}
});
final CountDownLatch latch = new CountDownLatch(1);
zipped.subscribe(new Subscriber<String>() {
@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(String s) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " OnNext : " + s);
}
});
latch.await(1000, TimeUnit.MILLISECONDS);
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
}
@Test
public void testRxRetry() throws Exception {
// see https://github.com/Netflix/Hystrix/issues/1100
// Since each command instance is single-use, the expectation is that applying the .retry() operator
// results in only a single execution and propagation out of that error
HystrixCommand<Integer> cmd = getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 300,
AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 100);
final CountDownLatch latch = new CountDownLatch(1);
System.out.println(System.currentTimeMillis() + " : Starting");
Observable<Integer> o = cmd.toObservable().retry(2);
System.out.println(System.currentTimeMillis() + " Created retried command : " + o);
o.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(1000, TimeUnit.MILLISECONDS);
System.out.println(System.currentTimeMillis() + " ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
}
/**
*********************** THREAD-ISOLATED Execution Hook Tests **************************************
*/
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: NO
* Execution Result: SUCCESS
*/
@Test
public void testExecutionHookThreadSuccess() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(1, 0, 1));
assertTrue(hook.executionEventsMatch(1, 0, 1));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onExecutionEmit - !onRunSuccess - !onComplete - onEmit - onExecutionSuccess - onThreadComplete - onSuccess - ", hook.executionSequence.toString());
}
});
}
@Test
public void testExecutionHookEarlyUnsubscribe() {
System.out.println("Running command.observe(), awaiting terminal state of Observable, then running assertions...");
final CountDownLatch latch = new CountDownLatch(1);
TestHystrixCommand<Integer> command = getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 1000);
Observable<Integer> o = command.observe();
Subscription s = o.
doOnUnsubscribe(new Action0() {
@Override
public void call() {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnUnsubscribe");
latch.countDown();
}
}).
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 i) {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : OnNext : " + i);
}
});
try {
Thread.sleep(15);
s.unsubscribe();
latch.await(3, TimeUnit.SECONDS);
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 0, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onUnsubscribe - onThreadComplete - ", hook.executionSequence.toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: NO
* Execution Result: synchronous HystrixBadRequestException
*/
@Test
public void testExecutionHookThreadBadRequestException() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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 - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: NO
* Execution Result: synchronous HystrixRuntimeException
* Fallback: UnsupportedOperationException
*/
@Test
public void testExecutionHookThreadExceptionNoFallback() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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 - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: NO
* Execution Result: synchronous HystrixRuntimeException
* Fallback: SUCCESS
*/
@Test
public void testExecutionHookThreadExceptionSuccessfulFallback() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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 - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: NO
* Execution Result: synchronous HystrixRuntimeException
* Fallback: HystrixRuntimeException
*/
@Test
public void testExecutionHookThreadExceptionUnsuccessfulFallback() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 0, AbstractTestHystrixCommand.FallbackResult.FAILURE);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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 - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: YES
* Execution Result: SUCCESS (but timeout prior)
* Fallback: UnsupportedOperationException
*/
@Test
public void testExecutionHookThreadTimeoutNoFallbackRunSuccess() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 200);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 1, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals(TimeoutException.class, hook.getCommandException().getClass());
assertNull(hook.getFallbackException());
System.out.println("RequestLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: YES
* Execution Result: SUCCESS (but timeout prior)
* Fallback: SUCCESS
*/
@Test
public void testExecutionHookThreadTimeoutSuccessfulFallbackRunSuccess() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 200);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(1, 0, 1));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(1, 0, 1));
System.out.println("RequestLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: YES
* Execution Result: SUCCESS (but timeout prior)
* Fallback: synchronous HystrixRuntimeException
*/
@Test
public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunSuccess() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, 200);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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(TimeoutException.class, hook.getCommandException().getClass());
assertEquals(RuntimeException.class, hook.getFallbackException().getClass());
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: YES
* Execution Result: HystrixRuntimeException (but timeout prior)
* Fallback: UnsupportedOperationException
*/
@Test
public void testExecutionHookThreadTimeoutNoFallbackRunFailure() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 200);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 1, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals(TimeoutException.class, hook.getCommandException().getClass());
assertNull(hook.getFallbackException());
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: YES
* Execution Result: HystrixRuntimeException (but timeout prior)
* Fallback: SUCCESS
*/
@Test
public void testExecutionHookThreadTimeoutSuccessfulFallbackRunFailure() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, 200);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertTrue(hook.commandEmissionsMatch(1, 0, 1));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(1, 0, 1));
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : NO
* Thread Pool Queue full?: NO
* Timeout: YES
* Execution Result: HystrixRuntimeException (but timeout prior)
* Fallback: synchronous HystrixRuntimeException
*/
@Test
public void testExecutionHookThreadTimeoutUnsuccessfulFallbackRunFailure() {
assertHooksOnFailure(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.FAILURE, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, 200);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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(TimeoutException.class, hook.getCommandException().getClass());
assertEquals(RuntimeException.class, hook.getFallbackException().getClass());
assertEquals("onStart - onThreadStart - !onRunStart - onExecutionStart - onThreadComplete - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : YES
* Thread Pool Queue full?: YES
* Fallback: UnsupportedOperationException
*/
@Test
public void testExecutionHookThreadPoolQueueFullNoFallback() {
assertHooksOnFailFast(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker();
HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1);
try {
// fill the pool
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600).observe();
// fill the queue
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600).observe();
} catch (Exception e) {
// ignore
}
return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 1, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals(RejectedExecutionException.class, hook.getCommandException().getClass());
assertNull(hook.getFallbackException());
assertEquals("onStart - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : YES
* Thread Pool Queue full?: YES
* Fallback: SUCCESS
*/
@Test
public void testExecutionHookThreadPoolQueueFullSuccessfulFallback() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker();
HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1);
try {
// fill the pool
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600).observe();
// fill the queue
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600).observe();
} catch (Exception e) {
// ignore
}
return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(1, 0, 1));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(1, 0, 1));
assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : YES
* Thread Pool Queue full?: YES
* Fallback: synchronous HystrixRuntimeException
*/
@Test
public void testExecutionHookThreadPoolQueueFullUnsuccessfulFallback() {
assertHooksOnFailFast(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker();
HystrixThreadPool pool = new SingleThreadedPoolWithQueue(1);
try {
// fill the pool
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600).observe();
// fill the queue
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600).observe();
} catch (Exception e) {
// ignore
}
return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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(RejectedExecutionException.class, hook.getCommandException().getClass());
assertEquals(RuntimeException.class, hook.getFallbackException().getClass());
assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : YES
* Thread Pool Queue full?: N/A
* Fallback: UnsupportedOperationException
*/
@Test
public void testExecutionHookThreadPoolFullNoFallback() {
assertHooksOnFailFast(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker();
HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue();
try {
// fill the pool
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600).observe();
} catch (Exception e) {
// ignore
}
return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, circuitBreaker, pool, 600);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 1, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals(RejectedExecutionException.class, hook.getCommandException().getClass());
assertNull(hook.getFallbackException());
assertEquals("onStart - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : YES
* Thread Pool Queue full?: N/A
* Fallback: SUCCESS
*/
@Test
public void testExecutionHookThreadPoolFullSuccessfulFallback() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker();
HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue();
try {
// fill the pool
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600).observe();
} catch (Exception e) {
// ignore
}
return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.SUCCESS, circuitBreaker, pool, 600);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(1, 0, 1));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Thread/semaphore: THREAD
* Thread Pool full? : YES
* Thread Pool Queue full?: N/A
* Fallback: synchronous HystrixRuntimeException
*/
@Test
public void testExecutionHookThreadPoolFullUnsuccessfulFallback() {
assertHooksOnFailFast(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker();
HystrixThreadPool pool = new SingleThreadedPoolWithNoQueue();
try {
// fill the pool
getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600).observe();
} catch (Exception e) {
// ignore
}
return getLatentCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 500, AbstractTestHystrixCommand.FallbackResult.FAILURE, circuitBreaker, pool, 600);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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(RejectedExecutionException.class, hook.getCommandException().getClass());
assertEquals(RuntimeException.class, hook.getFallbackException().getClass());
assertEquals("onStart - onFallbackStart - onFallbackError - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : YES
* Thread/semaphore: THREAD
* Fallback: UnsupportedOperationException
*/
@Test
public void testExecutionHookThreadShortCircuitNoFallback() {
assertHooksOnFailFast(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 1, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals(RuntimeException.class, hook.getCommandException().getClass());
assertNull(hook.getFallbackException());
assertEquals("onStart - onError - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : YES
* Thread/semaphore: THREAD
* Fallback: SUCCESS
*/
@Test
public void testExecutionHookThreadShortCircuitSuccessfulFallback() {
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.SUCCESS);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(1, 0, 1));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(1, 0, 1));
assertEquals("onStart - onFallbackStart - onFallbackEmit - !onFallbackSuccess - !onComplete - onEmit - onFallbackSuccess - onSuccess - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : YES
* Thread/semaphore: THREAD
* Fallback: synchronous HystrixRuntimeException
*/
@Test
public void testExecutionHookThreadShortCircuitUnsuccessfulFallback() {
assertHooksOnFailFast(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
HystrixCircuitBreakerTest.TestCircuitBreaker circuitBreaker = new HystrixCircuitBreakerTest.TestCircuitBreaker().setForceShortCircuit(true);
return getCircuitOpenCommand(ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.FallbackResult.FAILURE);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<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 - ", hook.executionSequence.toString());
}
});
}
/**
* Short-circuit? : NO
* Request-cache? : YES
*/
@Test
public void testExecutionHookResponseFromCache() {
final HystrixCommandKey key = HystrixCommandKey.Factory.asKey("Hook-Cache");
getCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 0, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, 100, AbstractTestHystrixCommand.CacheEnabled.YES, 42, 10, 10).observe();
assertHooksOnSuccess(
new Func0<TestHystrixCommand<Integer>>() {
@Override
public TestHystrixCommand<Integer> call() {
return getCommand(key, ExecutionIsolationStrategy.THREAD, AbstractTestHystrixCommand.ExecutionResult.SUCCESS, 0, AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED, 0, new HystrixCircuitBreakerTest.TestCircuitBreaker(), null, 100, AbstractTestHystrixCommand.CacheEnabled.YES, 42, 10, 10);
}
},
new Action1<TestHystrixCommand<Integer>>() {
@Override
public void call(TestHystrixCommand<Integer> command) {
TestableExecutionHook hook = command.getBuilder().executionHook;
assertTrue(hook.commandEmissionsMatch(0, 0, 0));
assertTrue(hook.executionEventsMatch(0, 0, 0));
assertTrue(hook.fallbackEventsMatch(0, 0, 0));
assertEquals("onCacheHit - ", hook.executionSequence.toString());
}
});
}
/**
*********************** END THREAD-ISOLATED Execution Hook Tests **************************************
*/
/* ******************************************************************************** */
/* ******************************************************************************** */
/* private HystrixCommand class implementations for unit testing */
/* ******************************************************************************** */
/* ******************************************************************************** */
static AtomicInteger uniqueNameCounter = new AtomicInteger(1);
@Override
TestHystrixCommand<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, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) {
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("Flexible-" + uniqueNameCounter.getAndIncrement());
return FlexibleTestHystrixCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled);
}
@Override
TestHystrixCommand<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, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) {
return FlexibleTestHystrixCommand.from(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled);
}
private static class FlexibleTestHystrixCommand {
public static Integer EXECUTE_VALUE = 1;
public static Integer FALLBACK_VALUE = 11;
public static AbstractFlexibleTestHystrixCommand 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, TryableSemaphore executionSemaphore, TryableSemaphore fallbackSemaphore, boolean circuitBreakerDisabled) {
if (fallbackResult.equals(AbstractTestHystrixCommand.FallbackResult.UNIMPLEMENTED)) {
return new FlexibleTestHystrixCommandNoFallback(commandKey, isolationStrategy, executionResult, executionLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled);
} else {
return new FlexibleTestHystrixCommandWithFallback(commandKey, isolationStrategy, executionResult, executionLatency, fallbackResult, fallbackLatency, circuitBreaker, threadPool, timeout, cacheEnabled, value, executionSemaphore, fallbackSemaphore, circuitBreakerDisabled);
}
}
}
private static class AbstractFlexibleTestHystrixCommand extends TestHystrixCommand<Integer> {
protected final AbstractTestHystrixCommand.ExecutionResult executionResult;
protected final int executionLatency;
protected final CacheEnabled cacheEnabled;
protected final Object value;
AbstractFlexibleTestHystrixCommand(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 Integer run() throws Exception {
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " starting the run() method");
addLatency(executionLatency);
if (executionResult == AbstractTestHystrixCommand.ExecutionResult.SUCCESS) {
return FlexibleTestHystrixCommand.EXECUTE_VALUE;
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.FAILURE) {
throw new RuntimeException("Execution Failure for TestHystrixCommand");
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.NOT_WRAPPED_FAILURE) {
throw new NotWrappedByHystrixTestRuntimeException();
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.HYSTRIX_FAILURE) {
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, AbstractFlexibleTestHystrixCommand.class, "Execution Hystrix Failure for TestHystrixCommand", new RuntimeException("Execution Failure for TestHystrixCommand"), new RuntimeException("Fallback Failure for TestHystrixCommand"));
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.RECOVERABLE_ERROR) {
throw new java.lang.Error("Execution ERROR for TestHystrixCommand");
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.UNRECOVERABLE_ERROR) {
throw new StackOverflowError("Unrecoverable Error for TestHystrixCommand");
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST) {
throw new HystrixBadRequestException("Execution BadRequestException for TestHystrixCommand");
} else if (executionResult == AbstractTestHystrixCommand.ExecutionResult.BAD_REQUEST_NOT_WRAPPED) {
throw new HystrixBadRequestException("Execution BadRequestException for TestHystrixCommand", new NotWrappedByHystrixTestRuntimeException());
} else {
throw new RuntimeException("You passed in a executionResult enum that can't be represented in HystrixCommand: " + 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 FlexibleTestHystrixCommandWithFallback extends AbstractFlexibleTestHystrixCommand {
protected final AbstractTestHystrixCommand.FallbackResult fallbackResult;
protected final int fallbackLatency;
FlexibleTestHystrixCommandWithFallback(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 Integer getFallback() {
addLatency(fallbackLatency);
if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.SUCCESS) {
return FlexibleTestHystrixCommand.FALLBACK_VALUE;
} else if (fallbackResult == AbstractTestHystrixCommand.FallbackResult.FAILURE) {
throw new RuntimeException("Fallback Failure for TestHystrixCommand");
} else if (fallbackResult == FallbackResult.UNIMPLEMENTED) {
return super.getFallback();
} else {
throw new RuntimeException("You passed in a fallbackResult enum that can't be represented in HystrixCommand: " + fallbackResult);
}
}
}
private static class FlexibleTestHystrixCommandNoFallback extends AbstractFlexibleTestHystrixCommand {
FlexibleTestHystrixCommandNoFallback(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 TestHystrixCommand<Boolean> {
public SuccessfulTestCommand() {
this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter());
}
public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) {
super(testPropsBuilder().setCommandPropertiesDefaults(properties));
}
@Override
protected Boolean run() {
return true;
}
}
/**
* Successful execution - no fallback implementation.
*/
private static class DynamicOwnerTestCommand extends TestHystrixCommand<Boolean> {
public DynamicOwnerTestCommand(HystrixCommandGroupKey owner) {
super(testPropsBuilder().setOwner(owner));
}
@Override
protected Boolean run() {
System.out.println("successfully executed");
return true;
}
}
/**
* Successful execution - no fallback implementation.
*/
private static class DynamicOwnerAndKeyTestCommand extends TestHystrixCommand<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 Boolean run() {
System.out.println("successfully executed");
return true;
}
}
/**
* Failed execution with known exception (HystrixException) - no fallback implementation.
*/
private static class KnownFailureTestCommandWithoutFallback extends TestHystrixCommand<Boolean> {
private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
@Override
protected Boolean run() {
System.out.println("*** simulated failed execution *** ==> " + Thread.currentThread());
throw new RuntimeException("we failed with a simulated issue");
}
}
/**
* Failed execution - fallback implementation successfully returns value.
*/
private static class KnownFailureTestCommandWithFallback extends TestHystrixCommand<Boolean> {
public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, boolean fallbackEnabled) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackEnabled(fallbackEnabled)));
}
@Override
protected Boolean run() {
System.out.println("*** simulated failed execution ***");
throw new RuntimeException("we failed with a simulated issue");
}
@Override
protected Boolean getFallback() {
return false;
}
}
/**
* A Command implementation that supports caching.
*/
private static class SuccessfulCacheableCommand<T> extends TestHystrixCommand<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));
this.value = value;
this.cacheEnabled = cacheEnabled;
}
@Override
protected T run() {
executed = true;
System.out.println("successfully executed");
return value;
}
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 TestHystrixCommand<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 String run() {
executed = true;
System.out.println("successfully executed");
return value;
}
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 TestHystrixCommand<String> {
private final String value;
private final int duration;
private volatile boolean executed = false;
public SlowCacheableCommand(TestCircuitBreaker circuitBreaker, String value, int duration) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
this.value = value;
this.duration = duration;
}
@Override
protected String run() {
executed = true;
try {
Thread.sleep(duration);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("successfully executed");
return value;
}
@Override
public String getCacheKey() {
return value;
}
}
/**
* This has a ThreadPool that has a single thread and queueSize of 1.
*/
private static class TestCommandRejection extends TestHystrixCommand<Boolean> {
private final static int FALLBACK_NOT_IMPLEMENTED = 1;
private final static int FALLBACK_SUCCESS = 2;
private final static int FALLBACK_FAILURE = 3;
private final int fallbackBehavior;
private final int sleepTime;
private TestCommandRejection(HystrixCommandKey key, TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int sleepTime, int timeout, int fallbackBehavior) {
super(testPropsBuilder()
.setCommandKey(key)
.setThreadPool(threadPool)
.setCircuitBreaker(circuitBreaker)
.setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(timeout)));
this.fallbackBehavior = fallbackBehavior;
this.sleepTime = sleepTime;
}
@Override
protected Boolean run() {
System.out.println(">>> TestCommandRejection running");
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
@Override
protected Boolean getFallback() {
if (fallbackBehavior == FALLBACK_SUCCESS) {
return false;
} else if (fallbackBehavior == FALLBACK_FAILURE) {
throw new RuntimeException("failed on fallback");
} else {
// FALLBACK_NOT_IMPLEMENTED
return super.getFallback();
}
}
}
/**
* Command that receives a custom thread-pool, sleepTime, timeout
*/
private static class CommandWithCustomThreadPool extends TestHystrixCommand<Boolean> {
public boolean didExecute = false;
private final int sleepTime;
private CommandWithCustomThreadPool(TestCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, int sleepTime, HystrixCommandProperties.Setter properties) {
super(testPropsBuilder().setThreadPool(threadPool).setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics).setCommandPropertiesDefaults(properties));
this.sleepTime = sleepTime;
}
@Override
protected Boolean run() {
System.out.println("**** Executing CommandWithCustomThreadPool. Execution => " + sleepTime);
didExecute = true;
try {
Thread.sleep(sleepTime);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
/**
* The run() will fail and getFallback() take a long time.
*/
private static class TestSemaphoreCommandWithSlowFallback extends TestHystrixCommand<Boolean> {
private final long fallbackSleep;
private TestSemaphoreCommandWithSlowFallback(TestCircuitBreaker circuitBreaker, int fallbackSemaphoreExecutionCount, long fallbackSleep) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withFallbackIsolationSemaphoreMaxConcurrentRequests(fallbackSemaphoreExecutionCount).withExecutionIsolationThreadInterruptOnTimeout(false)));
this.fallbackSleep = fallbackSleep;
}
@Override
protected Boolean run() {
throw new RuntimeException("run fails");
}
@Override
protected Boolean getFallback() {
try {
Thread.sleep(fallbackSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
private static class NoRequestCacheTimeoutWithoutFallback extends TestHystrixCommand<Boolean> {
public NoRequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(200).withCircuitBreakerEnabled(false)));
// we want it to timeout
}
@Override
protected Boolean run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(">>>> Sleep Interrupted: " + e.getMessage());
// e.printStackTrace();
}
return true;
}
@Override
public String getCacheKey() {
return null;
}
}
/**
* The run() will take time. Configurable fallback implementation.
*/
private static class TestSemaphoreCommand extends TestHystrixCommand<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 TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep, int resultBehavior, int fallbackBehavior) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
.withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount)));
this.executionSleep = executionSleep;
this.resultBehavior = resultBehavior;
this.fallbackBehavior = fallbackBehavior;
}
private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, long executionSleep, int resultBehavior, int fallbackBehavior) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))
.setExecutionSemaphore(semaphore));
this.executionSleep = executionSleep;
this.resultBehavior = resultBehavior;
this.fallbackBehavior = fallbackBehavior;
}
@Override
protected Boolean run() {
try {
Thread.sleep(executionSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (resultBehavior == RESULT_SUCCESS) {
return true;
} else if (resultBehavior == RESULT_FAILURE) {
throw new RuntimeException("TestSemaphoreCommand failure");
} else if (resultBehavior == RESULT_BAD_REQUEST_EXCEPTION) {
throw new HystrixBadRequestException("TestSemaphoreCommand BadRequestException");
} else {
throw new IllegalStateException("Didn't use a proper enum for result behavior");
}
}
@Override
protected Boolean getFallback() {
if (fallbackBehavior == FALLBACK_SUCCESS) {
return false;
} else if (fallbackBehavior == FALLBACK_FAILURE) {
throw new RuntimeException("fallback failure");
} else { //FALLBACK_NOT_IMPLEMENTED
return super.getFallback();
}
}
}
/**
* 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 TestHystrixCommand<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 java.util.concurrent.CountDownLatch#countDown()} immediately
* upon running
* @param waitLatch
* this command calls {@link java.util.concurrent.CountDownLatch#await()} once it starts
* to run. The caller can use the latch to signal the command to finish
*/
private LatchedSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore semaphore, CountDownLatch startLatch, CountDownLatch waitLatch) {
this("Latched", circuitBreaker, semaphore, startLatch, waitLatch);
}
private LatchedSemaphoreCommand(String commandName, TestCircuitBreaker circuitBreaker, TryableSemaphore 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 Boolean run() {
// signals caller that run has started
this.startLatch.countDown();
try {
// waits for caller to countDown latch
this.waitLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
}
/**
* The run() will take time. Contains fallback.
*/
private static class TestSemaphoreCommandWithFallback extends TestHystrixCommand<Boolean> {
private final long executionSleep;
private final 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 = fallback;
}
@Override
protected Boolean run() {
try {
Thread.sleep(executionSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
@Override
protected Boolean getFallback() {
return fallback;
}
}
private static class RequestCacheNullPointerExceptionCase extends TestHystrixCommand<Boolean> {
public RequestCacheNullPointerExceptionCase(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(200)));
// we want it to timeout
}
@Override
protected Boolean run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
@Override
protected Boolean getFallback() {
return false;
}
@Override
public String getCacheKey() {
return "A";
}
}
private static class RequestCacheTimeoutWithoutFallback extends TestHystrixCommand<Boolean> {
public RequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionTimeoutInMilliseconds(200).withCircuitBreakerEnabled(false)));
// we want it to timeout
}
@Override
protected Boolean run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(">>>> Sleep Interrupted: " + e.getMessage());
// e.printStackTrace();
}
return true;
}
@Override
public String getCacheKey() {
return "A";
}
}
private static class RequestCacheThreadRejectionWithoutFallback extends TestHystrixCommand<Boolean> {
final CountDownLatch completionLatch;
public RequestCacheThreadRejectionWithoutFallback(TestCircuitBreaker circuitBreaker, CountDownLatch completionLatch) {
super(testPropsBuilder()
.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 Boolean run() {
try {
if (completionLatch.await(1000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("timed out waiting on completionLatch");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return true;
}
@Override
public String getCacheKey() {
return "A";
}
}
private static class BadRequestCommand extends TestHystrixCommand<Boolean> {
public BadRequestCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationType) {
super(testPropsBuilder()
.setCircuitBreaker(circuitBreaker)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationType))
.setMetrics(circuitBreaker.metrics));
}
@Override
protected Boolean run() {
throw new HystrixBadRequestException("Message to developer that they passed in bad data or something like that.");
}
@Override
protected Boolean getFallback() {
return false;
}
@Override
protected String getCacheKey() {
return "one";
}
}
private static class AsyncCacheableCommand extends HystrixCommand<Boolean> {
private final String arg;
private final AtomicBoolean cancelled = new AtomicBoolean(false);
public AsyncCacheableCommand(String arg) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ASYNC")));
this.arg = arg;
}
@Override
protected Boolean run() {
try {
Thread.sleep(500);
return true;
} catch (InterruptedException ex) {
cancelled.set(true);
throw new RuntimeException(ex);
}
}
@Override
protected String getCacheKey() {
return arg;
}
public boolean isCancelled() {
return cancelled.get();
}
}
private static class BusinessException extends Exception {
public BusinessException(String msg) {
super(msg);
}
}
private static class ExceptionToBadRequestByExecutionHookCommand extends TestHystrixCommand<Boolean> {
public ExceptionToBadRequestByExecutionHookCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationType) {
super(testPropsBuilder()
.setCircuitBreaker(circuitBreaker)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationType))
.setMetrics(circuitBreaker.metrics)
.setExecutionHook(new TestableExecutionHook(){
@Override
public <T> Exception onRunError(HystrixInvokable<T> commandInstance, Exception e) {
super.onRunError(commandInstance, e);
return new HystrixBadRequestException("autoconverted exception", e);
}
}));
}
@Override
protected Boolean run() throws BusinessException {
throw new BusinessException("invalid input by the user");
}
@Override
protected String getCacheKey() {
return "nein";
}
}
private static class CommandWithCheckedException extends TestHystrixCommand<Boolean> {
public CommandWithCheckedException(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder()
.setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
@Override
protected Boolean run() throws Exception {
throw new IOException("simulated checked exception message");
}
}
private static class CommandWithNotWrappedByHystrixException extends TestHystrixCommand<Boolean> {
public CommandWithNotWrappedByHystrixException(TestCircuitBreaker circuitBreaker) {
super(testPropsBuilder()
.setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics));
}
@Override
protected Boolean run() throws Exception {
throw new NotWrappedByHystrixTestException();
}
}
private static class InterruptibleCommand extends TestHystrixCommand<Boolean> {
public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt, boolean shouldInterruptOnCancel, int timeoutInMillis) {
super(testPropsBuilder()
.setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)
.setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()
.withExecutionIsolationThreadInterruptOnFutureCancel(shouldInterruptOnCancel)
.withExecutionIsolationThreadInterruptOnTimeout(shouldInterrupt)
.withExecutionTimeoutInMilliseconds(timeoutInMillis)));
}
public InterruptibleCommand(TestCircuitBreaker circuitBreaker, boolean shouldInterrupt) {
this(circuitBreaker, shouldInterrupt, false, 100);
}
private volatile boolean hasBeenInterrupted;
public boolean hasBeenInterrupted() {
return hasBeenInterrupted;
}
@Override
protected Boolean run() throws Exception {
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
System.out.println("Interrupted!");
e.printStackTrace();
hasBeenInterrupted = true;
}
return hasBeenInterrupted;
}
}
private static class CommandWithDisabledTimeout extends TestHystrixCommand<Boolean> {
private final int latency;
public CommandWithDisabledTimeout(int timeout, int latency) {
super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()
.withExecutionTimeoutInMilliseconds(timeout)
.withExecutionTimeoutEnabled(false)));
this.latency = latency;
}
@Override
protected Boolean run() throws Exception {
try {
Thread.sleep(latency);
return true;
} catch (InterruptedException ex) {
return false;
}
}
@Override
protected Boolean getFallback() {
return false;
}
}
}