diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableBase.java b/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java similarity index 57% rename from hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableBase.java rename to hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java index 8a26a39e1..57fe2afc3 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableBase.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java @@ -16,14 +16,13 @@ package com.netflix.hystrix; import java.lang.ref.Reference; +import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -32,11 +31,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import rx.Notification; import rx.Observable; +import rx.Observer; +import rx.Observable.OnSubscribe; +import rx.Observable.Operator; import rx.Scheduler; import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; import rx.schedulers.Schedulers; import rx.subjects.ReplaySubject; +import rx.subscriptions.CompositeSubscription; import com.netflix.hystrix.HystrixCircuitBreaker.NoOpCircuitBreaker; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; @@ -46,6 +53,8 @@ import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; +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.eventnotifier.HystrixEventNotifier; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; @@ -53,12 +62,13 @@ import com.netflix.hystrix.strategy.properties.HystrixPropertiesFactory; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixTimer; import com.netflix.hystrix.util.HystrixTimer.TimerListener; -/* package */abstract class HystrixExecutableBase implements HystrixExecutable, HystrixExecutableInfo { +/* package */abstract class AbstractCommand implements HystrixExecutableInfo, HystrixObservable { // TODO make this package private - private static final Logger logger = LoggerFactory.getLogger(HystrixExecutableBase.class); + private static final Logger logger = LoggerFactory.getLogger(AbstractCommand.class); protected final HystrixCircuitBreaker circuitBreaker; protected final HystrixThreadPool threadPool; protected final HystrixThreadPoolKey threadPoolKey; @@ -132,7 +142,7 @@ protected static enum TimedOutStatus { return name; } - protected HystrixExecutableBase(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, + protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { @@ -280,306 +290,701 @@ protected HystrixExecutableBase(HystrixCommandGroupKey group, HystrixCommandKey } /** - * Used for synchronous execution of command. + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution of the command the same as {@link #queue()} and {@link #execute()}. + *

+ * A lazy {@link Observable} can be obtained from {@link #toObservable()}. + *

+ * See https://github.com/Netflix/RxJava/wiki for more information. * - * @return R - * Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @return {@code Observable} that executes and calls back with the result of command execution or a fallback if the command fails for any reason. * @throws HystrixRuntimeException - * if a failure occurs and a fallback cannot be retrieved + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
* @throws HystrixBadRequestException - * if invalid arguments or state were used representing a user failure, not a system failure + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure * @throws IllegalStateException * if invoked more than once */ - public R execute() { - try { - return queue().get(); - } catch (Exception e) { - throw decomposeException(e); - } + final public Observable observe() { + // us a ReplaySubject to buffer the eagerly subscribed-to Observable + ReplaySubject subject = ReplaySubject.create(); + // eagerly kick off subscription + toObservable().subscribe(subject); + // return the subject that can be subscribed to later while the execution has already started + return subject; } /** - * Used for asynchronous execution of command. + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. *

- * This will queue up the command on the thread pool and return an {@link Future} to get the result once it completes. + * This lazily starts execution of the command once the {@link Observable} is subscribed to. *

- * NOTE: If configured to not run in a separate thread, this will have the same effect as {@link #execute()} and will block. + * An eager {@link Observable} can be obtained from {@link #observe()}. *

- * We don't throw an exception but just flip to synchronous execution so code doesn't need to change in order to switch a command from running on a separate thread to the calling thread. + * See https://github.com/ReactiveX/RxJava/wiki for more information. * - * @return {@code Future} Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @return {@code Observable} that executes and calls back with the result of command execution or a fallback if the command fails for any reason. * @throws HystrixRuntimeException * if a fallback does not exist *

*

    - *
  • via {@code Future.get()} in {@link ExecutionException#getCause()} if a failure occurs
  • + *
  • via {@code Observer#onError} if a failure occurs
  • *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • *
* @throws HystrixBadRequestException - * via {@code Future.get()} in {@link ExecutionException#getCause()} if invalid arguments or state were used representing a user failure, not a system failure + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure * @throws IllegalStateException * if invoked more than once */ - public Future queue() { - /* - * --- Schedulers.immediate() - * - * We use the 'immediate' schedule since Future.get() is blocking so we don't want to bother doing the callback to the Future on a separate thread - * as we don't need to separate the Hystrix thread from user threads since they are already providing it via the Future.get() call. - * - * --- performAsyncTimeout: false - * - * We pass 'false' to tell the Observable we will block on it so it doesn't schedule an async timeout. - * - * This optimizes for using the calling thread to do the timeout rather than scheduling another thread. - * - * In a tight-loop of executing commands this optimization saves a few microseconds per execution. - * It also just makes no sense to use a separate thread to timeout the command when the calling thread - * is going to sit waiting on it. - */ - final ObservableCommand o = toObservable(Schedulers.immediate(), false); - final Future f = o.toBlocking().toFuture(); + final public Observable toObservable() { + return toObservable(true); + } - /* special handling of error states that throw immediately */ - if (f.isDone()) { - try { - f.get(); - return f; - } catch (Exception e) { - RuntimeException re = decomposeException(e); - if (re instanceof HystrixBadRequestException) { - return f; - } else if (re instanceof HystrixRuntimeException) { - HystrixRuntimeException hre = (HystrixRuntimeException) re; - if (hre.getFailureType() == FailureType.COMMAND_EXCEPTION || hre.getFailureType() == FailureType.TIMEOUT) { - // we don't throw these types from queue() only from queue().get() as they are execution errors - return f; + protected abstract Observable getExecutionObservable(); + + protected abstract Observable getFallbackObservable(); + + protected ObservableCommand toObservable(final boolean performAsyncTimeout) { + /* this is a stateful object so can only be used once */ + if (!started.compareAndSet(false, true)) { + throw new IllegalStateException("This instance can only be executed once. Please instantiate a new instance."); + } + + /* try from cache first */ + if (isRequestCachingEnabled()) { + Observable fromCache = requestCache.get(getCacheKey()); + if (fromCache != null) { + /* mark that we received this response from cache */ + metrics.markResponseFromCache(); + return new CachedObservableResponse((CachedObservableOriginal) fromCache, this); + } + } + + final HystrixInvokable _this = this; + final AtomicReference endCurrentThreadExecutingCommand = new AtomicReference(); // don't like how this is being done + + // create an Observable that will lazily execute when subscribed to + Observable o = Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber observer) { + // async record keeping + recordExecutedCommand(); + + // mark that we're starting execution on the ExecutionHook + executionHook.onStart(_this); + + /* determine if we're allowed to execute */ + if (circuitBreaker.allowRequest()) { + final TryableSemaphore executionSemaphore = getExecutionSemaphore(); + // acquire a permit + if (executionSemaphore.tryAcquire()) { + try { + /* used to track userThreadExecutionTime */ + invocationStartTime = System.currentTimeMillis(); + + // store the command that is being run + endCurrentThreadExecutingCommand.set(Hystrix.startCurrentThreadExecutingCommand(getCommandKey())); + + getRunObservableDecoratedForMetricsAndErrorHandling(performAsyncTimeout) + .doOnTerminate(new Action0() { + + @Override + public void call() { + // release the semaphore + // this is done here instead of below so that the acquire/release happens where it is guaranteed + // and not affected by the conditional circuit-breaker checks, timeouts, etc + executionSemaphore.release(); + + } + }).unsafeSubscribe(observer); + } catch (RuntimeException e) { + observer.onError(e); + } } else { - // these are errors we throw from queue() as they as rejection type errors - throw hre; + metrics.markSemaphoreRejection(); + logger.debug("HystrixCommand Execution Rejection by Semaphore."); // debug only since we're throwing the exception and someone higher will do something with it + // retrieve a fallback or throw an exception if no fallback available + getFallbackOrThrowException(HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION, "could not acquire a semaphore for execution").unsafeSubscribe(observer); } } else { - throw re; + // record that we are returning a short-circuited fallback + metrics.markShortCircuited(); + // short-circuit and go directly to fallback (or throw an exception if no fallback implemented) + try { + getFallbackOrThrowException(HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT, "short-circuited").unsafeSubscribe(observer); + } catch (Exception e) { + observer.onError(e); + } } } - } + }); - return new Future() { + // error handling at very end (this means fallback didn't exist or failed) + o = o.onErrorResumeNext(new Func1>() { @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return f.cancel(mayInterruptIfRunning); + public Observable call(Throwable t) { + // count that we are throwing an exception and re-throw it + metrics.markExceptionThrown(); + return Observable.error(t); } + }); + + // any final cleanup needed + o = o.doOnTerminate(new Action0() { + @Override - public boolean isCancelled() { - return f.isCancelled(); + public void call() { + Reference tl = timeoutTimer.get(); + if (tl != null) { + tl.clear(); + } + + try { + // if we executed we will record the execution time + if (invocationStartTime > 0 && !isResponseRejected()) { + /* execution time (must occur before terminal state otherwise a race condition can occur if requested by client) */ + recordTotalExecutionTime(invocationStartTime); + } + + // pop the command that is being run + if (endCurrentThreadExecutingCommand.get() != null) { + endCurrentThreadExecutingCommand.get().call(); + } + } finally { + metrics.decrementConcurrentExecutionCount(); + // record that we're completed + isExecutionComplete.set(true); + } } - @Override - public boolean isDone() { - return f.isDone(); + }); + + // put in cache + if (isRequestCachingEnabled()) { + // wrap it for caching + o = new CachedObservableOriginal(o.cache(), this); + Observable fromCache = requestCache.putIfAbsent(getCacheKey(), o); + if (fromCache != null) { + // another thread beat us so we'll use the cached value instead + o = new CachedObservableResponse((CachedObservableOriginal) fromCache, this); } + // we just created an ObservableCommand so we cast and return it + return (ObservableCommand) o; + } else { + // no request caching so a simple wrapper just to pass 'this' along with the Observable + return new ObservableCommand(o, this); + } + } + + /** + * This decorate "Hystrix" functionality around the run() Observable. + * + * @return R + */ + private Observable getRunObservableDecoratedForMetricsAndErrorHandling(final boolean performAsyncTimeout) { + final AbstractCommand _self = this; + // allow tracking how many concurrent threads are executing + metrics.incrementConcurrentExecutionCount(); + + final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread(); + + Observable run = null; + if (properties.executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD)) { + // mark that we are executing in a thread (even if we end up being rejected we still were a THREAD execution and not SEMAPHORE) + isExecutedInThread.set(true); + + run = Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + executionHook.onRunStart(_self); + executionHook.onThreadStart(_self); + if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) { + // the command timed out in the wrapping thread so we will return immediately + // and not increment any of the counters below or other such logic + s.onError(new RuntimeException("timed out before executing run()")); + } else { + // not timed out so execute + try { + final Action0 endCurrentThread = Hystrix.startCurrentThreadExecutingCommand(getCommandKey()); + getExecutionObservable().doOnTerminate(new Action0() { + + @Override + public void call() { + // TODO is this actually the end of the thread? + executionHook.onThreadComplete(_self); + endCurrentThread.call(); + } + }).unsafeSubscribe(s); + } catch (Throwable t) { + // the run() method is a user provided implementation so can throw instead of using Observable.onError + // so we catch it here and turn it into Observable.error + Observable. error(t).unsafeSubscribe(s); + } + } + } + + }).subscribeOn(threadPool.getScheduler()); + } else { + // semaphore isolated + executionHook.onRunStart(_self); + try { + run = getExecutionObservable(); + } catch (Throwable t) { + // the run() method is a user provided implementation so can throw instead of using Observable.onError + // so we catch it here and turn it into Observable.error + run = Observable.error(t); + } + } + + run = run.doOnEach(new Action1>() { @Override - public R get() throws InterruptedException, ExecutionException { - return performBlockingGetWithTimeout(o, f); + public void call(Notification n) { + setRequestContextIfNeeded(currentRequestContext); } - /** - * --- Non-Blocking Timeout (performAsyncTimeout:true) --- - * - * When 'toObservable' is done with non-blocking timeout then timeout functionality is provided - * by a separate HystrixTimer thread that will "tick" and cancel the underlying async Future inside the Observable. - * - * This method allows stealing that responsibility and letting the thread that's going to block anyways - * do the work to reduce pressure on the HystrixTimer. - * - * Blocking via queue().get() on a non-blocking timeout will work it's just less efficient - * as it involves an extra thread and cancels the scheduled action that does the timeout. - * - * --- Blocking Timeout (performAsyncTimeout:false) --- - * - * When blocking timeout is assumed (default behavior for execute/queue flows) then the async - * timeout will not have been scheduled and this will wait in a blocking manner and if a timeout occurs - * trigger the timeout logic that comes from inside the Observable/Observer. - * - * - * --- Examples - * - * Stack for timeout with performAsyncTimeout=false (note the calling thread via get): - * - * at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.tick(HystrixCommand.java:788) - * at com.netflix.hystrix.HystrixCommand$1.performBlockingGetWithTimeout(HystrixCommand.java:536) - * at com.netflix.hystrix.HystrixCommand$1.get(HystrixCommand.java:484) - * at com.netflix.hystrix.HystrixCommand.execute(HystrixCommand.java:413) - * - * - * Stack for timeout with performAsyncTimeout=true (note the HystrixTimer involved): - * - * at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.tick(HystrixCommand.java:799) - * at com.netflix.hystrix.util.HystrixTimer$1.run(HystrixTimer.java:101) - * - * - * - * @param o - * @param f - * @throws InterruptedException - * @throws ExecutionException - */ - protected R performBlockingGetWithTimeout(final ObservableCommand o, final Future f) throws InterruptedException, ExecutionException { - // shortcut if already done - if (f.isDone()) { - return f.get(); + }).lift(new HystrixObservableTimeoutOperator(_self, performAsyncTimeout)).map(new Func1() { + + boolean once = false; + + @Override + public R call(R t1) { + System.out.println("map " + t1); + if (!once) { + // report success + executionResult = executionResult.addEvents(HystrixEventType.SUCCESS); + once = true; } - // it's still working so proceed with blocking/timeout logic - HystrixExecutableBase originalCommand = o.getCommand(); - /** - * One thread will get the timeoutTimer if it's set and clear it then do blocking timeout. - *

- * If non-blocking timeout was scheduled this will unschedule it. If it wasn't scheduled it is basically - * a no-op but fits the same interface so blocking and non-blocking flows both work the same. - *

- * This "originalCommand" concept exists because of request caching. We only do the work and timeout logic - * on the original, not the cached responses. However, whichever the first thread is that comes in to block - * will be the one who performs the timeout logic. - *

- * If request caching is disabled then it will always go into here. - */ - if (originalCommand != null) { - Reference timer = originalCommand.timeoutTimer.getAndSet(null); - if (timer != null) { - /** - * If an async timeout was scheduled then: - * - * - We are going to clear the Reference so the scheduler threads stop managing the timeout - * and we'll take over instead since we're going to be blocking on it anyways. - * - * - Other threads (since we won the race) will just wait on the normal Future which will release - * once the Observable is marked as completed (which may come via timeout) - * - * If an async timeout was not scheduled: - * - * - We go through the same flow as we receive the same interfaces just the "timer.clear()" will do nothing. - */ - // get the timer we'll use to perform the timeout - TimerListener l = timer.get(); - // remove the timer from the scheduler - timer.clear(); - - // determine how long we should wait for, taking into account time since work started - // and when this thread came in to block. If invocationTime hasn't been set then assume time remaining is entire timeout value - // as this maybe a case of multiple threads trying to run this command in which one thread wins but even before the winning thread is able to set - // the starttime another thread going via the Cached command route gets here first. - long timeout = originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get(); - long timeRemaining = timeout; - long currTime = System.currentTimeMillis(); - if (originalCommand.invocationStartTime != -1) { - timeRemaining = (originalCommand.invocationStartTime - + originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get()) - - currTime; + return executionHook.onRunSuccess(_self, t1); + } - } - if (timeRemaining > 0) { - // we need to block with the calculated timeout - try { - return f.get(timeRemaining, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - if (l != null) { - // this perform the timeout logic on the Observable/Observer - l.tick(); - } - } + }).doOnCompleted(new Action0() { + + @Override + public void call() { + long duration = System.currentTimeMillis() - invocationStartTime; + metrics.addCommandExecutionTime(duration); + metrics.markSuccess(duration); + circuitBreaker.markSuccess(); + eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) duration, executionResult.events); + } + + }).onErrorResumeNext(new Func1>() { + + @Override + public Observable call(Throwable t) { + Exception e = getExceptionFromThrowable(t); + if (e instanceof RejectedExecutionException) { + /** + * Rejection handling + */ + metrics.markThreadPoolRejection(); + // use a fallback instead (or throw exception if not implemented) + return getFallbackOrThrowException(HystrixEventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION, "could not be queued for execution", e); + } else if (t instanceof HystrixObservableTimeoutOperator.HystrixTimeoutException) { + /** + * Timeout handling + * + * Callback is performed on the HystrixTimer thread. + */ + return getFallbackOrThrowException(HystrixEventType.TIMEOUT, FailureType.TIMEOUT, "timed-out", new TimeoutException()); + } else if (t instanceof HystrixBadRequestException) { + /** + * BadRequest handling + */ + try { + Exception decorated = executionHook.onRunError(_self, (Exception) t); + + if (decorated instanceof HystrixBadRequestException) { + t = (HystrixBadRequestException) decorated; } else { - // this means it should have already timed out so do so if it is not completed - if (!f.isDone()) { - if (l != null) { - l.tick(); - } - } + logger.warn("ExecutionHook.onRunError returned an exception that was not an instance of HystrixBadRequestException so will be ignored.", decorated); } + } catch (Exception hookException) { + logger.warn("Error calling ExecutionHook.onRunError", hookException); } + + /* + * HystrixBadRequestException is treated differently and allowed to propagate without any stats tracking or fallback logic + */ + return Observable.error(t); + } else { + /** + * All other error handling + */ + try { + e = executionHook.onRunError(_self, e); + } catch (Exception hookException) { + logger.warn("Error calling ExecutionHook.endRunFailure", hookException); + } + + logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", e); + + // report failure + metrics.markFailure(System.currentTimeMillis() - invocationStartTime); + // record the exception + executionResult = executionResult.setException(e); + return getFallbackOrThrowException(HystrixEventType.FAILURE, FailureType.COMMAND_EXCEPTION, "failed", e); } - // other threads will block until the "l.tick" occurs and releases the underlying Future. - return f.get(); + } + }).doOnEach(new Action1>() { + // setting again as the fallback could have lost the context + @Override + public void call(Notification n) { + setRequestContextIfNeeded(currentRequestContext); } + }).map(new Func1() { + @Override - public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return get(); + public R call(R t1) { + // allow transforming the results via the executionHook whether it came from success or fallback + return executionHook.onComplete(_self, t1); } - }; + }); + return run; } /** - * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. - *

- * This eagerly starts execution of the command the same as {@link #queue()} and {@link #execute()}. - * A lazy {@link Observable} can be obtained from {@link #toObservable()}. - *

- * Callback Scheduling + * Execute getFallback() within protection of a semaphore that limits number of concurrent executions. *

- *

    - *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#threadPoolForComputation()} for callbacks.
  • - *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • - *
- * Use {@link #toObservable(rx.Scheduler)} to schedule the callback differently. + * Fallback implementations shouldn't perform anything that can be blocking, but we protect against it anyways in case someone doesn't abide by the contract. *

- * See https://github.com/Netflix/RxJava/wiki for more information. + * If something in the getFallback() implementation is latent (such as a network call) then the semaphore will cause us to start rejecting requests rather than allowing potentially + * all threads to pile up and block. * - * @return {@code Observable} that executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @return K + * @throws UnsupportedOperationException + * if getFallback() not implemented + * @throws HystrixException + * if getFallback() fails (throws an Exception) or is rejected by the semaphore + */ + private Observable getFallbackWithProtection() { + final TryableSemaphore fallbackSemaphore = getFallbackSemaphore(); + + // acquire a permit + if (fallbackSemaphore.tryAcquire()) { + executionHook.onFallbackStart(this); + final AbstractCommand _cmd = this; + + Observable fallback = null; + try { + fallback = getFallbackObservable(); + } catch (Throwable t) { + // getFallback() is user provided and can throw so we catch it and turn it into Observable.error + fallback = Observable.error(t); + } + + return fallback.map(new Func1() { + + @Override + public R call(R t1) { + // allow transforming the value + return executionHook.onFallbackSuccess(_cmd, t1); + } + + }).onErrorResumeNext(new Func1>() { + + @Override + public Observable call(Throwable t) { + Exception e = getExceptionFromThrowable(t); + Exception decorated = executionHook.onFallbackError(_cmd, e); + + if (decorated instanceof RuntimeException) { + e = (RuntimeException) decorated; + } else { + logger.warn("ExecutionHook.onFallbackError returned an exception that was not an instance of RuntimeException so will be ignored.", decorated); + } + return Observable.error(e); + } + + }).doOnTerminate(new Action0() { + + @Override + public void call() { + fallbackSemaphore.release(); + } + + }); + } else { + metrics.markFallbackRejection(); + + logger.debug("HystrixCommand Fallback Rejection."); // debug only since we're throwing the exception and someone higher will do something with it + // if we couldn't acquire a permit, we "fail fast" by throwing an exception + return Observable.error(new HystrixRuntimeException(FailureType.REJECTED_SEMAPHORE_FALLBACK, this.getClass(), getLogMessagePrefix() + " fallback execution rejected.", null, null)); + } + } + + /** * @throws HystrixRuntimeException - * if a fallback does not exist - *

- *

    - *
  • via {@code Observer#onError} if a failure occurs
  • - *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • - *
- * @throws HystrixBadRequestException - * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure - * @throws IllegalStateException - * if invoked more than once */ - public Observable observe() { - // us a ReplaySubject to buffer the eagerly subscribed-to Observable - ReplaySubject subject = ReplaySubject.create(); - // eagerly kick off subscription - toObservable().subscribe(subject); - // return the subject that can be subscribed to later while the execution has already started - return subject; + private Observable getFallbackOrThrowException(HystrixEventType eventType, FailureType failureType, String message) { + return getFallbackOrThrowException(eventType, failureType, message, null); } /** - * A lazy {@link Observable} that will execute the command when subscribed to. - *

- * See https://github.com/Netflix/RxJava/wiki for more information. - * - * @param observeOn - * The {@link Scheduler} to execute callbacks on. - * @return {@code Observable} that lazily executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. * @throws HystrixRuntimeException - * if a fallback does not exist - *

- *

    - *
  • via {@code Observer#onError} if a failure occurs
  • - *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • - *
- * @throws HystrixBadRequestException - * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure - * @throws IllegalStateException - * if invoked more than once */ - public Observable toObservable(Scheduler observeOn) { - return toObservable(observeOn, true); + private Observable getFallbackOrThrowException(final HystrixEventType eventType, final FailureType failureType, final String message, final Exception originalException) { + final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread(); + + if (properties.fallbackEnabled().get()) { + /* fallback behavior is permitted so attempt */ + // record the executionResult + // do this before executing fallback so it can be queried from within getFallback (see See https://github.com/Netflix/Hystrix/pull/144) + executionResult = executionResult.addEvents(eventType); + final AbstractCommand _cmd = this; + + return getFallbackWithProtection().map(new Func1() { + + @Override + public R call(R t1) { + System.out.println(">>>>>>>>>>>> fallback on thread: " + Thread.currentThread()); + return executionHook.onComplete(_cmd, t1); + } + + }).doOnCompleted(new Action0() { + + @Override + public void call() { + // mark fallback on counter + metrics.markFallbackSuccess(); + // record the executionResult + executionResult = executionResult.addEvents(HystrixEventType.FALLBACK_SUCCESS); + } + + }).onErrorResumeNext(new Func1>() { + + @Override + public Observable call(Throwable t) { + Exception e = originalException; + Exception fe = getExceptionFromThrowable(t); + + if (fe instanceof UnsupportedOperationException) { + logger.debug("No fallback for HystrixCommand. ", fe); // debug only since we're throwing the exception and someone higher will do something with it + + /* executionHook for all errors */ + try { + e = executionHook.onError(_cmd, failureType, e); + } catch (Exception hookException) { + logger.warn("Error calling ExecutionHook.onError", hookException); + } + + return Observable.error(new HystrixRuntimeException(failureType, _cmd.getClass(), getLogMessagePrefix() + " " + message + " and no fallback available.", e, fe)); + } else { + logger.debug("HystrixCommand execution " + failureType.name() + " and fallback retrieval failed.", fe); + metrics.markFallbackFailure(); + // record the executionResult + executionResult = executionResult.addEvents(HystrixEventType.FALLBACK_FAILURE); + + /* executionHook for all errors */ + try { + e = executionHook.onError(_cmd, failureType, e); + } catch (Exception hookException) { + logger.warn("Error calling ExecutionHook.onError", hookException); + } + + return Observable.error(new HystrixRuntimeException(failureType, _cmd.getClass(), getLogMessagePrefix() + " " + message + " and failed retrieving fallback.", e, fe)); + } + } + + }).doOnTerminate(new Action0() { + + @Override + public void call() { + // record that we're completed (to handle non-successful events we do it here as well as at the end of executeCommand + isExecutionComplete.set(true); + } + + }).doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + setRequestContextIfNeeded(currentRequestContext); + } + + }); + } else { + /* fallback is disabled so throw HystrixRuntimeException */ + Exception e = originalException; + + logger.debug("Fallback disabled for HystrixCommand so will throw HystrixRuntimeException. ", e); // debug only since we're throwing the exception and someone higher will do something with it + // record the executionResult + executionResult = executionResult.addEvents(eventType); + + /* executionHook for all errors */ + try { + e = executionHook.onError(this, failureType, e); + } catch (Exception hookException) { + logger.warn("Error calling ExecutionHook.onError", hookException); + } + return Observable. error(new HystrixRuntimeException(failureType, this.getClass(), getLogMessagePrefix() + " " + message + " and fallback disabled.", e, null)).doOnTerminate(new Action0() { + + @Override + public void call() { + // record that we're completed (to handle non-successful events we do it here as well as at the end of executeCommand + isExecutionComplete.set(true); + } + + }).doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + setRequestContextIfNeeded(currentRequestContext); + } + + }); + } } - public abstract Observable toObservable(); + private static class HystrixObservableTimeoutOperator implements Operator { + + final AbstractCommand originalCommand; + final boolean isNonBlocking; + + public HystrixObservableTimeoutOperator(final AbstractCommand originalCommand, final boolean isNonBlocking) { + this.originalCommand = originalCommand; + this.isNonBlocking = isNonBlocking; + } + + public static class HystrixTimeoutException extends Exception { + + private static final long serialVersionUID = 7460860948388895401L; + + } + + @Override + public Subscriber call(final Subscriber child) { + final CompositeSubscription s = new CompositeSubscription(); + // if the child unsubscribes we unsubscribe our parent as well + child.add(s); + + /* + * Define the action to perform on timeout outside of the TimerListener to it can capture the HystrixRequestContext + * of the calling thread which doesn't exist on the Timer thread. + */ + final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, new Runnable() { - protected abstract ObservableCommand toObservable(final Scheduler observeOn, boolean performAsyncTimeout); + @Override + public void run() { + child.onError(new HystrixTimeoutException()); + } + }); + + TimerListener listener = new TimerListener() { + + @Override + public void tick() { + // if we can go from NOT_EXECUTED to TIMED_OUT then we do the timeout codepath + // otherwise it means we lost a race and the run() execution completed + if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) { + // do fallback logic + // report timeout failure + originalCommand.metrics.markTimeout(System.currentTimeMillis() - originalCommand.invocationStartTime); + + // we record execution time because we are returning before + originalCommand.recordTotalExecutionTime(originalCommand.invocationStartTime); + + // shut down the original request + s.unsubscribe(); + + timeoutRunnable.run(); + } + + } + + @Override + public int getIntervalTimeInMilliseconds() { + return originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get(); + } + }; + + Reference _tl = null; + if (isNonBlocking) { + /* + * Scheduling a separate timer to do timeouts is more expensive + * so we'll only do it if we're being used in a non-blocking manner. + */ + _tl = HystrixTimer.getInstance().addTimerListener(listener); + } else { + /* + * Otherwise we just set the hook that queue().get() can trigger if a timeout occurs. + * + * This allows the blocking and non-blocking approaches to be coded basically the same way + * though it is admittedly awkward if we were just blocking (the use of Reference annoys me for example) + */ + _tl = new SoftReference(listener); + } + final Reference tl = _tl; + + // set externally so execute/queue can see this + originalCommand.timeoutTimer.set(tl); + + /** + * If this subscriber receives values it means the parent succeeded/completed + */ + Subscriber parent = new Subscriber() { + + @Override + public void onCompleted() { + if (isNotTimedOut()) { + // stop timer and pass notification through + tl.clear(); + child.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + if (isNotTimedOut()) { + // stop timer and pass notification through + tl.clear(); + child.onError(e); + } + } + + @Override + public void onNext(R v) { + if (isNotTimedOut()) { + child.onNext(v); + } + } + + private boolean isNotTimedOut() { + // if already marked COMPLETED (by onNext) or succeeds in setting to COMPLETED + return originalCommand.isCommandTimedOut.get() == TimedOutStatus.COMPLETED || + originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.COMPLETED); + } + + }; + + // if s is unsubscribed we want to unsubscribe the parent + s.add(parent); + + return parent; + } + + } + + private static void setRequestContextIfNeeded(final HystrixRequestContext currentRequestContext) { + if (!HystrixRequestContext.isCurrentThreadInitialized()) { + // even if the user Observable doesn't have context we want it set for chained operators + HystrixRequestContext.setContextOnCurrentThread(currentRequestContext); + } + } /** * Get the TryableSemaphore this HystrixCommand should use if a fallback occurs. @@ -633,18 +1038,18 @@ protected TryableSemaphore getExecutionSemaphore() { } protected static class ObservableCommand extends Observable { - private final HystrixExecutableBase command; + private final AbstractCommand command; - ObservableCommand(OnSubscribe func, final HystrixExecutableBase command) { + ObservableCommand(OnSubscribe func, final AbstractCommand command) { super(func); this.command = command; } - public HystrixExecutableBase getCommand() { + public AbstractCommand getCommand() { return command; } - ObservableCommand(final Observable originalObservable, final HystrixExecutableBase command) { + ObservableCommand(final Observable originalObservable, final AbstractCommand command) { super(new OnSubscribe() { @Override @@ -666,9 +1071,9 @@ public void call(Subscriber observer) { */ protected static class CachedObservableOriginal extends ObservableCommand { - final HystrixExecutableBase originalCommand; + final AbstractCommand originalCommand; - CachedObservableOriginal(final Observable actual, HystrixExecutableBase command) { + CachedObservableOriginal(final Observable actual, AbstractCommand command) { super(new OnSubscribe() { @Override @@ -691,7 +1096,7 @@ public void call(final Subscriber observer) { protected static class CachedObservableResponse extends ObservableCommand { final CachedObservableOriginal originalObservable; - CachedObservableResponse(final CachedObservableOriginal originalObservable, final HystrixExecutableBase commandOfDuplicateCall) { + CachedObservableResponse(final CachedObservableOriginal originalObservable, final AbstractCommand commandOfDuplicateCall) { super(new OnSubscribe() { @Override @@ -735,13 +1140,13 @@ private void completeCommand() { /* * This is a cached response so we want the command of the observable we're wrapping. */ - public HystrixExecutableBase getCommand() { + public AbstractCommand getCommand() { return originalObservable.originalCommand; } } /** - * @return {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixObservableCommand} objects. + * @return {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixAsyncCommand} objects. *

* The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace with, * common business purpose etc. @@ -770,7 +1175,7 @@ public HystrixThreadPoolKey getThreadPoolKey() { } /** - * The {@link HystrixCommandMetrics} associated with this {@link HystrixObservableCommand} instance. + * The {@link HystrixCommandMetrics} associated with this {@link HystrixAsyncCommand} instance. * * @return HystrixCommandMetrics */ @@ -779,7 +1184,7 @@ public HystrixCommandMetrics getMetrics() { } /** - * The {@link HystrixCommandProperties} associated with this {@link HystrixObservableCommand} instance. + * The {@link HystrixCommandProperties} associated with this {@link HystrixAsyncCommand} instance. * * @return HystrixCommandProperties */ @@ -1213,7 +1618,7 @@ public void onRunStart(HystrixCommand commandInstance) { } @Override - public void onRunStart(HystrixExecutable commandInstance) { + public void onRunStart(HystrixInvokable commandInstance) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { onRunStart(c); @@ -1228,7 +1633,7 @@ public T onRunSuccess(HystrixCommand commandInstance, T response) { } @Override - public T onRunSuccess(HystrixExecutable commandInstance, T response) { + public T onRunSuccess(HystrixInvokable commandInstance, T response) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { response = onRunSuccess(c, response); @@ -1243,7 +1648,7 @@ public Exception onRunError(HystrixCommand commandInstance, Exception e) } @Override - public Exception onRunError(HystrixExecutable commandInstance, Exception e) { + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { e = onRunError(c, e); @@ -1258,7 +1663,7 @@ public void onFallbackStart(HystrixCommand commandInstance) { } @Override - public void onFallbackStart(HystrixExecutable commandInstance) { + public void onFallbackStart(HystrixInvokable commandInstance) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { onFallbackStart(c); @@ -1273,7 +1678,7 @@ public T onFallbackSuccess(HystrixCommand commandInstance, T fallbackResp } @Override - public T onFallbackSuccess(HystrixExecutable commandInstance, T fallbackResponse) { + public T onFallbackSuccess(HystrixInvokable commandInstance, T fallbackResponse) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { fallbackResponse = onFallbackSuccess(c, fallbackResponse); @@ -1288,7 +1693,7 @@ public Exception onFallbackError(HystrixCommand commandInstance, Exceptio } @Override - public Exception onFallbackError(HystrixExecutable commandInstance, Exception e) { + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { e = onFallbackError(c, e); @@ -1303,7 +1708,7 @@ public void onStart(HystrixCommand commandInstance) { } @Override - public void onStart(HystrixExecutable commandInstance) { + public void onStart(HystrixInvokable commandInstance) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { onStart(c); @@ -1318,7 +1723,7 @@ public T onComplete(HystrixCommand commandInstance, T response) { } @Override - public T onComplete(HystrixExecutable commandInstance, T response) { + public T onComplete(HystrixInvokable commandInstance, T response) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { response = onComplete(c, response); @@ -1333,7 +1738,7 @@ public Exception onError(HystrixCommand commandInstance, FailureType fail } @Override - public Exception onError(HystrixExecutable commandInstance, FailureType failureType, Exception e) { + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { e = onError(c, failureType, e); @@ -1348,7 +1753,7 @@ public void onThreadStart(HystrixCommand commandInstance) { } @Override - public void onThreadStart(HystrixExecutable commandInstance) { + public void onThreadStart(HystrixInvokable commandInstance) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { onThreadStart(c); @@ -1363,7 +1768,7 @@ public void onThreadComplete(HystrixCommand commandInstance) { } @Override - public void onThreadComplete(HystrixExecutable commandInstance) { + public void onThreadComplete(HystrixInvokable commandInstance) { HystrixCommand c = getHystrixCommandFromAbstractIfApplicable(commandInstance); if (c != null) { onThreadComplete(c); @@ -1372,9 +1777,9 @@ public void onThreadComplete(HystrixExecutable commandInstance) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - private HystrixCommand getHystrixCommandFromAbstractIfApplicable(HystrixExecutable commandInstance) { - if (commandInstance instanceof HystrixCommand.HystrixCommandFromObservableCommand) { - return ((HystrixCommand.HystrixCommandFromObservableCommand) commandInstance).getOriginal(); + private HystrixCommand getHystrixCommandFromAbstractIfApplicable(HystrixInvokable commandInstance) { + if (commandInstance instanceof HystrixCommand) { + return (HystrixCommand) commandInstance; } else { return null; } diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixAsyncCommand.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixAsyncCommand.java new file mode 100644 index 000000000..2816aa002 --- /dev/null +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixAsyncCommand.java @@ -0,0 +1,537 @@ +/** + * Copyright 2012 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 java.lang.ref.Reference; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.subjects.ReplaySubject; + +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; + +/** + * Used to wrap code that will execute potentially risky functionality (typically meaning a service call over the network) + * with fault and latency tolerance, statistics and performance metrics capture, circuit breaker and bulkhead functionality. + * This command is essentially a blocking command but provides an Observable facade if used with observe() + * + * @param + * the return type + * + * @ThreadSafe + */ +public abstract class HystrixAsyncCommand extends AbstractCommand implements HystrixExecutable, HystrixExecutableInfo, HystrixObservable { + + /** + * Construct a {@link HystrixAsyncCommand} with defined {@link HystrixCommandGroupKey}. + *

+ * The {@link HystrixCommandKey} will be derived from the implementing class name. + * + * @param group + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixAsyncCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace with, + * common business purpose etc. + */ + protected HystrixAsyncCommand(HystrixCommandGroupKey group) { + // use 'null' to specify use the default + this(new Setter(group)); + } + + /** + * Construct a {@link HystrixAsyncCommand} with defined {@link Setter} that allows injecting property and strategy overrides and other optional arguments. + *

+ * NOTE: The {@link HystrixCommandKey} is used to associate a {@link HystrixAsyncCommand} with {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and other objects. + *

+ * Do not create multiple {@link HystrixAsyncCommand} implementations with the same {@link HystrixCommandKey} but different injected default properties as the first instantiated will win. + *

+ * Properties passed in via {@link Setter#andCommandPropertiesDefaults} or {@link Setter#andThreadPoolPropertiesDefaults} are cached for the given {@link HystrixCommandKey} for the life of the JVM + * or until {@link Hystrix#reset()} is called. Dynamic properties allow runtime changes. Read more on the Hystrix Wiki. + * + * @param setter + * Fluent interface for constructor arguments + */ + protected HystrixAsyncCommand(Setter setter) { + // use 'null' to specify use the default + this(setter.groupKey, setter.commandKey, setter.threadPoolKey, null, null, setter.commandPropertiesDefaults, setter.threadPoolPropertiesDefaults, null, null, null, null, null); + } + + /** + * Allow constructing a {@link HystrixAsyncCommand} with injection of most aspects of its functionality. + *

+ * Some of these never have a legitimate reason for injection except in unit testing. + *

+ * Most of the args will revert to a valid default if 'null' is passed in. + */ + /* package for testing */HystrixAsyncCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, + HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, + HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, + HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { + super(group, key, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook); + } + + /** + * Fluent interface for arguments to the {@link HystrixAsyncCommand} constructor. + *

+ * The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods. + *

+ * Example: + *

 {@code
+     *  Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
+                .andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))
+                .andEventNotifier(notifier);
+     * } 
+ * + * @NotThreadSafe + */ + final public static class Setter { + + protected final HystrixCommandGroupKey groupKey; + protected HystrixCommandKey commandKey; + protected HystrixThreadPoolKey threadPoolKey; + protected HystrixCommandProperties.Setter commandPropertiesDefaults; + protected HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults; + + /** + * Setter factory method containing required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param groupKey + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixAsyncCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace + * with, + * common business purpose etc. + */ + protected Setter(HystrixCommandGroupKey groupKey) { + this.groupKey = groupKey; + + // default to using SEMAPHORE for ObservableCommand + commandPropertiesDefaults = setDefaults(HystrixCommandProperties.Setter()); + } + + /** + * Setter factory method with required values. + *

+ * All optional arguments can be set via the chained methods. + * + * @param groupKey + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixAsyncCommand} objects. + *

+ * The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace + * with, + * common business purpose etc. + */ + public static Setter withGroupKey(HystrixCommandGroupKey groupKey) { + return new Setter(groupKey); + } + + /** + * @param commandKey + * {@link HystrixCommandKey} used to identify a {@link HystrixAsyncCommand} instance for statistics, circuit-breaker, properties, etc. + *

+ * By default this will be derived from the instance class name. + *

+ * NOTE: Every unique {@link HystrixCommandKey} will result in new instances of {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and {@link HystrixCommandProperties}. + * Thus, + * the number of variants should be kept to a finite and reasonable number to avoid high-memory usage or memory leacks. + *

+ * Hundreds of keys is fine, tens of thousands is probably not. + * @return Setter for fluent interface via method chaining + */ + public Setter andCommandKey(HystrixCommandKey commandKey) { + this.commandKey = commandKey; + return this; + } + + /** + * Optional + * + * @param commandPropertiesDefaults + * {@link HystrixCommandProperties.Setter} with property overrides for this specific instance of {@link HystrixAsyncCommand}. + *

+ * See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence. + * @return Setter for fluent interface via method chaining + */ + public Setter andCommandPropertiesDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + this.commandPropertiesDefaults = setDefaults(commandPropertiesDefaults); + return this; + } + + private HystrixCommandProperties.Setter setDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + System.out.println("*********** " + commandPropertiesDefaults.getExecutionIsolationStrategy()); + if (commandPropertiesDefaults.getExecutionIsolationStrategy() == null) { + // default to using SEMAPHORE for ObservableCommand if the user didn't set it + commandPropertiesDefaults.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE); + } + return commandPropertiesDefaults; + } + + } + + public static final class HystrixFuture { + final Action0 cancel; + private final Promise promise; + + private HystrixFuture(Promise promise, Action0 cancel) { + this.promise = promise; + this.cancel = cancel; + } + + public static HystrixFuture create(Promise promise, Action0 cancel) { + return new HystrixFuture(promise, cancel); + } + + public static HystrixFuture create(Promise promise) { + return new HystrixFuture(promise, null); + } + + /** + * Attempt cancellation. Not all implementations support this. + */ + public final void cancel() { + if (cancel != null) { + cancel.call(); + } + } + + public Observable asObservable() { + return promise.replay.asObservable(); + } + + public void addListener(Action1 onSuccess, Action1 onError) { + promise.replay.subscribe(onSuccess, onError); + } + } + + public static final class Promise { + private final ReplaySubject replay = ReplaySubject.createWithSize(1); + + private Promise() { + } + + public static Promise create() { + return new Promise(); + } + + public final synchronized void onError(Throwable e) { + replay.onError(e); + } + + public final synchronized void onSuccess(R response) { + replay.onNext(response); + replay.onCompleted(); + } + + public final HystrixFuture createFuture() { + return HystrixFuture.create(this); + } + } + + /** + * Implement this method with code to be executed when the command is invoked. + * + * @return R response type + */ + protected abstract HystrixFuture run(); + + /** + * If {@link #execute()} or {@link #queue()} fails in any way then this method will be invoked to provide an opportunity to return a fallback response. + *

+ * This should do work that does not require network transport to produce. + *

+ * In other words, this should be a static or cached result that can immediately be returned upon failure. + *

+ * If network traffic is wanted for fallback (such as going to MemCache) then the fallback implementation should invoke another {@link HystrixAsyncCommand} instance that protects against that + * network + * access and possibly has another level of fallback that does not involve network access. + *

+ * DEFAULT BEHAVIOR: It throws UnsupportedOperationException. + * + * @return R or throw UnsupportedOperationException if not implemented + */ + protected HystrixFuture getFallback() { + throw new UnsupportedOperationException("No fallback available."); + } + + @Override + final protected Observable getExecutionObservable() { + try { + return run().asObservable(); + } catch (Throwable e) { + return Observable.error(e); + } + } + + @Override + final protected Observable getFallbackObservable() { + try { + return getFallback().asObservable(); + } catch (Throwable e) { + e.printStackTrace(); + return Observable.error(e); + } + } + + /** + * Used for synchronous execution of command. + * + * @return R + * Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @throws HystrixRuntimeException + * if a failure occurs and a fallback cannot be retrieved + * @throws HystrixBadRequestException + * if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + final public R execute() { + try { + return queue().get(); + } catch (Exception e) { + throw decomposeException(e); + } + } + + /** + * Used for asynchronous execution of command. + *

+ * This will queue up the command on the thread pool and return an {@link Future} to get the result once it completes. + *

+ * NOTE: If configured to not run in a separate thread, this will have the same effect as {@link #execute()} and will block. + *

+ * We don't throw an exception but just flip to synchronous execution so code doesn't need to change in order to switch a command from running on a separate thread to the calling thread. + * + * @return {@code Future} Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Future.get()} in {@link ExecutionException#getCause()} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Future.get()} in {@link ExecutionException#getCause()} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + final public Future queue() { + /* + * --- Schedulers.immediate() + * + * We use the 'immediate' schedule since Future.get() is blocking so we don't want to bother doing the callback to the Future on a separate thread + * as we don't need to separate the Hystrix thread from user threads since they are already providing it via the Future.get() call. + * + * --- performAsyncTimeout: false + * + * We pass 'false' to tell the Observable we will block on it so it doesn't schedule an async timeout. + * + * This optimizes for using the calling thread to do the timeout rather than scheduling another thread. + * + * In a tight-loop of executing commands this optimization saves a few microseconds per execution. + * It also just makes no sense to use a separate thread to timeout the command when the calling thread + * is going to sit waiting on it. + */ + final ObservableCommand o = toObservable(false); + final Future f = o.toBlocking().toFuture(); + + /* special handling of error states that throw immediately */ + if (f.isDone()) { + try { + f.get(); + return f; + } catch (Exception e) { + RuntimeException re = decomposeException(e); + if (re instanceof HystrixBadRequestException) { + return f; + } else if (re instanceof HystrixRuntimeException) { + HystrixRuntimeException hre = (HystrixRuntimeException) re; + if (hre.getFailureType() == FailureType.COMMAND_EXCEPTION || hre.getFailureType() == FailureType.TIMEOUT) { + // we don't throw these types from queue() only from queue().get() as they are execution errors + return f; + } else { + // these are errors we throw from queue() as they as rejection type errors + throw hre; + } + } else { + throw re; + } + } + } + + return new Future() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return f.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return f.isCancelled(); + } + + @Override + public boolean isDone() { + return f.isDone(); + } + + @Override + public R get() throws InterruptedException, ExecutionException { + return performBlockingGetWithTimeout(o, f); + } + + /** + * --- Non-Blocking Timeout (performAsyncTimeout:true) --- + * + * When 'toObservable' is done with non-blocking timeout then timeout functionality is provided + * by a separate HystrixTimer thread that will "tick" and cancel the underlying async Future inside the Observable. + * + * This method allows stealing that responsibility and letting the thread that's going to block anyways + * do the work to reduce pressure on the HystrixTimer. + * + * Blocking via queue().get() on a non-blocking timeout will work it's just less efficient + * as it involves an extra thread and cancels the scheduled action that does the timeout. + * + * --- Blocking Timeout (performAsyncTimeout:false) --- + * + * When blocking timeout is assumed (default behavior for execute/queue flows) then the async + * timeout will not have been scheduled and this will wait in a blocking manner and if a timeout occurs + * trigger the timeout logic that comes from inside the Observable/Observer. + * + * + * --- Examples + * + * Stack for timeout with performAsyncTimeout=false (note the calling thread via get): + * + * at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.tick(HystrixCommand.java:788) + * at com.netflix.hystrix.HystrixCommand$1.performBlockingGetWithTimeout(HystrixCommand.java:536) + * at com.netflix.hystrix.HystrixCommand$1.get(HystrixCommand.java:484) + * at com.netflix.hystrix.HystrixCommand.execute(HystrixCommand.java:413) + * + * + * Stack for timeout with performAsyncTimeout=true (note the HystrixTimer involved): + * + * at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.tick(HystrixCommand.java:799) + * at com.netflix.hystrix.util.HystrixTimer$1.run(HystrixTimer.java:101) + * + * + * + * @param o + * @param f + * @throws InterruptedException + * @throws ExecutionException + */ + protected R performBlockingGetWithTimeout(final ObservableCommand o, final Future f) throws InterruptedException, ExecutionException { + // shortcut if already done + if (f.isDone()) { + return f.get(); + } + + // it's still working so proceed with blocking/timeout logic + AbstractCommand originalCommand = o.getCommand(); + /** + * One thread will get the timeoutTimer if it's set and clear it then do blocking timeout. + *

+ * If non-blocking timeout was scheduled this will unschedule it. If it wasn't scheduled it is basically + * a no-op but fits the same interface so blocking and non-blocking flows both work the same. + *

+ * This "originalCommand" concept exists because of request caching. We only do the work and timeout logic + * on the original, not the cached responses. However, whichever the first thread is that comes in to block + * will be the one who performs the timeout logic. + *

+ * If request caching is disabled then it will always go into here. + */ + if (originalCommand != null) { + Reference timer = originalCommand.timeoutTimer.getAndSet(null); + if (timer != null) { + /** + * If an async timeout was scheduled then: + * + * - We are going to clear the Reference so the scheduler threads stop managing the timeout + * and we'll take over instead since we're going to be blocking on it anyways. + * + * - Other threads (since we won the race) will just wait on the normal Future which will release + * once the Observable is marked as completed (which may come via timeout) + * + * If an async timeout was not scheduled: + * + * - We go through the same flow as we receive the same interfaces just the "timer.clear()" will do nothing. + */ + // get the timer we'll use to perform the timeout + TimerListener l = timer.get(); + // remove the timer from the scheduler + timer.clear(); + + // determine how long we should wait for, taking into account time since work started + // and when this thread came in to block. If invocationTime hasn't been set then assume time remaining is entire timeout value + // as this maybe a case of multiple threads trying to run this command in which one thread wins but even before the winning thread is able to set + // the starttime another thread going via the Cached command route gets here first. + long timeout = originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get(); + long timeRemaining = timeout; + long currTime = System.currentTimeMillis(); + if (originalCommand.invocationStartTime != -1) { + timeRemaining = (originalCommand.invocationStartTime + + originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get()) + - currTime; + + } + if (timeRemaining > 0) { + // we need to block with the calculated timeout + try { + return f.get(timeRemaining, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + if (l != null) { + // this perform the timeout logic on the Observable/Observer + l.tick(); + } + } + } else { + // this means it should have already timed out so do so if it is not completed + if (!f.isDone()) { + if (l != null) { + l.tick(); + } + } + } + } + } + // other threads will block until the "l.tick" occurs and releases the underlying Future. + return f.get(); + } + + @Override + public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); + } + + }; + + } + +} diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java index 9aa448947..67904feb3 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapser.java @@ -23,8 +23,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import rx.*; -import rx.Observable.OnSubscribe; +import rx.Observable; +import rx.Scheduler; import rx.functions.Func1; import rx.schedulers.Schedulers; import rx.subjects.ReplaySubject; @@ -59,7 +59,7 @@ * @param * The type of the request argument. If multiple arguments are needed, wrap them in another object or a Tuple. */ -public abstract class HystrixCollapser implements HystrixExecutable { +public abstract class HystrixCollapser implements HystrixExecutable, HystrixObservable { static final Logger logger = LoggerFactory.getLogger(HystrixCollapser.class); diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java index a2147a39e..567ab5b7c 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCollapserProperties.java @@ -15,7 +15,7 @@ */ package com.netflix.hystrix; -import static com.netflix.hystrix.strategy.properties.HystrixProperty.Factory.*; +import static com.netflix.hystrix.strategy.properties.HystrixProperty.Factory.asProperty; import com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedArchaiusProperty; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java index f7ee6a22e..e57a28b2f 100755 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommand.java @@ -15,22 +15,24 @@ */ package com.netflix.hystrix; -import java.util.List; +import java.lang.ref.Reference; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Scheduler; import rx.Subscriber; import rx.schedulers.Schedulers; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; -import com.netflix.hystrix.HystrixExecutableBase.ObservableCommand; -import com.netflix.hystrix.HystrixExecutableBase.TryableSemaphore; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; +import com.netflix.hystrix.util.HystrixTimer.TimerListener; /** * Used to wrap code that will execute potentially risky functionality (typically meaning a service call over the network) @@ -39,12 +41,10 @@ * * @param * the return type - * + * * @ThreadSafe */ -public abstract class HystrixCommand implements HystrixExecutable, HystrixExecutableInfo { - - private final HystrixCommandFromObservableCommand observableCommand; +public abstract class HystrixCommand extends AbstractCommand implements HystrixExecutable, HystrixExecutableInfo, HystrixObservable { /** * Construct a {@link HystrixCommand} with defined {@link HystrixCommandGroupKey}. @@ -91,24 +91,11 @@ protected HystrixCommand(Setter setter) { HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { - - /* - * CommandKey initialization - */ - HystrixCommandKey commandKey = null; - if (key == null || key.name().trim().equals("")) { - // use the HystrixCommand class rather than this ObservableCommand class if we have it - final String keyName = HystrixExecutableBase.getDefaultNameFromClass(getClass()); - commandKey = HystrixCommandKey.Factory.asKey(keyName); - } else { - commandKey = key; - } - - this.observableCommand = new HystrixCommandFromObservableCommand(this, group, commandKey, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook); + super(group, key, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook); } /** - * Fluent interface for arguments to the {@link HystrixObservableCommand} constructor. + * Fluent interface for arguments to the {@link HystrixCommand} constructor. *

* The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods. *

@@ -121,7 +108,7 @@ protected HystrixCommand(Setter setter) { * * @NotThreadSafe */ - public static class Setter { + final public static class Setter { protected final HystrixCommandGroupKey groupKey; protected HystrixCommandKey commandKey; @@ -135,7 +122,7 @@ public static class Setter { * All optional arguments can be set via the chained methods. * * @param groupKey - * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixObservableCommand} objects. + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. *

* The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace * with, @@ -151,7 +138,7 @@ protected Setter(HystrixCommandGroupKey groupKey) { * All optional arguments can be set via the chained methods. * * @param groupKey - * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixObservableCommand} objects. + * {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects. *

* The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace * with, @@ -163,7 +150,7 @@ public static Setter withGroupKey(HystrixCommandGroupKey groupKey) { /** * @param commandKey - * {@link HystrixCommandKey} used to identify a {@link HystrixObservableCommand} instance for statistics, circuit-breaker, properties, etc. + * {@link HystrixCommandKey} used to identify a {@link HystrixCommand} instance for statistics, circuit-breaker, properties, etc. *

* By default this will be derived from the instance class name. *

@@ -197,7 +184,7 @@ public Setter andThreadPoolKey(HystrixThreadPoolKey threadPoolKey) { * Optional * * @param commandPropertiesDefaults - * {@link HystrixCommandProperties.Setter} with property overrides for this specific instance of {@link HystrixObservableCommand}. + * {@link HystrixCommandProperties.Setter} with property overrides for this specific instance of {@link HystrixCommand}. *

* See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence. * @return Setter for fluent interface via method chaining @@ -211,7 +198,7 @@ public Setter andCommandPropertiesDefaults(HystrixCommandProperties.Setter comma * Optional * * @param threadPoolPropertiesDefaults - * {@link HystrixThreadPoolProperties.Setter} with property overrides for the {@link HystrixThreadPool} used by this specific instance of {@link HystrixObservableCommand}. + * {@link HystrixThreadPoolProperties.Setter} with property overrides for the {@link HystrixThreadPool} used by this specific instance of {@link HystrixCommand}. *

* See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence. * @return Setter for fluent interface via method chaining @@ -250,252 +237,274 @@ protected R getFallback() { throw new UnsupportedOperationException("No fallback available."); } - /** - * Key to be used for request caching. - *

- * By default this returns null which means "do not cache". - *

- * To enable caching override this method and return a string key uniquely representing the state of a command instance. - *

- * If multiple command instances in the same request scope match keys then only the first will be executed and all others returned from cache. - * - * @return cacheKey - */ - protected String getCacheKey() { - return null; + @Override + final protected Observable getExecutionObservable() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + s.onNext(run()); + s.onCompleted(); + } catch (Throwable e) { + s.onError(e); + } + } + + }); + } + + @Override + final protected Observable getFallbackObservable() { + return Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + try { + s.onNext(getFallback()); + s.onCompleted(); + } catch (Throwable e) { + s.onError(e); + } + } + + }); } /** - * A lazy {@link Observable} that will execute the command when subscribed to. - *

- * Callback Scheduling - *

- *

    - *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#threadPoolForComputation()} for callbacks.
  • - *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • - *
- *

- * See https://github.com/Netflix/RxJava/wiki for more information. - * - * @return {@code Observable} that lazily executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * Used for synchronous execution of command. * + * @return R + * Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. * @throws HystrixRuntimeException - * if a fallback does not exist - *

- *

    - *
  • via {@code Observer#onError} if a failure occurs
  • - *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • - *
+ * if a failure occurs and a fallback cannot be retrieved * @throws HystrixBadRequestException - * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * if invalid arguments or state were used representing a user failure, not a system failure * @throws IllegalStateException * if invoked more than once */ - public Observable toObservable() { - if (observableCommand.properties.executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD)) { - return toObservable(Schedulers.computation()); - } else { - // semaphore isolation is all blocking, no new threads involved - // so we'll use the calling thread - return toObservable(Schedulers.immediate()); + final public R execute() { + try { + return queue().get(); + } catch (Exception e) { + throw decomposeException(e); } } /** - * A lazy {@link Observable} that will execute the command when subscribed to. + * Used for asynchronous execution of command. *

- * See https://github.com/Netflix/RxJava/wiki for more information. + * This will queue up the command on the thread pool and return an {@link Future} to get the result once it completes. + *

+ * NOTE: If configured to not run in a separate thread, this will have the same effect as {@link #execute()} and will block. + *

+ * We don't throw an exception but just flip to synchronous execution so code doesn't need to change in order to switch a command from running on a separate thread to the calling thread. * - * @param observeOn - * The {@link Scheduler} to execute callbacks on. - * @return {@code Observable} that lazily executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @return {@code Future} Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. * @throws HystrixRuntimeException * if a fallback does not exist *

*

    - *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • via {@code Future.get()} in {@link ExecutionException#getCause()} if a failure occurs
  • *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • *
* @throws HystrixBadRequestException - * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * via {@code Future.get()} in {@link ExecutionException#getCause()} if invalid arguments or state were used representing a user failure, not a system failure * @throws IllegalStateException * if invoked more than once */ - public Observable toObservable(Scheduler observeOn) { - return toObservable(observeOn, true); - } - - private ObservableCommand toObservable(final Scheduler observeOn, boolean performAsyncTimeout) { - return observableCommand.toObservable(observeOn, performAsyncTimeout); - } - - /* package */static class HystrixCommandFromObservableCommand extends HystrixObservableCommand { - - private final HystrixCommand original; - - protected HystrixCommandFromObservableCommand(HystrixCommand o, HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool, - HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults, - HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore, - HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) { - super(group, key, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook); - this.original = o; - } - - /* package */HystrixCommand getOriginal() { - return original; - } - - @Override - protected Observable run() { - return Observable.create(new OnSubscribe() { - - @Override - public void call(Subscriber s) { - try { - s.onNext(original.run()); - s.onCompleted(); - } catch (Throwable e) { - s.onError(e); + final public Future queue() { + /* + * --- Schedulers.immediate() + * + * We use the 'immediate' schedule since Future.get() is blocking so we don't want to bother doing the callback to the Future on a separate thread + * as we don't need to separate the Hystrix thread from user threads since they are already providing it via the Future.get() call. + * + * --- performAsyncTimeout: false + * + * We pass 'false' to tell the Observable we will block on it so it doesn't schedule an async timeout. + * + * This optimizes for using the calling thread to do the timeout rather than scheduling another thread. + * + * In a tight-loop of executing commands this optimization saves a few microseconds per execution. + * It also just makes no sense to use a separate thread to timeout the command when the calling thread + * is going to sit waiting on it. + */ + final ObservableCommand o = toObservable(false); + final Future f = o.toBlocking().toFuture(); + + /* special handling of error states that throw immediately */ + if (f.isDone()) { + try { + f.get(); + return f; + } catch (Exception e) { + RuntimeException re = decomposeException(e); + if (re instanceof HystrixBadRequestException) { + return f; + } else if (re instanceof HystrixRuntimeException) { + HystrixRuntimeException hre = (HystrixRuntimeException) re; + if (hre.getFailureType() == FailureType.COMMAND_EXCEPTION || hre.getFailureType() == FailureType.TIMEOUT) { + // we don't throw these types from queue() only from queue().get() as they are execution errors + return f; + } else { + // these are errors we throw from queue() as they as rejection type errors + throw hre; } + } else { + throw re; } - - }); + } } - @Override - protected Observable getFallback() { - return Observable.create(new OnSubscribe() { - - @Override - public void call(Subscriber s) { - try { - s.onNext(original.getFallback()); - s.onCompleted(); - } catch (Throwable e) { - s.onError(e); - } + return new Future() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return f.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return f.isCancelled(); + } + + @Override + public boolean isDone() { + return f.isDone(); + } + + @Override + public R get() throws InterruptedException, ExecutionException { + return performBlockingGetWithTimeout(o, f); + } + + /** + * --- Non-Blocking Timeout (performAsyncTimeout:true) --- + * + * When 'toObservable' is done with non-blocking timeout then timeout functionality is provided + * by a separate HystrixTimer thread that will "tick" and cancel the underlying async Future inside the Observable. + * + * This method allows stealing that responsibility and letting the thread that's going to block anyways + * do the work to reduce pressure on the HystrixTimer. + * + * Blocking via queue().get() on a non-blocking timeout will work it's just less efficient + * as it involves an extra thread and cancels the scheduled action that does the timeout. + * + * --- Blocking Timeout (performAsyncTimeout:false) --- + * + * When blocking timeout is assumed (default behavior for execute/queue flows) then the async + * timeout will not have been scheduled and this will wait in a blocking manner and if a timeout occurs + * trigger the timeout logic that comes from inside the Observable/Observer. + * + * + * --- Examples + * + * Stack for timeout with performAsyncTimeout=false (note the calling thread via get): + * + * at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.tick(HystrixCommand.java:788) + * at com.netflix.hystrix.HystrixCommand$1.performBlockingGetWithTimeout(HystrixCommand.java:536) + * at com.netflix.hystrix.HystrixCommand$1.get(HystrixCommand.java:484) + * at com.netflix.hystrix.HystrixCommand.execute(HystrixCommand.java:413) + * + * + * Stack for timeout with performAsyncTimeout=true (note the HystrixTimer involved): + * + * at com.netflix.hystrix.HystrixCommand$TimeoutObservable$1$1.tick(HystrixCommand.java:799) + * at com.netflix.hystrix.util.HystrixTimer$1.run(HystrixTimer.java:101) + * + * + * + * @param o + * @param f + * @throws InterruptedException + * @throws ExecutionException + */ + protected R performBlockingGetWithTimeout(final ObservableCommand o, final Future f) throws InterruptedException, ExecutionException { + // shortcut if already done + if (f.isDone()) { + return f.get(); } - }); - } - - @Override - protected String getCacheKey() { - return original.getCacheKey(); - } - } - - @Override - public HystrixCommandGroupKey getCommandGroup() { - return observableCommand.getCommandGroup(); - } - - @Override - public HystrixCommandKey getCommandKey() { - return observableCommand.getCommandKey(); - } - - @Override - public HystrixThreadPoolKey getThreadPoolKey() { - return observableCommand.getThreadPoolKey(); - } - - @Override - public HystrixCommandMetrics getMetrics() { - return observableCommand.getMetrics(); - } - - @Override - public HystrixCommandProperties getProperties() { - return observableCommand.getProperties(); - } - - @Override - public boolean isCircuitBreakerOpen() { - return observableCommand.isCircuitBreakerOpen(); - } - - @Override - public boolean isExecutionComplete() { - return observableCommand.isExecutionComplete(); - } - - @Override - public boolean isExecutedInThread() { - return observableCommand.isExecutedInThread(); - } - - @Override - public boolean isSuccessfulExecution() { - return observableCommand.isSuccessfulExecution(); - } - - @Override - public boolean isFailedExecution() { - return observableCommand.isFailedExecution(); - } - - @Override - public Throwable getFailedExecutionException() { - return observableCommand.getFailedExecutionException(); - } - - @Override - public boolean isResponseFromFallback() { - return observableCommand.isResponseFromFallback(); - } - - @Override - public boolean isResponseTimedOut() { - return observableCommand.isResponseTimedOut(); - } - - @Override - public boolean isResponseShortCircuited() { - return observableCommand.isResponseShortCircuited(); - } - - @Override - public boolean isResponseFromCache() { - return observableCommand.isResponseFromCache(); - } - - @Override - public boolean isResponseRejected() { - return observableCommand.isResponseRejected(); - } - - @Override - public List getExecutionEvents() { - return observableCommand.getExecutionEvents(); - } - - @Override - public int getExecutionTimeInMilliseconds() { - return observableCommand.getExecutionTimeInMilliseconds(); - } - - @Override - public Future queue() { - return observableCommand.queue(); - } - - @Override - public R execute() { - return observableCommand.execute(); - } + // it's still working so proceed with blocking/timeout logic + AbstractCommand originalCommand = o.getCommand(); + /** + * One thread will get the timeoutTimer if it's set and clear it then do blocking timeout. + *

+ * If non-blocking timeout was scheduled this will unschedule it. If it wasn't scheduled it is basically + * a no-op but fits the same interface so blocking and non-blocking flows both work the same. + *

+ * This "originalCommand" concept exists because of request caching. We only do the work and timeout logic + * on the original, not the cached responses. However, whichever the first thread is that comes in to block + * will be the one who performs the timeout logic. + *

+ * If request caching is disabled then it will always go into here. + */ + if (originalCommand != null) { + Reference timer = originalCommand.timeoutTimer.getAndSet(null); + if (timer != null) { + /** + * If an async timeout was scheduled then: + * + * - We are going to clear the Reference so the scheduler threads stop managing the timeout + * and we'll take over instead since we're going to be blocking on it anyways. + * + * - Other threads (since we won the race) will just wait on the normal Future which will release + * once the Observable is marked as completed (which may come via timeout) + * + * If an async timeout was not scheduled: + * + * - We go through the same flow as we receive the same interfaces just the "timer.clear()" will do nothing. + */ + // get the timer we'll use to perform the timeout + TimerListener l = timer.get(); + // remove the timer from the scheduler + timer.clear(); + + // determine how long we should wait for, taking into account time since work started + // and when this thread came in to block. If invocationTime hasn't been set then assume time remaining is entire timeout value + // as this maybe a case of multiple threads trying to run this command in which one thread wins but even before the winning thread is able to set + // the starttime another thread going via the Cached command route gets here first. + long timeout = originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get(); + long timeRemaining = timeout; + long currTime = System.currentTimeMillis(); + if (originalCommand.invocationStartTime != -1) { + timeRemaining = (originalCommand.invocationStartTime + + originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get()) + - currTime; + + } + if (timeRemaining > 0) { + // we need to block with the calculated timeout + try { + return f.get(timeRemaining, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + if (l != null) { + // this perform the timeout logic on the Observable/Observer + l.tick(); + } + } + } else { + // this means it should have already timed out so do so if it is not completed + if (!f.isDone()) { + if (l != null) { + l.tick(); + } + } + } + } + } + // other threads will block until the "l.tick" occurs and releases the underlying Future. + return f.get(); + } - @Override - public Observable observe() { - return observableCommand.observe(); - } + @Override + public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); + } - /* package */void markAsCollapsedCommand(int sizeOfBatch) { - observableCommand.markAsCollapsedCommand(sizeOfBatch); - } + }; - /* package */HystrixCircuitBreaker getCircuitBreaker() { - return observableCommand.getCircuitBreaker(); } } diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java index a8e08a80c..07b88d80a 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixCommandProperties.java @@ -15,7 +15,7 @@ */ package com.netflix.hystrix; -import static com.netflix.hystrix.strategy.properties.HystrixProperty.Factory.*; +import static com.netflix.hystrix.strategy.properties.HystrixProperty.Factory.asProperty; import java.util.concurrent.Future; diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java index 77785e7c7..a8f1d255b 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutable.java @@ -29,7 +29,7 @@ * * @param */ -public interface HystrixExecutable { +public interface HystrixExecutable extends HystrixInvokable { /** * Used for synchronous execution of command. diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableInfo.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableInfo.java index eb387c29d..ea968eae6 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableInfo.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixExecutableInfo.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 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 java.util.List; diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokable.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokable.java new file mode 100644 index 000000000..f90821432 --- /dev/null +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixInvokable.java @@ -0,0 +1,8 @@ +package com.netflix.hystrix; + +/** + * Marker interface for Hystrix commands that can be invoked. + */ +public interface HystrixInvokable { + +} diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservable.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservable.java new file mode 100644 index 000000000..b72105b4d --- /dev/null +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservable.java @@ -0,0 +1,96 @@ +/** + * Copyright 2014 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 rx.Observable; +import rx.schedulers.Schedulers; + +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +/** + * Common interface for executables that implement the Observable methods {@link #observe()} and {@link #toObservable()} so client code can treat them the same and combine in typed collections if desired. + * + * @param + */ +public interface HystrixObservable extends HystrixInvokable { + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This eagerly starts execution of the command the same as {@link #queue()} and {@link #execute()}. + *

+ * A lazy {@link Observable} can be obtained from {@link #toObservable()}. + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#threadPoolForComputation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ * Use {@link HystrixCommand#toObservable(rx.Scheduler)} or {@link HystrixCollapser#toObservable(rx.Scheduler)} to schedule the callback differently. + *

+ * See https://github.com/ReactiveX/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable observe(); + + /** + * Used for asynchronous execution of command with a callback by subscribing to the {@link Observable}. + *

+ * This lazily starts execution of the command only once the {@link Observable} is subscribed to. + *

+ * An eager {@link Observable} can be obtained from {@link #observe()} + *

+ * Callback Scheduling + *

+ *

    + *
  • When using {@link ExecutionIsolationStrategy#THREAD} this defaults to using {@link Schedulers#threadPoolForComputation()} for callbacks.
  • + *
  • When using {@link ExecutionIsolationStrategy#SEMAPHORE} this defaults to using {@link Schedulers#immediate()} for callbacks.
  • + *
+ * Use {@link HystrixCommand#toObservable(rx.Scheduler)} or {@link HystrixCollapser#toObservable(rx.Scheduler)} to schedule the callback differently. + *

+ * See https://github.com/ReactiveX/RxJava/wiki for more information. + * + * @return {@code Observable} that executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. + * @throws HystrixRuntimeException + * if a fallback does not exist + *

+ *

    + *
  • via {@code Observer#onError} if a failure occurs
  • + *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • + *
+ * @throws HystrixBadRequestException + * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure + * @throws IllegalStateException + * if invoked more than once + */ + public Observable toObservable(); + +} diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java index a060453ca..dbaedb9ab 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCollapser.java @@ -15,21 +15,29 @@ */ package com.netflix.hystrix; -import java.util.*; -import java.util.concurrent.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; -import org.slf4j.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import rx.*; import rx.Observable; -import rx.functions.*; +import rx.Scheduler; +import rx.functions.Action0; +import rx.functions.Func1; import rx.schedulers.Schedulers; import rx.subjects.ReplaySubject; import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; -import com.netflix.hystrix.collapser.*; -import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.collapser.CollapserTimer; +import com.netflix.hystrix.collapser.HystrixCollapserBridge; +import com.netflix.hystrix.collapser.RealCollapserTimer; +import com.netflix.hystrix.collapser.RequestCollapser; +import com.netflix.hystrix.collapser.RequestCollapserFactory; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; @@ -55,7 +63,7 @@ * @param * The type of the request argument. If multiple arguments are needed, wrap them in another object or a Tuple. */ -public abstract class HystrixObservableCollapser implements HystrixExecutable { +public abstract class HystrixObservableCollapser implements HystrixObservable { static final Logger logger = LoggerFactory.getLogger(HystrixObservableCollapser.class); @@ -397,52 +405,6 @@ public Observable toObservable(Scheduler observeOn) { return response; } - /** - * Used for synchronous execution. - *

- * If {@link Scope#REQUEST} is being used then synchronous execution will only result in collapsing if other threads are running within the same scope. - * - * @return ResponseType - * Result of {@link HystrixCommand}{@code } execution after passing through {@link #mapResponseToRequests} to transform the {@code } into - * {@code } - * @throws HystrixRuntimeException - * if an error occurs and a fallback cannot be retrieved - */ - public ResponseType execute() { - try { - return queue().get(); - } catch (Throwable e) { - if (e instanceof HystrixRuntimeException) { - throw (HystrixRuntimeException) e; - } - // if we have an exception we know about we'll throw it directly without the threading wrapper exception - if (e.getCause() instanceof HystrixRuntimeException) { - throw (HystrixRuntimeException) e.getCause(); - } - // we don't know what kind of exception this is so create a generic message and throw a new HystrixRuntimeException - String message = getClass().getSimpleName() + " HystrixCollapser failed while executing."; - logger.debug(message, e); // debug only since we're throwing the exception and someone higher will do something with it - //TODO should this be made a HystrixRuntimeException? - throw new RuntimeException(message, e); - } - } - - /** - * Used for asynchronous execution. - *

- * This will queue up the command and return a Future to get the result once it completes. - * - * @return ResponseType - * Result of {@link HystrixCommand}{@code } execution after passing through {@link #mapResponseToRequests} to transform the {@code } into - * {@code } - * @throws HystrixRuntimeException - * within an ExecutionException.getCause() (thrown by {@link Future#get}) if an error occurs and a fallback cannot be retrieved - */ - public Future queue() { - final Observable o = toObservable(); - return o.toBlocking().toFuture(); - } - /** * Key to be used for request caching. *

@@ -550,4 +512,4 @@ public Setter andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter p @SuppressWarnings("rawtypes") private static ConcurrentHashMap, String> defaultNameCache = new ConcurrentHashMap, String>(); -} +} \ No newline at end of file diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java index 9d3491bc9..8a838f7ed 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixObservableCommand.java @@ -15,27 +15,14 @@ */ package com.netflix.hystrix; -import java.lang.ref.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; - -import org.slf4j.*; - -import rx.*; -import rx.Observable.OnSubscribe; -import rx.Observable.Operator; -import rx.functions.*; +import rx.Observable; import rx.schedulers.Schedulers; -import rx.subscriptions.CompositeSubscription; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; -import com.netflix.hystrix.exception.*; -import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; -import com.netflix.hystrix.strategy.concurrency.*; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; -import com.netflix.hystrix.util.*; -import com.netflix.hystrix.util.HystrixTimer.TimerListener; /** * Used to wrap code that will execute potentially risky functionality (typically meaning a service call over the network) @@ -47,9 +34,7 @@ * * @ThreadSafe */ -public abstract class HystrixObservableCommand extends HystrixExecutableBase implements HystrixExecutable, HystrixExecutableInfo { - - private static final Logger logger = LoggerFactory.getLogger(HystrixObservableCommand.class); +public abstract class HystrixObservableCommand extends AbstractCommand implements HystrixObservable, HystrixExecutableInfo { /** * Construct a {@link HystrixObservableCommand} with defined {@link HystrixCommandGroupKey}. @@ -113,7 +98,7 @@ protected HystrixObservableCommand(Setter setter) { * * @NotThreadSafe */ - public static class Setter { + final public static class Setter { protected final HystrixCommandGroupKey groupKey; protected HystrixCommandKey commandKey; @@ -203,7 +188,7 @@ private HystrixCommandProperties.Setter setDefaults(HystrixCommandProperties.Set * * @return R response type */ - protected abstract Observable run(); + protected abstract Observable construct(); /** * If {@link #execute()} or {@link #queue()} fails in any way then this method will be invoked to provide an opportunity to return a fallback response. @@ -220,672 +205,17 @@ private HystrixCommandProperties.Setter setDefaults(HystrixCommandProperties.Set * * @return R or UnsupportedOperationException if not implemented */ - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.error(new UnsupportedOperationException("No fallback available.")); } - /** - * A lazy {@link Observable} that will execute the command when subscribed to. - *

- * This defaults to using {@link Schedulers#immediate()} for callbacks. - * - *

- * See https://github.com/Netflix/RxJava/wiki for more information. - * - * @return {@code Observable} that lazily executes and calls back with the result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason. - * - * @throws HystrixRuntimeException - * if a fallback does not exist - *

- *

    - *
  • via {@code Observer#onError} if a failure occurs
  • - *
  • or immediately if the command can not be queued (such as short-circuited, thread-pool/semaphore rejected)
  • - *
- * @throws HystrixBadRequestException - * via {@code Observer#onError} if invalid arguments or state were used representing a user failure, not a system failure - * @throws IllegalStateException - * if invoked more than once - */ - public Observable toObservable() { - return toObservable(Schedulers.immediate()); - } - - protected ObservableCommand toObservable(final Scheduler observeOn, final boolean performAsyncTimeout) { - /* this is a stateful object so can only be used once */ - if (!started.compareAndSet(false, true)) { - throw new IllegalStateException("This instance can only be executed once. Please instantiate a new instance."); - } - - /* try from cache first */ - if (isRequestCachingEnabled()) { - Observable fromCache = requestCache.get(getCacheKey()); - if (fromCache != null) { - /* mark that we received this response from cache */ - metrics.markResponseFromCache(); - return new CachedObservableResponse((CachedObservableOriginal) fromCache, this); - } - } - - final HystrixObservableCommand _this = this; - final AtomicReference endCurrentThreadExecutingCommand = new AtomicReference(); // don't like how this is being done - - // create an Observable that will lazily execute when subscribed to - Observable o = Observable.create(new OnSubscribe() { - - @Override - public void call(Subscriber observer) { - // async record keeping - recordExecutedCommand(); - - // mark that we're starting execution on the ExecutionHook - executionHook.onStart(_this); - - /* determine if we're allowed to execute */ - if (circuitBreaker.allowRequest()) { - final TryableSemaphore executionSemaphore = getExecutionSemaphore(); - // acquire a permit - if (executionSemaphore.tryAcquire()) { - try { - /* used to track userThreadExecutionTime */ - invocationStartTime = System.currentTimeMillis(); - - // store the command that is being run - endCurrentThreadExecutingCommand.set(Hystrix.startCurrentThreadExecutingCommand(getCommandKey())); - - getRunObservableDecoratedForMetricsAndErrorHandling(observeOn, performAsyncTimeout) - .doOnTerminate(new Action0() { - - @Override - public void call() { - // release the semaphore - // this is done here instead of below so that the acquire/release happens where it is guaranteed - // and not affected by the conditional circuit-breaker checks, timeouts, etc - executionSemaphore.release(); - - } - }).unsafeSubscribe(observer); - } catch (RuntimeException e) { - observer.onError(e); - } - } else { - metrics.markSemaphoreRejection(); - logger.debug("HystrixCommand Execution Rejection by Semaphore."); // debug only since we're throwing the exception and someone higher will do something with it - // retrieve a fallback or throw an exception if no fallback available - getFallbackOrThrowException(HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION, "could not acquire a semaphore for execution").unsafeSubscribe(observer); - } - } else { - // record that we are returning a short-circuited fallback - metrics.markShortCircuited(); - // short-circuit and go directly to fallback (or throw an exception if no fallback implemented) - try { - getFallbackOrThrowException(HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT, "short-circuited").unsafeSubscribe(observer); - } catch (Exception e) { - observer.onError(e); - } - } - } - }); - - // error handling at very end (this means fallback didn't exist or failed) - o = o.onErrorResumeNext(new Func1>() { - - @Override - public Observable call(Throwable t) { - // count that we are throwing an exception and re-throw it - metrics.markExceptionThrown(); - return Observable.error(t); - } - - }); - - // any final cleanup needed - o = o.doOnTerminate(new Action0() { - - @Override - public void call() { - Reference tl = timeoutTimer.get(); - if (tl != null) { - tl.clear(); - } - - try { - // if we executed we will record the execution time - if (invocationStartTime > 0 && !isResponseRejected()) { - /* execution time (must occur before terminal state otherwise a race condition can occur if requested by client) */ - recordTotalExecutionTime(invocationStartTime); - } - - // pop the command that is being run - if (endCurrentThreadExecutingCommand.get() != null) { - endCurrentThreadExecutingCommand.get().call(); - } - } finally { - metrics.decrementConcurrentExecutionCount(); - // record that we're completed - isExecutionComplete.set(true); - } - } - - }); - - // put in cache - if (isRequestCachingEnabled()) { - // wrap it for caching - o = new CachedObservableOriginal(o.cache(), this); - Observable fromCache = requestCache.putIfAbsent(getCacheKey(), o); - if (fromCache != null) { - // another thread beat us so we'll use the cached value instead - o = new CachedObservableResponse((CachedObservableOriginal) fromCache, this); - } - // we just created an ObservableCommand so we cast and return it - return (ObservableCommand) o; - } else { - // no request caching so a simple wrapper just to pass 'this' along with the Observable - return new ObservableCommand(o, this); - } - } - - /** - * This decorate "Hystrix" functionality around the run() Observable. - * - * @return R - */ - private Observable getRunObservableDecoratedForMetricsAndErrorHandling(final Scheduler observeOn, final boolean performAsyncTimeout) { - final HystrixObservableCommand _self = this; - // allow tracking how many concurrent threads are executing - metrics.incrementConcurrentExecutionCount(); - - final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread(); - - Observable run = null; - if (properties.executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD)) { - // mark that we are executing in a thread (even if we end up being rejected we still were a THREAD execution and not SEMAPHORE) - isExecutedInThread.set(true); - - run = Observable.create(new OnSubscribe() { - - @Override - public void call(Subscriber s) { - executionHook.onRunStart(_self); - executionHook.onThreadStart(_self); - if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) { - // the command timed out in the wrapping thread so we will return immediately - // and not increment any of the counters below or other such logic - s.onError(new RuntimeException("timed out before executing run()")); - } else { - // not timed out so execute - try { - final Action0 endCurrentThread = Hystrix.startCurrentThreadExecutingCommand(getCommandKey()); - run().doOnTerminate(new Action0() { - - @Override - public void call() { - // TODO is this actually the end of the thread? - executionHook.onThreadComplete(_self); - endCurrentThread.call(); - } - }).unsafeSubscribe(s); - } catch (Throwable t) { - // the run() method is a user provided implementation so can throw instead of using Observable.onError - // so we catch it here and turn it into Observable.error - Observable. error(t).unsafeSubscribe(s); - } - } - } - }).subscribeOn(threadPool.getScheduler()); - } else { - // semaphore isolated - executionHook.onRunStart(_self); - try { - run = run(); - } catch (Throwable t) { - // the run() method is a user provided implementation so can throw instead of using Observable.onError - // so we catch it here and turn it into Observable.error - run = Observable.error(t); - } - } - - run = run.doOnEach(new Action1>() { - - @Override - public void call(Notification n) { - setRequestContextIfNeeded(currentRequestContext); - } - - }).lift(new HystrixObservableTimeoutOperator(_self, performAsyncTimeout)).map(new Func1() { - - /** - * If we get here it means we did not timeout, otherwise it will skip this and go to onErrorResumeNext - */ - - @Override - public R call(R t1) { - long duration = System.currentTimeMillis() - invocationStartTime; - metrics.addCommandExecutionTime(duration); - // report success - executionResult = executionResult.addEvents(HystrixEventType.SUCCESS); - metrics.markSuccess(duration); - circuitBreaker.markSuccess(); - eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) duration, executionResult.events); - return executionHook.onRunSuccess(_self, t1); - } - - }).onErrorResumeNext(new Func1>() { - - @Override - public Observable call(Throwable t) { - Exception e = getExceptionFromThrowable(t); - if (e instanceof RejectedExecutionException) { - /** - * Rejection handling - */ - metrics.markThreadPoolRejection(); - // use a fallback instead (or throw exception if not implemented) - return getFallbackOrThrowException(HystrixEventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION, "could not be queued for execution", e); - } else if (t instanceof HystrixObservableTimeoutOperator.HystrixTimeoutException) { - /** - * Timeout handling - */ - Observable v = getFallbackOrThrowException(HystrixEventType.TIMEOUT, FailureType.TIMEOUT, "timed-out", new TimeoutException()); - /* - * we subscribeOn the computation scheduler as we don't want to use the Timer thread, nor can we use the - * THREAD isolation pool as it may be saturated and that's the reason we're in fallback. The fallback logic - * should not perform IO and thus we run on the computation event loops. - */ - return v.subscribeOn(new HystrixContextScheduler(concurrencyStrategy, Schedulers.computation())); - } else if (t instanceof HystrixBadRequestException) { - /** - * BadRequest handling - */ - try { - Exception decorated = executionHook.onRunError(_self, (Exception) t); - - if (decorated instanceof HystrixBadRequestException) { - t = (HystrixBadRequestException) decorated; - } else { - logger.warn("ExecutionHook.onRunError returned an exception that was not an instance of HystrixBadRequestException so will be ignored.", decorated); - } - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.onRunError", hookException); - } - - /* - * HystrixBadRequestException is treated differently and allowed to propagate without any stats tracking or fallback logic - */ - return Observable.error(t); - } else { - /** - * All other error handling - */ - try { - e = executionHook.onRunError(_self, e); - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.endRunFailure", hookException); - } - - logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", e); - - // report failure - metrics.markFailure(System.currentTimeMillis() - invocationStartTime); - // record the exception - executionResult = executionResult.setException(e); - return getFallbackOrThrowException(HystrixEventType.FAILURE, FailureType.COMMAND_EXCEPTION, "failed", e); - } - } - }).doOnEach(new Action1>() { - // setting again as the fallback could have lost the context - @Override - public void call(Notification n) { - setRequestContextIfNeeded(currentRequestContext); - } - - }).map(new Func1() { - - @Override - public R call(R t1) { - // allow transforming the results via the executionHook whether it came from success or fallback - return executionHook.onComplete(_self, t1); - } - - }); - - if (properties.executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD)) { - // we want to hand off work to a different scheduler so we don't tie up the Hystrix thread - if (!Schedulers.immediate().equals(observeOn)) { - // don't waste overhead if it's the 'immediate' scheduler - // otherwise we'll 'observeOn' and wrap with the HystrixContextScheduler - // to copy state across threads (if threads are involved) - run = run.observeOn(new HystrixContextScheduler(concurrencyStrategy, observeOn)); - } - } - - return run; - } - - /** - * Execute getFallback() within protection of a semaphore that limits number of concurrent executions. - *

- * Fallback implementations shouldn't perform anything that can be blocking, but we protect against it anyways in case someone doesn't abide by the contract. - *

- * If something in the getFallback() implementation is latent (such as a network call) then the semaphore will cause us to start rejecting requests rather than allowing potentially - * all threads to pile up and block. - * - * @return K - * @throws UnsupportedOperationException - * if getFallback() not implemented - * @throws HystrixException - * if getFallback() fails (throws an Exception) or is rejected by the semaphore - */ - private Observable getFallbackWithProtection() { - final TryableSemaphore fallbackSemaphore = getFallbackSemaphore(); - - // acquire a permit - if (fallbackSemaphore.tryAcquire()) { - executionHook.onFallbackStart(this); - final HystrixObservableCommand _cmd = this; - - Observable fallback = null; - try { - fallback = getFallback(); - } catch (Throwable t) { - // getFallback() is user provided and can throw so we catch it and turn it into Observable.error - fallback = Observable.error(t); - } - - return fallback.map(new Func1() { - - @Override - public R call(R t1) { - // allow transforming the value - return executionHook.onFallbackSuccess(_cmd, t1); - } - - }).onErrorResumeNext(new Func1>() { - - @Override - public Observable call(Throwable t) { - Exception e = getExceptionFromThrowable(t); - Exception decorated = executionHook.onFallbackError(_cmd, e); - - if (decorated instanceof RuntimeException) { - e = (RuntimeException) decorated; - } else { - logger.warn("ExecutionHook.onFallbackError returned an exception that was not an instance of RuntimeException so will be ignored.", decorated); - } - return Observable.error(e); - } - - }).doOnTerminate(new Action0() { - - @Override - public void call() { - fallbackSemaphore.release(); - } - - }); - } else { - metrics.markFallbackRejection(); - - logger.debug("HystrixCommand Fallback Rejection."); // debug only since we're throwing the exception and someone higher will do something with it - // if we couldn't acquire a permit, we "fail fast" by throwing an exception - return Observable.error(new HystrixRuntimeException(FailureType.REJECTED_SEMAPHORE_FALLBACK, this.getClass(), getLogMessagePrefix() + " fallback execution rejected.", null, null)); - } - } - - /** - * @throws HystrixRuntimeException - */ - private Observable getFallbackOrThrowException(HystrixEventType eventType, FailureType failureType, String message) { - return getFallbackOrThrowException(eventType, failureType, message, null); + @Override + final protected Observable getExecutionObservable() { + return construct(); } - - /** - * @throws HystrixRuntimeException - */ - private Observable getFallbackOrThrowException(final HystrixEventType eventType, final FailureType failureType, final String message, final Exception originalException) { - final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread(); - - if (properties.fallbackEnabled().get()) { - /* fallback behavior is permitted so attempt */ - // record the executionResult - // do this before executing fallback so it can be queried from within getFallback (see See https://github.com/Netflix/Hystrix/pull/144) - executionResult = executionResult.addEvents(eventType); - final HystrixObservableCommand _cmd = this; - - return getFallbackWithProtection().map(new Func1() { - - @Override - public R call(R t1) { - return executionHook.onComplete(_cmd, t1); - } - - }).doOnCompleted(new Action0() { - - @Override - public void call() { - // mark fallback on counter - metrics.markFallbackSuccess(); - // record the executionResult - executionResult = executionResult.addEvents(HystrixEventType.FALLBACK_SUCCESS); - } - - }).onErrorResumeNext(new Func1>() { - - @Override - public Observable call(Throwable t) { - Exception e = originalException; - Exception fe = getExceptionFromThrowable(t); - - if (fe instanceof UnsupportedOperationException) { - logger.debug("No fallback for HystrixCommand. ", fe); // debug only since we're throwing the exception and someone higher will do something with it - - /* executionHook for all errors */ - try { - e = executionHook.onError(_cmd, failureType, e); - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.onError", hookException); - } - - return Observable.error(new HystrixRuntimeException(failureType, _cmd.getClass(), getLogMessagePrefix() + " " + message + " and no fallback available.", e, fe)); - } else { - logger.debug("HystrixCommand execution " + failureType.name() + " and fallback retrieval failed.", fe); - metrics.markFallbackFailure(); - // record the executionResult - executionResult = executionResult.addEvents(HystrixEventType.FALLBACK_FAILURE); - - /* executionHook for all errors */ - try { - e = executionHook.onError(_cmd, failureType, e); - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.onError", hookException); - } - - return Observable.error(new HystrixRuntimeException(failureType, _cmd.getClass(), getLogMessagePrefix() + " " + message + " and failed retrieving fallback.", e, fe)); - } - } - - }).doOnTerminate(new Action0() { - - @Override - public void call() { - // record that we're completed (to handle non-successful events we do it here as well as at the end of executeCommand - isExecutionComplete.set(true); - } - - }).doOnEach(new Action1>() { - - @Override - public void call(Notification n) { - setRequestContextIfNeeded(currentRequestContext); - } - - }); - } else { - /* fallback is disabled so throw HystrixRuntimeException */ - Exception e = originalException; - - logger.debug("Fallback disabled for HystrixCommand so will throw HystrixRuntimeException. ", e); // debug only since we're throwing the exception and someone higher will do something with it - // record the executionResult - executionResult = executionResult.addEvents(eventType); - - /* executionHook for all errors */ - try { - e = executionHook.onError(this, failureType, e); - } catch (Exception hookException) { - logger.warn("Error calling ExecutionHook.onError", hookException); - } - return Observable. error(new HystrixRuntimeException(failureType, this.getClass(), getLogMessagePrefix() + " " + message + " and fallback disabled.", e, null)).doOnTerminate(new Action0() { - - @Override - public void call() { - // record that we're completed (to handle non-successful events we do it here as well as at the end of executeCommand - isExecutionComplete.set(true); - } - - }).doOnEach(new Action1>() { - - @Override - public void call(Notification n) { - setRequestContextIfNeeded(currentRequestContext); - } - - }); - } - } - - private static class HystrixObservableTimeoutOperator implements Operator { - - final HystrixObservableCommand originalCommand; - final boolean isNonBlocking; - - public HystrixObservableTimeoutOperator(final HystrixObservableCommand originalCommand, final boolean isNonBlocking) { - this.originalCommand = originalCommand; - this.isNonBlocking = isNonBlocking; - } - - public static class HystrixTimeoutException extends Exception { - - private static final long serialVersionUID = 7460860948388895401L; - - } - - @Override - public Subscriber call(final Subscriber child) { - final CompositeSubscription s = new CompositeSubscription(); - // if the child unsubscribes we unsubscribe our parent as well - child.add(s); - - /* - * Define the action to perform on timeout outside of the TimerListener to it can capture the HystrixRequestContext - * of the calling thread which doesn't exist on the Timer thread. - */ - final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, new Runnable() { - - @Override - public void run() { - child.onError(new HystrixTimeoutException()); - } - }); - - TimerListener listener = new TimerListener() { - - @Override - public void tick() { - // if we can go from NOT_EXECUTED to TIMED_OUT then we do the timeout codepath - // otherwise it means we lost a race and the run() execution completed - if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) { - // do fallback logic - // report timeout failure - originalCommand.metrics.markTimeout(System.currentTimeMillis() - originalCommand.invocationStartTime); - - // we record execution time because we are returning before - originalCommand.recordTotalExecutionTime(originalCommand.invocationStartTime); - - // shut down the original request - s.unsubscribe(); - - timeoutRunnable.run(); - } - - } - - @Override - public int getIntervalTimeInMilliseconds() { - return originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get(); - } - }; - - Reference _tl = null; - if (isNonBlocking) { - /* - * Scheduling a separate timer to do timeouts is more expensive - * so we'll only do it if we're being used in a non-blocking manner. - */ - _tl = HystrixTimer.getInstance().addTimerListener(listener); - } else { - /* - * Otherwise we just set the hook that queue().get() can trigger if a timeout occurs. - * - * This allows the blocking and non-blocking approaches to be coded basically the same way - * though it is admittedly awkward if we were just blocking (the use of Reference annoys me for example) - */ - _tl = new SoftReference(listener); - } - final Reference tl = _tl; - - // set externally so execute/queue can see this - originalCommand.timeoutTimer.set(tl); - - /** - * If this subscriber receives values it means the parent succeeded/completed - */ - Subscriber parent = new Subscriber() { - - @Override - public void onCompleted() { - if (isNotTimedOut()) { - // stop timer and pass notification through - tl.clear(); - child.onCompleted(); - } - } - - @Override - public void onError(Throwable e) { - if (isNotTimedOut()) { - // stop timer and pass notification through - tl.clear(); - child.onError(e); - } - } - - @Override - public void onNext(R v) { - if (isNotTimedOut()) { - child.onNext(v); - } - } - - private boolean isNotTimedOut() { - // if already marked COMPLETED (by onNext) or succeeds in setting to COMPLETED - return originalCommand.isCommandTimedOut.get() == TimedOutStatus.COMPLETED || - originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.COMPLETED); - } - - }; - - // if s is unsubscribed we want to unsubscribe the parent - s.add(parent); - - return parent; - } - - } - - private static void setRequestContextIfNeeded(final HystrixRequestContext currentRequestContext) { - if (!HystrixRequestContext.isCurrentThreadInitialized()) { - // even if the user Observable doesn't have context we want it set for chained operators - HystrixRequestContext.setContextOnCurrentThread(currentRequestContext); - } + + @Override + final protected Observable getFallbackObservable() { + return onFailureResumeWithFallback(); } } diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java index bdc0d62c8..2bd58f605 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixRequestLog.java @@ -129,9 +129,9 @@ public Collection> getAllExecutedCommands() { } // TODO remove this when deprecation completed - if (command instanceof HystrixCommand.HystrixCommandFromObservableCommand) { + if (command instanceof HystrixCommand) { @SuppressWarnings("rawtypes") - HystrixCommand _c = ((HystrixCommand.HystrixCommandFromObservableCommand) command).getOriginal(); + HystrixCommand _c = (HystrixCommand) command; if (!executedCommands.offer(_c)) { // see RequestLog: Reduce Chance of Memory Leak https://github.com/Netflix/Hystrix/issues/53 logger.warn("RequestLog ignoring command after reaching limit of " + MAX_STORAGE + ". See https://github.com/Netflix/Hystrix/issues/53 for more information."); diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java index aa88eecb8..3c02f10a7 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/HystrixThreadPoolProperties.java @@ -15,7 +15,7 @@ */ package com.netflix.hystrix; -import static com.netflix.hystrix.strategy.properties.HystrixProperty.Factory.*; +import static com.netflix.hystrix.strategy.properties.HystrixProperty.Factory.asProperty; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java b/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java index 95bb9c4c6..c6a47812e 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestBatch.java @@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory; import rx.Observable; -import rx.Observer; -import rx.functions.*; +import rx.functions.Action0; +import rx.functions.Action1; import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java b/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java index f94b98ce7..822e2e9df 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/collapser/RequestCollapserFactory.java @@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory; import com.netflix.hystrix.HystrixCollapser; -import com.netflix.hystrix.HystrixCollapser.Scope; import com.netflix.hystrix.HystrixCollapserKey; import com.netflix.hystrix.HystrixCollapserProperties; import com.netflix.hystrix.strategy.HystrixPlugins; diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java b/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java index 0792dbd45..6c1dd5052 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/exception/HystrixRuntimeException.java @@ -16,7 +16,7 @@ package com.netflix.hystrix.exception; import com.netflix.hystrix.HystrixCommand; -import com.netflix.hystrix.HystrixExecutable; +import com.netflix.hystrix.HystrixInvokable; import com.netflix.hystrix.util.ExceptionThreadingUtility; /** @@ -27,7 +27,7 @@ public class HystrixRuntimeException extends RuntimeException { private static final long serialVersionUID = 5219160375476046229L; - private final Class commandClass; + private final Class commandClass; private final Throwable fallbackException; private final FailureType failureCause; @@ -35,7 +35,7 @@ public static enum FailureType { COMMAND_EXCEPTION, TIMEOUT, SHORTCIRCUIT, REJECTED_THREAD_EXECUTION, REJECTED_SEMAPHORE_EXECUTION, REJECTED_SEMAPHORE_FALLBACK } - public HystrixRuntimeException(FailureType failureCause, Class commandClass, String message, Exception cause, Throwable fallbackException) { + public HystrixRuntimeException(FailureType failureCause, Class commandClass, String message, Exception cause, Throwable fallbackException) { super(message, cause); this.failureCause = failureCause; this.commandClass = commandClass; @@ -43,7 +43,7 @@ public HystrixRuntimeException(FailureType failureCause, Class commandClass, String message, Throwable cause, Throwable fallbackException) { + public HystrixRuntimeException(FailureType failureCause, Class commandClass, String message, Throwable cause, Throwable fallbackException) { super(message, cause); this.failureCause = failureCause; this.commandClass = commandClass; @@ -65,7 +65,7 @@ public FailureType getFailureType() { * * @return {@code Class } */ - public Class getImplementingClass() { + public Class getImplementingClass() { return commandClass; } diff --git a/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java b/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java index 1ce80d9b4..2dc847426 100644 --- a/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java +++ b/hystrix-core/src/main/java/com/netflix/hystrix/strategy/executionhook/HystrixCommandExecutionHook.java @@ -17,7 +17,7 @@ import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; -import com.netflix.hystrix.HystrixExecutable; +import com.netflix.hystrix.HystrixInvokable; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; import com.netflix.hystrix.strategy.HystrixPlugins; @@ -52,7 +52,7 @@ public void onRunStart(HystrixCommand commandInstance) { // do nothing by default } - public void onRunStart(HystrixExecutable commandInstance) { + public void onRunStart(HystrixInvokable commandInstance) { // do nothing by default } @@ -73,7 +73,7 @@ public T onRunSuccess(HystrixCommand commandInstance, T response) { return response; } - public T onRunSuccess(HystrixExecutable commandInstance, T response) { + public T onRunSuccess(HystrixInvokable commandInstance, T response) { // pass-thru by default return response; } @@ -95,7 +95,7 @@ public Exception onRunError(HystrixCommand commandInstance, Exception e) return e; } - public Exception onRunError(HystrixExecutable commandInstance, Exception e) { + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { // pass-thru by default return e; } @@ -113,7 +113,7 @@ public void onFallbackStart(HystrixCommand commandInstance) { // do nothing by default } - public void onFallbackStart(HystrixExecutable commandInstance) { + public void onFallbackStart(HystrixInvokable commandInstance) { // do nothing by default } @@ -134,7 +134,7 @@ public T onFallbackSuccess(HystrixCommand commandInstance, T fallbackResp return fallbackResponse; } - public T onFallbackSuccess(HystrixExecutable commandInstance, T fallbackResponse) { + public T onFallbackSuccess(HystrixInvokable commandInstance, T fallbackResponse) { // pass-thru by default return fallbackResponse; } @@ -156,7 +156,7 @@ public Exception onFallbackError(HystrixCommand commandInstance, Exceptio return e; } - public Exception onFallbackError(HystrixExecutable commandInstance, Exception e) { + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { // pass-thru by default return e; } @@ -174,7 +174,7 @@ public void onStart(HystrixCommand commandInstance) { // do nothing by default } - public void onStart(HystrixExecutable commandInstance) { + public void onStart(HystrixInvokable commandInstance) { // do nothing by default } @@ -197,7 +197,7 @@ public T onComplete(HystrixCommand commandInstance, T response) { return response; } - public T onComplete(HystrixExecutable commandInstance, T response) { + public T onComplete(HystrixInvokable commandInstance, T response) { // pass-thru by default return response; } @@ -223,7 +223,7 @@ public Exception onError(HystrixCommand commandInstance, FailureType fail return e; } - public Exception onError(HystrixExecutable commandInstance, FailureType failureType, Exception e) { + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { // pass-thru by default return e; } @@ -241,7 +241,7 @@ public void onThreadStart(HystrixCommand commandInstance) { // do nothing by default } - public void onThreadStart(HystrixExecutable commandInstance) { + public void onThreadStart(HystrixInvokable commandInstance) { // do nothing by default } @@ -258,7 +258,7 @@ public void onThreadComplete(HystrixCommand commandInstance) { // do nothing by default } - public void onThreadComplete(HystrixExecutable commandInstance) { + public void onThreadComplete(HystrixInvokable commandInstance) { // do nothing by default } diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixAsyncCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixAsyncCommandTest.java new file mode 100644 index 000000000..375b64fcc --- /dev/null +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixAsyncCommandTest.java @@ -0,0 +1,7084 @@ +package com.netflix.hystrix; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import rx.Notification; +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Observer; +import rx.Scheduler; +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.AbstractCommand.TryableSemaphoreActual; +import com.netflix.hystrix.HystrixCircuitBreakerTest.TestCircuitBreaker; +import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; +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.HystrixPropertiesStrategy; +import com.netflix.hystrix.strategy.properties.HystrixProperty; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; + +public class HystrixAsyncCommandTest { + + @Before + public void prepareForTest() { + /* we must call this to simulate a new request lifecycle running and clearing caches */ + HystrixRequestContext.initializeContext(); + } + + @After + public void cleanup() { + // instead of storing the reference from initialize we'll just get the current state and shutdown + if (HystrixRequestContext.getContextForCurrentThread() != null) { + // it could have been set NULL by the test + HystrixRequestContext.getContextForCurrentThread().shutdown(); + } + + // force properties to be clean as well + ConfigurationManager.getConfigInstance().clear(); + + /* + * RxJava will create one worker for each processor when we schedule Observables in the + * Schedulers.computation(). Any leftovers here might lead to a congestion in a following + * thread. To ensure all existing threads have completed we now schedule some observables + * that will execute in distinct threads due to the latch.. + */ + int count = Runtime.getRuntime().availableProcessors(); + final CountDownLatch latch = new CountDownLatch(count); + ArrayList> futures = new ArrayList>(); + for (int i = 0; i < count; ++i) { + futures.add(Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber sub) { + latch.countDown(); + try { + latch.await(); + sub.onNext(true); + sub.onCompleted(); + } catch (InterruptedException e) { + sub.onError(e); + } + } + }).subscribeOn(Schedulers.computation()).toBlocking().toFuture()); + } + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + //TODO commented out as it has issues when built from command-line even though it works from IDE + // HystrixCommandKey key = Hystrix.getCurrentThreadExecutingCommand(); + // if (key != null) { + // throw new IllegalStateException("should be null but got: " + key); + // } + } + + /** + * Test a successful command execution. + */ + @Test + public void testExecutionSuccess() { + try { + TestHystrixCommand command = new SuccessfulTestCommand(); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(true, command.observe().toBlocking().single()); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + assertEquals(null, command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test that a command can not be executed multiple times. + */ + @Test + public void testExecutionMultipleTimes() { + SuccessfulTestCommand command = new SuccessfulTestCommand(); + assertFalse(command.isExecutionComplete()); + // first should succeed + assertEquals(true, command.observe().toBlocking().single()); + System.out.println(">> completed, checking metrics"); + assertTrue(command.isExecutionComplete()); + assertFalse(command.isExecutedInThread()); + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + try { + // second should fail + command.observe().toBlocking().single(); + fail("we should not allow this ... it breaks the state of request logs"); + } catch (IllegalStateException e) { + e.printStackTrace(); + // we want to get here + } + + try { + // queue should also fail + command.observe().toBlocking().toFuture(); + fail("we should not allow this ... it breaks the state of request logs"); + } catch (IllegalStateException e) { + e.printStackTrace(); + // we want to get here + } + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution that throws an HystrixException and didn't implement getFallback. + */ + @Test + public void testExecutionKnownFailureWithNoFallback() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + TestHystrixCommand command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertNotNull(e.getFallbackException()); + assertNotNull(e.getImplementingClass()); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + } catch (Exception e) { + e.printStackTrace(); + fail("We should always get an HystrixRuntimeException when an error occurs."); + } + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution that throws an unknown exception (not HystrixException) and didn't implement getFallback. + */ + @Test + public void testExecutionUnknownFailureWithNoFallback() { + TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertNotNull(e.getFallbackException()); + assertNotNull(e.getImplementingClass()); + + } catch (Exception e) { + e.printStackTrace(); + fail("We should always get an HystrixRuntimeException when an error occurs."); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution that fails but has a fallback. + */ + @Test + public void testExecutionFailureWithFallback() { + TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + try { + assertEquals(false, command.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals("we failed with a simulated issue", command.getFailedExecutionException().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution that fails, has getFallback implemented but that fails as well. + */ + @Test + public void testExecutionFailureWithFallbackFailure() { + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (HystrixRuntimeException e) { + System.out.println("------------------------------------------------"); + e.printStackTrace(); + System.out.println("------------------------------------------------"); + assertNotNull(e.getFallbackException()); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a successful command execution (asynchronously). + */ + @Test + public void testQueueSuccess() { + TestHystrixCommand command = new SuccessfulTestCommand(); + try { + Future future = command.observe().toBlocking().toFuture(); + assertEquals(true, future.get()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution (asynchronously) that throws an HystrixException and didn't implement getFallback. + */ + @Test + public void testQueueKnownFailureWithNoFallback() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + TestHystrixCommand command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); + try { + command.observe().toBlocking().toFuture().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()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution (asynchronously) that throws an unknown exception (not HystrixException) and didn't implement getFallback. + */ + @Test + public void testQueueUnknownFailureWithNoFallback() { + TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); + try { + command.observe().toBlocking().toFuture().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()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution (asynchronously) that fails but has a fallback. + */ + @Test + public void testQueueFailureWithFallback() { + TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + try { + Future future = command.observe().toBlocking().toFuture(); + assertEquals(false, future.get()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution (asynchronously) that fails, has getFallback implemented but that fails as well. + */ + @Test + public void testQueueFailureWithFallbackFailure() { + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); + try { + command.observe().toBlocking().toFuture().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()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a successful command execution. + */ + @Test + public void testObserveSuccess() { + try { + TestHystrixCommand command = new SuccessfulTestCommand(); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + assertEquals(true, command.observe().toBlocking().single()); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + assertEquals(null, command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test a successful command execution. + * + * @Test + * public void testObserveOnScheduler() throws Exception { + * + * System.out.println("test observeOn begins"); + * final AtomicReference commandThread = new AtomicReference(); + * final AtomicReference subscribeThread = new AtomicReference(); + * + * TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder()) { + * @Override + * protected Observable run() { + * commandThread.set(Thread.currentThread()); + * return Observable.just(true); + * } + * }; + * + * final CountDownLatch latch = new CountDownLatch(1); + * + * Scheduler customScheduler = new Scheduler() { + * + * private final Scheduler self = this; + * @Override + * public Subscription schedule(T state, Func2 action) { + * return schedule(state, action, 0, TimeUnit.MILLISECONDS); + * } + * @Override + * public Subscription schedule(final T state, final Func2 action, long delayTime, TimeUnit unit) { + * new Thread("RxScheduledThread") { + * @Override + * public void run() { + * System.out.println("in schedule"); + * action.call(self, state); + * } + * }.start(); + * + * // not testing unsubscribe behavior + * return Subscriptions.empty(); + * } + * + * }; + * + * command.toObservable(customScheduler).subscribe(new Observer() { + * @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("subscribeThread: " + subscribeThread.get().getName()); + * assertTrue(commandThread.get().getName().startsWith("main")); + * //assertTrue(subscribeThread.get().getName().equals("RxScheduledThread")); + * assertTrue(subscribeThread.get().getName().equals("main")); + * } + */ + /** + * Test a successful command execution. + * + * @Test + * public void testObserveOnComputationSchedulerByDefaultForThreadIsolation() throws Exception { + * + * final AtomicReference commandThread = new AtomicReference(); + * final AtomicReference subscribeThread = new AtomicReference(); + * + * TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder()) { + * @Override + * protected Observable run() { + * commandThread.set(Thread.currentThread()); + * return Observable.just(true); + * } + * }; + * + * final CountDownLatch latch = new CountDownLatch(1); + * + * command.toObservable().subscribe(new Observer() { + * @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("RxComputationThreadPool")); + * + * assertTrue(commandThread.get().getName().startsWith("main")); + * assertTrue(subscribeThread.get().getName().startsWith("main")); + * } + */ + + /** + * Test a successful command execution. + */ + @Test + public void testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation() throws Exception { + + final AtomicReference commandThread = new AtomicReference(); + final AtomicReference subscribeThread = new AtomicReference(); + + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))) { + + @Override + protected HystrixFuture run() { + commandThread.set(Thread.currentThread()); + return HystrixFutureUtil.just(true); + } + }; + + final CountDownLatch latch = new CountDownLatch(1); + + command.toObservable().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + + } + + @Override + public void onError(Throwable e) { + latch.countDown(); + e.printStackTrace(); + + } + + @Override + public void onNext(Boolean args) { + subscribeThread.set(Thread.currentThread()); + } + }); + + if (!latch.await(2000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + assertNotNull(commandThread.get()); + assertNotNull(subscribeThread.get()); + + System.out.println("Command Thread: " + commandThread.get()); + System.out.println("Subscribe Thread: " + subscribeThread.get()); + + String mainThreadName = Thread.currentThread().getName(); + + // semaphore should be on the calling thread + assertTrue(commandThread.get().getName().equals(mainThreadName)); + System.out.println("testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation: " + subscribeThread.get() + " => " + mainThreadName); + assertTrue(subscribeThread.get().getName().equals(mainThreadName)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test that the circuit-breaker will 'trip' and prevent command execution on subsequent calls. + */ + @Test + public void testCircuitBreakerTripsAfterFailures() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + /* fail 3 times and then it should trip the circuit and stop executing */ + // failure 1 + KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt1.observe().toBlocking().single(); + assertTrue(attempt1.isResponseFromFallback()); + assertFalse(attempt1.isCircuitBreakerOpen()); + assertFalse(attempt1.isResponseShortCircuited()); + + // failure 2 + KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt2.observe().toBlocking().single(); + assertTrue(attempt2.isResponseFromFallback()); + assertFalse(attempt2.isCircuitBreakerOpen()); + assertFalse(attempt2.isResponseShortCircuited()); + + // failure 3 + KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt3.observe().toBlocking().single(); + assertTrue(attempt3.isResponseFromFallback()); + assertFalse(attempt3.isResponseShortCircuited()); + // it should now be 'open' and prevent further executions + assertTrue(attempt3.isCircuitBreakerOpen()); + + // attempt 4 + KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt4.observe().toBlocking().single(); + 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()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(4, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test that the circuit-breaker will 'trip' and prevent command execution on subsequent calls. + */ + @Test + public void testCircuitBreakerTripsAfterFailuresViaQueue() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + try { + /* fail 3 times and then it should trip the circuit and stop executing */ + // failure 1 + KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt1.observe().toBlocking().toFuture().get(); + assertTrue(attempt1.isResponseFromFallback()); + assertFalse(attempt1.isCircuitBreakerOpen()); + assertFalse(attempt1.isResponseShortCircuited()); + + // failure 2 + KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt2.observe().toBlocking().toFuture().get(); + assertTrue(attempt2.isResponseFromFallback()); + assertFalse(attempt2.isCircuitBreakerOpen()); + assertFalse(attempt2.isResponseShortCircuited()); + + // failure 3 + KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt3.observe().toBlocking().toFuture().get(); + assertTrue(attempt3.isResponseFromFallback()); + assertFalse(attempt3.isResponseShortCircuited()); + // it should now be 'open' and prevent further executions + assertTrue(attempt3.isCircuitBreakerOpen()); + + // attempt 4 + KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt4.observe().toBlocking().toFuture().get(); + 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()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(4, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received fallbacks."); + } + } + + /** + * Test that the circuit-breaker is shared across HystrixCommand objects with the same CommandKey. + *

+ * This will test HystrixCommand objects with a single circuit-breaker (as if each injected with same CommandKey) + *

+ * Multiple HystrixCommand objects with the same dependency use the same circuit-breaker. + */ + @Test + public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + /* fail 3 times and then it should trip the circuit and stop executing */ + // failure 1 + KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt1.observe().toBlocking().single(); + assertTrue(attempt1.isResponseFromFallback()); + assertFalse(attempt1.isCircuitBreakerOpen()); + assertFalse(attempt1.isResponseShortCircuited()); + + // failure 2 with a different command, same circuit breaker + KnownFailureTestCommandWithoutFallback attempt2 = new KnownFailureTestCommandWithoutFallback(circuitBreaker); + try { + attempt2.observe().toBlocking().single(); + } catch (Exception e) { + // ignore ... this doesn't have a fallback so will throw an exception + } + assertTrue(attempt2.isFailedExecution()); + assertFalse(attempt2.isResponseFromFallback()); // false because no fallback + assertFalse(attempt2.isCircuitBreakerOpen()); + assertFalse(attempt2.isResponseShortCircuited()); + + // failure 3 of the Hystrix, 2nd for this particular HystrixCommand + KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt3.observe().toBlocking().single(); + assertTrue(attempt2.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 + KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker); + attempt4.observe().toBlocking().single(); + 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()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test that the circuit-breaker is different between HystrixCommand objects with a different Hystrix. + */ + @Test + public void testCircuitBreakerAcrossMultipleCommandsAndDifferentDependency() { + TestCircuitBreaker circuitBreaker_one = new TestCircuitBreaker(); + TestCircuitBreaker circuitBreaker_two = new TestCircuitBreaker(); + /* fail 3 times, twice on one Hystrix, once on a different Hystrix ... circuit-breaker should NOT open */ + + // failure 1 + KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker_one); + attempt1.observe().toBlocking().single(); + assertTrue(attempt1.isResponseFromFallback()); + assertFalse(attempt1.isCircuitBreakerOpen()); + assertFalse(attempt1.isResponseShortCircuited()); + + // failure 2 with a different HystrixCommand implementation and different Hystrix + KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker_two); + attempt2.observe().toBlocking().single(); + assertTrue(attempt2.isResponseFromFallback()); + assertFalse(attempt2.isCircuitBreakerOpen()); + assertFalse(attempt2.isResponseShortCircuited()); + + // failure 3 but only 2nd of the Hystrix.ONE + KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker_one); + attempt3.observe().toBlocking().single(); + assertTrue(attempt3.isResponseFromFallback()); + assertFalse(attempt3.isResponseShortCircuited()); + + // it should remain 'closed' since we have only had 2 failures on Hystrix.ONE + assertFalse(attempt3.isCircuitBreakerOpen()); + + // this one should also remain closed as it only had 1 failure for Hystrix.TWO + assertFalse(attempt2.isCircuitBreakerOpen()); + + // attempt 4 (3rd attempt for Hystrix.ONE) + KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker_one); + attempt4.observe().toBlocking().single(); + // this should NOW flip to true as this is the 3rd failure for Hystrix.ONE + assertTrue(attempt3.isCircuitBreakerOpen()); + assertTrue(attempt3.isResponseFromFallback()); + assertFalse(attempt3.isResponseShortCircuited()); + + // Hystrix.TWO should still remain closed + assertFalse(attempt2.isCircuitBreakerOpen()); + + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(3, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(3, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker_one.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker_one.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker_two.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker_two.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test that the circuit-breaker being disabled doesn't wreak havoc. + */ + @Test + public void testExecutionSuccessWithCircuitBreakerDisabled() { + TestHystrixCommand command = new TestCommandWithoutCircuitBreaker(); + try { + assertEquals(true, command.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + + // we'll still get metrics ... just not the circuit breaker opening/closing + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test a command execution timeout where the command didn't implement getFallback. + */ + @Test + public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.SEMAPHORE); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + // e.printStackTrace(); + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + + assertTrue(command.isResponseTimedOut()); + assertFalse(command.isResponseFromFallback()); + assertFalse(command.isResponseRejected()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command implemented getFallback. + */ + @Test + public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.SEMAPHORE); + try { + assertEquals(false, command.observe().toBlocking().single()); + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertTrue(command.isResponseTimedOut()); + assertTrue(command.isResponseFromFallback()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command implemented getFallback but it fails. + */ + @Test + public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.SEMAPHORE); + 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 HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command didn't implement getFallback. + */ + @Test + public void testExecutionTimeoutWithNoFallbackUsingThreadIsolation() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.THREAD); + try { + command.observe().toBlocking().single(); + fail("we shouldn't get here"); + } catch (Exception e) { + // e.printStackTrace(); + if (e instanceof HystrixRuntimeException) { + HystrixRuntimeException de = (HystrixRuntimeException) e; + assertNotNull(de.getFallbackException()); + assertTrue(de.getFallbackException() instanceof UnsupportedOperationException); + assertNotNull(de.getImplementingClass()); + assertNotNull(de.getCause()); + assertTrue(de.getCause() instanceof TimeoutException); + } else { + fail("the exception should be HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + + assertTrue(command.isResponseTimedOut()); + assertFalse(command.isResponseFromFallback()); + assertFalse(command.isResponseRejected()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // thread isolated + assertTrue(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command implemented getFallback. + */ + @Test + public void testExecutionTimeoutWithFallbackUsingThreadIsolation() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.THREAD); + try { + assertEquals(false, command.observe().toBlocking().single()); + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertTrue(command.isResponseTimedOut()); + assertTrue(command.isResponseFromFallback()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // thread isolated + assertTrue(command.isExecutedInThread()); + } + + /** + * Test a command execution timeout where the command implemented getFallback but it fails. + */ + @Test + public void testExecutionTimeoutFallbackFailureUsingThreadIsolation() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.THREAD); + 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 HystrixRuntimeException"); + } + } + // the time should be 50+ since we timeout at 50ms + assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // thread isolated + assertTrue(command.isExecutedInThread()); + } + + /** + * Test that the circuit-breaker counts a command execution timeout as a 'timeout' and not just failure. + */ + @Test + public void testCircuitBreakerOnExecutionTimeout() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + try { + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + + command.observe().toBlocking().single(); + + assertTrue(command.isResponseFromFallback()); + assertFalse(command.isCircuitBreakerOpen()); + assertFalse(command.isResponseShortCircuited()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test that the command finishing AFTER a timeout (because thread continues in background) does not register a SUCCESS + */ + @Test + public void testCountersOnExecutionTimeout() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + try { + command.observe().toBlocking().single(); + + /* 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()); + + /* failure and timeout count should be the same as 'testCircuitBreakerOnExecutionTimeout' */ + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + + /* we should NOT have a 'success' counter */ + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a queued command execution timeout where the command didn't implement getFallback. + *

+ * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. + */ + @Test + public void testQueuedExecutionTimeoutWithNoFallback() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + try { + command.observe().toBlocking().toFuture().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()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback. + *

+ * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. + */ + @Test + public void testQueuedExecutionTimeoutWithFallback() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + try { + assertEquals(false, command.observe().toBlocking().toFuture().get()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback but it fails. + *

+ * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. + */ + @Test + public void testQueuedExecutionTimeoutFallbackFailure() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); + try { + command.observe().toBlocking().toFuture().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"); + } + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a queued command execution timeout where the command didn't implement getFallback. + *

+ * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. + */ + @Test + public void testObservedExecutionTimeoutWithNoFallback() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + 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()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback. + *

+ * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. + */ + @Test + public void testObservedExecutionTimeoutWithFallback() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + try { + assertEquals(false, command.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a queued command execution timeout where the command implemented getFallback but it fails. + *

+ * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. + */ + @Test + public void testObservedExecutionTimeoutFallbackFailure() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); + 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"); + } + } + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test that the circuit-breaker counts a command execution timeout as a 'timeout' and not just failure. + */ + @Test + public void testShortCircuitFallbackCounter() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + try { + new KnownFailureTestCommandWithFallback(circuitBreaker).observe().toBlocking().single(); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + KnownFailureTestCommandWithFallback command = new KnownFailureTestCommandWithFallback(circuitBreaker); + command.observe().toBlocking().single(); + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + // will be -1 because it never attempted execution + assertEquals(-1, command.getExecutionTimeInMilliseconds()); + assertTrue(command.isResponseShortCircuited()); + assertFalse(command.isResponseTimedOut()); + + // because it was short-circuited to a fallback we don't count an error + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testExecutionSemaphoreWithQueue() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + // single thread should work + try { + boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200).observe().toBlocking().toFuture().get(); + assertTrue(result); + } catch (Exception e) { + // we shouldn't fail on this one + throw new RuntimeException(e); + } + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TryableSemaphoreActual semaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + Runnable r = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + new TestSemaphoreCommand(circuitBreaker, semaphore, 200).observe().toBlocking().toFuture().get(); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + // 2 threads, the second should be rejected by the semaphore + Thread t1 = new Thread(r); + Thread t2 = new Thread(r); + + t1.start(); + t2.start(); + try { + t1.join(); + t2.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + if (!exceptionReceived.get()) { + fail("We expected an exception on the 2nd get"); + } + + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + // we don't have a fallback so threw an exception when rejected + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + // not a failure as the command never executed so can't fail + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + // no fallback failure as there isn't a fallback implemented + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + // we should have rejected via semaphore + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + // the rest should not be involved in this test + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testExecutionSemaphoreWithExecution() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + // single thread should work + try { + TestSemaphoreCommand command = new TestSemaphoreCommand(circuitBreaker, 1, 200); + boolean result = command.observe().toBlocking().single(); + assertFalse(command.isExecutedInThread()); + assertTrue(result); + } catch (Exception e) { + // we shouldn't fail on this one + throw new RuntimeException(e); + } + + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + final TryableSemaphoreActual semaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(1)); + + Runnable r = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200).observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + // 2 threads, the second should be rejected by the semaphore + Thread t1 = new Thread(r); + Thread t2 = new Thread(r); + + t1.start(); + t2.start(); + try { + t1.join(); + t2.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + 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)); + + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + // no failure ... we throw an exception because of rejection but the command does not fail execution + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + // there is no fallback implemented so no failure can occur on it + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + // we rejected via semaphore + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + // the rest should not be involved in this test + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testRejectedExecutionSemaphoreWithFallback() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + Runnable r = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + results.add(new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false).observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r); + Thread t2 = new Thread(r); + + t1.start(); + t2.start(); + try { + t1.join(); + t2.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + if (exceptionReceived.get()) { + fail("We should have received a fallback response"); + } + + // both threads should have returned values + assertEquals(2, results.size()); + // should contain both a true and false result + assertTrue(results.contains(Boolean.TRUE)); + assertTrue(results.contains(Boolean.FALSE)); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + // the rest should not be involved in this test + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + System.out.println("**** DONE"); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Tests that semaphores are counted separately for commands with unique keys + */ + @Test + public void testSemaphorePermitsInUse() { + 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() + 1); + + // used to signal that all command can finish + final CountDownLatch sharedLatch = new CountDownLatch(1); + + final Runnable sharedSemaphoreRunnable = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + public void run() { + try { + new LatchedSemaphoreCommand(circuitBreaker, sharedSemaphore, startLatch, sharedLatch).observe().toBlocking().single(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + // 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); + + // tracks failures to obtain semaphores + final AtomicInteger failureCount = new AtomicInteger(); + + final Thread isolatedThread = new Thread(new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + public void run() { + try { + new LatchedSemaphoreCommand(circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch).observe().toBlocking().single(); + } catch (Exception e) { + e.printStackTrace(); + failureCount.incrementAndGet(); + } + } + })); + + // verifies no permits in use before starting threads + assertEquals("wrong number of permits for shared semaphore", 0, sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("wrong number of permits for isolated semaphore", 0, isolatedSemaphore.getNumberOfPermitsUsed()); + + for (int i = 0; i < sharedThreadCount; i++) { + sharedSemaphoreThreads[i].start(); + } + isolatedThread.start(); + + // waits until all commands have started + try { + startLatch.await(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + // verifies that all semaphores are in use + assertEquals("wrong number of permits for shared semaphore", + sharedSemaphore.numberOfPermits.get().longValue(), sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("wrong number of permits for isolated semaphore", + isolatedSemaphore.numberOfPermits.get().longValue(), isolatedSemaphore.getNumberOfPermitsUsed()); + + // signals commands to finish + sharedLatch.countDown(); + isolatedLatch.countDown(); + + try { + for (int i = 0; i < sharedThreadCount; i++) { + sharedSemaphoreThreads[i].join(); + } + isolatedThread.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + // verifies no permits in use after finishing threads + assertEquals("wrong number of permits for shared semaphore", 0, sharedSemaphore.getNumberOfPermitsUsed()); + assertEquals("wrong number of permits for isolated semaphore", 0, isolatedSemaphore.getNumberOfPermitsUsed()); + + // verifies that some executions failed + final int expectedFailures = sharedSemaphore.getNumberOfPermitsUsed(); + assertEquals("failures expected but did not happen", expectedFailures, failureCount.get()); + } + + /** + * Test that HystrixOwner can be passed in dynamically. + */ + @Test + public void testDynamicOwner() { + try { + TestHystrixCommand command = new DynamicOwnerTestCommand(CommandGroupForUnitTest.OWNER_ONE); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(true, command.observe().toBlocking().single()); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test a successful command execution. + */ + @Test + public void testDynamicOwnerFails() { + try { + TestHystrixCommand command = new DynamicOwnerTestCommand(null); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(true, command.observe().toBlocking().single()); + fail("we should have thrown an exception as we need an owner"); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + // success if we get here + } + } + + /** + * Test that HystrixCommandKey can be passed in dynamically. + */ + @Test + public void testDynamicKey() { + try { + DynamicOwnerAndKeyTestCommand command1 = new DynamicOwnerAndKeyTestCommand(CommandGroupForUnitTest.OWNER_ONE, CommandKeyForUnitTest.KEY_ONE); + assertEquals(true, command1.observe().toBlocking().single()); + DynamicOwnerAndKeyTestCommand command2 = new DynamicOwnerAndKeyTestCommand(CommandGroupForUnitTest.OWNER_ONE, CommandKeyForUnitTest.KEY_TWO); + assertEquals(true, command2.observe().toBlocking().single()); + + // 2 different circuit breakers should be created + assertNotSame(command1.getCircuitBreaker(), command2.getCircuitBreaker()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCache1UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("A", f2.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // the second one should not have executed as it should have received the cached value instead + assertFalse(command2.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command1.getExecutionTimeInMilliseconds() > -1); + assertFalse(command1.isResponseFromCache()); + + // the execution log for command2 should show it came from cache + assertEquals(2, command2.getExecutionEvents().size()); // it will include the SUCCESS + RESPONSE_FROM_CACHE + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.RESPONSE_FROM_CACHE)); + assertTrue(command2.getExecutionTimeInMilliseconds() == -1); + assertTrue(command2.isResponseFromCache()); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test Request scoped caching doesn't prevent different ones from executing + */ + @Test + public void testRequestCache2UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "B"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command2.getExecutionTimeInMilliseconds() > -1); + assertFalse(command2.isResponseFromCache()); + + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCache3UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "B"); + SuccessfulCacheableCommand command3 = new SuccessfulCacheableCommand(circuitBreaker, true, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // but the 3rd should come from cache + assertFalse(command3.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command3 should show it came from cache + assertEquals(2, command3.getExecutionEvents().size()); // it will include the SUCCESS + RESPONSE_FROM_CACHE + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.RESPONSE_FROM_CACHE)); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + assertTrue(command3.isResponseFromCache()); + + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + System.out.println("executedCommand: " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /** + * Test Request scoped caching of commands so that a 2nd duplicate call doesn't execute but returns the previous Future + */ + @Test + public void testRequestCacheWithSlowExecution() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SlowCacheableCommand command1 = new SlowCacheableCommand(circuitBreaker, "A", 200); + SlowCacheableCommand command2 = new SlowCacheableCommand(circuitBreaker, "A", 100); + SlowCacheableCommand command3 = new SlowCacheableCommand(circuitBreaker, "A", 100); + SlowCacheableCommand command4 = new SlowCacheableCommand(circuitBreaker, "A", 100); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + Future f4 = command4.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f2.get()); + assertEquals("A", f3.get()); + assertEquals("A", f4.get()); + + assertEquals("A", f1.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // the second one should not have executed as it should have received the cached value instead + assertFalse(command2.executed); + assertFalse(command3.executed); + assertFalse(command4.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command1.getExecutionTimeInMilliseconds() > -1); + assertFalse(command1.isResponseFromCache()); + + // the execution log for command2 should show it came from cache + assertEquals(2, command2.getExecutionEvents().size()); // it will include the SUCCESS + RESPONSE_FROM_CACHE + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.RESPONSE_FROM_CACHE)); + assertTrue(command2.getExecutionTimeInMilliseconds() == -1); + assertTrue(command2.isResponseFromCache()); + + assertTrue(command3.isResponseFromCache()); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + assertTrue(command4.isResponseFromCache()); + assertTrue(command4.getExecutionTimeInMilliseconds() == -1); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + System.out.println("HystrixRequestLog: " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + assertFalse(command2.isExecutedInThread()); + assertFalse(command3.isExecutedInThread()); + assertFalse(command4.isExecutedInThread()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCache3UsingThreadIsolation() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + SuccessfulCacheableCommand command1 = new SuccessfulCacheableCommand(circuitBreaker, false, "A"); + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, false, "B"); + SuccessfulCacheableCommand command3 = new SuccessfulCacheableCommand(circuitBreaker, false, "A"); + + assertTrue(command1.isCommandRunningInThread()); + + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // this should also execute since we disabled the cache + assertTrue(command3.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command3 should show a SUCCESS + assertEquals(1, command3.getExecutionEvents().size()); + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // thread isolated + assertTrue(command1.isExecutedInThread()); + assertTrue(command2.isExecutedInThread()); + assertTrue(command3.isExecutedInThread()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCacheViaQueueUsingSemaphoreIsolation() { + 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 f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // but the 3rd should come from cache + assertFalse(command3.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command3 should show it comes from cache + assertEquals(2, command3.getExecutionEvents().size()); // it will include the SUCCESS + RESPONSE_FROM_CACHE + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.RESPONSE_FROM_CACHE)); + + assertTrue(command3.isResponseFromCache()); + assertTrue(command3.getExecutionTimeInMilliseconds() == -1); + + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + assertFalse(command2.isExecutedInThread()); + assertFalse(command3.isExecutedInThread()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCacheViaQueueUsingSemaphoreIsolation() { + 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 f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + + try { + assertEquals("A", f1.get()); + assertEquals("B", f2.get()); + assertEquals("A", f3.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertTrue(command1.executed); + // both should execute as they are different + assertTrue(command2.executed); + // this should also execute because caching is disabled + assertTrue(command3.executed); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command3 should show a SUCCESS + assertEquals(1, command3.getExecutionEvents().size()); + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + assertFalse(command2.isExecutedInThread()); + assertFalse(command3.isExecutedInThread()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testRequestCacheViaExecuteUsingSemaphoreIsolation() { + 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.observe().toBlocking().single(); + String f2 = command2.observe().toBlocking().single(); + String f3 = command3.observe().toBlocking().single(); + + 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); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command3 should show it comes from cache + assertEquals(2, command3.getExecutionEvents().size()); // it will include the SUCCESS + RESPONSE_FROM_CACHE + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.RESPONSE_FROM_CACHE)); + + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + assertFalse(command2.isExecutedInThread()); + assertFalse(command3.isExecutedInThread()); + } + + /** + * Test Request scoped caching with a mixture of commands + */ + @Test + public void testNoRequestCacheViaExecuteUsingSemaphoreIsolation() { + 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.observe().toBlocking().single(); + String f2 = command2.observe().toBlocking().single(); + String f3 = command3.observe().toBlocking().single(); + + 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); + + // the execution log for command1 should show a SUCCESS + assertEquals(1, command1.getExecutionEvents().size()); + assertTrue(command1.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command2 should show a SUCCESS + assertEquals(1, command2.getExecutionEvents().size()); + assertTrue(command2.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + // the execution log for command3 should show a SUCCESS + assertEquals(1, command3.getExecutionEvents().size()); + assertTrue(command3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command1.isExecutedInThread()); + assertFalse(command2.isExecutedInThread()); + assertFalse(command3.isExecutedInThread()); + } + + @Test + public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + NoRequestCacheTimeoutWithoutFallback r1 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + System.out.println("r1 value: " + r1.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseTimedOut()); + // what we want + } + + NoRequestCacheTimeoutWithoutFallback r2 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r2.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r2.isResponseTimedOut()); + // what we want + } + + NoRequestCacheTimeoutWithoutFallback r3 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + Future f3 = r3.observe().toBlocking().toFuture(); + try { + f3.get(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (ExecutionException e) { + e.printStackTrace(); + assertTrue(r3.isResponseTimedOut()); + // what we want + } + + Thread.sleep(500); // timeout on command is set to 200ms + + NoRequestCacheTimeoutWithoutFallback r4 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r4.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r4.isResponseTimedOut()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(4, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(4, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testRequestCacheOnTimeoutCausesNullPointerException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + // Expect it to time out - all results should be false + assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single()); + assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single()); // return from cache #1 + assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single()); // return from cache #2 + Thread.sleep(500); // timeout on command is set to 200ms + Boolean value = new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single(); // return from cache #3 + assertFalse(value); + RequestCacheNullPointerExceptionCase c = new RequestCacheNullPointerExceptionCase(circuitBreaker); + Future f = c.observe().toBlocking().toFuture(); // return from cache #4 + // the bug is that we're getting a null Future back, rather than a Future that returns false + assertNotNull(f); + assertFalse(f.get()); + + assertTrue(c.isResponseFromFallback()); + assertTrue(c.isResponseTimedOut()); + assertFalse(c.isFailedExecution()); + assertFalse(c.isResponseShortCircuited()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(4, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(5, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + HystrixExecutableInfo[] executeCommands = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixAsyncCommand[] {}); + + System.out.println(":executeCommands[0].getExecutionEvents()" + executeCommands[0].getExecutionEvents()); + assertEquals(2, executeCommands[0].getExecutionEvents().size()); + assertTrue(executeCommands[0].getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + assertTrue(executeCommands[0].getExecutionEvents().contains(HystrixEventType.TIMEOUT)); + assertTrue(executeCommands[0].getExecutionTimeInMilliseconds() > -1); + assertTrue(executeCommands[0].isResponseTimedOut()); + assertTrue(executeCommands[0].isResponseFromFallback()); + assertFalse(executeCommands[0].isResponseFromCache()); + + assertEquals(3, executeCommands[1].getExecutionEvents().size()); // it will include FALLBACK_SUCCESS/TIMEOUT + RESPONSE_FROM_CACHE + assertTrue(executeCommands[1].getExecutionEvents().contains(HystrixEventType.RESPONSE_FROM_CACHE)); + assertTrue(executeCommands[1].getExecutionTimeInMilliseconds() == -1); + assertTrue(executeCommands[1].isResponseFromCache()); + assertTrue(executeCommands[1].isResponseTimedOut()); + assertTrue(executeCommands[1].isResponseFromFallback()); + } + + // + @Test + public void testRequestCacheOnTimeoutThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + RequestCacheTimeoutWithoutFallback r1 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + System.out.println("r1 value: " + r1.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r1.isResponseTimedOut()); + // what we want + } + + RequestCacheTimeoutWithoutFallback r2 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r2.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r2.isResponseTimedOut()); + // what we want + } + + RequestCacheTimeoutWithoutFallback r3 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + Future f3 = r3.observe().toBlocking().toFuture(); + try { + f3.get(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (ExecutionException e) { + e.printStackTrace(); + assertTrue(r3.isResponseTimedOut()); + // what we want + } + + Thread.sleep(500); // timeout on command is set to 200ms + + RequestCacheTimeoutWithoutFallback r4 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); + try { + r4.observe().toBlocking().single(); + // we should have thrown an exception + fail("expected a timeout"); + } catch (HystrixRuntimeException e) { + assertTrue(r4.isResponseTimedOut()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testRequestCacheOnThreadRejectionThrowsException() throws Exception { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CountDownLatch completionLatch = new CountDownLatch(1); + RequestCacheThreadRejectionWithoutFallback r1 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r1: " + r1.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + e.printStackTrace(); + assertTrue(r1.isResponseRejected()); + // what we want + } + + RequestCacheThreadRejectionWithoutFallback r2 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r2: " + r2.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r2.isResponseRejected()); + // what we want + } + + RequestCacheThreadRejectionWithoutFallback r3 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("f3: " + r3.observe().toBlocking().toFuture().get()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (ExecutionException e) { + assertTrue(r3.isResponseRejected()); + assertTrue(e.getCause() instanceof HystrixRuntimeException); + } + + // let the command finish (only 1 should actually be blocked on this due to the response cache) + completionLatch.countDown(); + + // then another after the command has completed + RequestCacheThreadRejectionWithoutFallback r4 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); + try { + System.out.println("r4: " + r4.observe().toBlocking().single()); + // we should have thrown an exception + fail("expected a rejection"); + } catch (HystrixRuntimeException e) { + // e.printStackTrace(); + assertTrue(r4.isResponseRejected()); + assertFalse(r4.isResponseFromFallback()); + // what we want + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(3, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, circuitBreaker.metrics.getHealthCounts().getErrorPercentage()); + + // assertEquals(4, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + } + + /** + * Test that we can do basic execution without a RequestVariable being initialized. + */ + @Test + public void testBasicExecutionWorksWithoutRequestVariable() { + try { + /* force the RequestVariable to not be initialized */ + HystrixRequestContext.setContextOnCurrentThread(null); + + TestHystrixCommand command = new SuccessfulTestCommand(); + assertEquals(true, command.observe().toBlocking().single()); + + TestHystrixCommand command2 = new SuccessfulTestCommand(); + assertEquals(true, command2.observe().toBlocking().toFuture().get()); + + // we should be able to execute without a RequestVariable if ... + // 1) We don't have a cacheKey + // 2) We don't ask for the RequestLog + // 3) We don't do collapsing + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception => " + e.getMessage()); + } + } + + /** + * Test that if we try and execute a command with a cacheKey without initializing RequestVariable that it gives an error. + */ + @Test + public void testCacheKeyExecutionRequiresRequestVariable() { + try { + /* force the RequestVariable to not be initialized */ + HystrixRequestContext.setContextOnCurrentThread(null); + + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + SuccessfulCacheableCommand command = new SuccessfulCacheableCommand(circuitBreaker, true, "one"); + assertEquals(true, command.observe().toBlocking().single()); + + SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "two"); + assertEquals(true, command2.observe().toBlocking().toFuture().get()); + + fail("We expect an exception because cacheKey requires RequestVariable."); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaExecuteInThread() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + try { + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().single(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (HystrixBadRequestException e) { + // success + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaQueueInThread() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + try { + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().toFuture().get(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (ExecutionException e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixBadRequestException) { + // success + } else { + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + } + + /** + * Test that BadRequestException behavior works the same on a cached response. + */ + @Test + public void testBadRequestExceptionViaQueueInThreadOnResponseFromCache() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + + // execute once to cache the value + try { + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().single(); + } catch (Throwable e) { + // ignore + } + + try { + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().toFuture().get(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (ExecutionException e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixBadRequestException) { + // success + } else { + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaExecuteInSemaphore() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + try { + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE).observe().toBlocking().single(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (HystrixBadRequestException e) { + // success + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + } + + /** + * Test that a BadRequestException can be thrown and not count towards errors and bypasses fallback. + */ + @Test + public void testBadRequestExceptionViaQueueInSemaphore() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + try { + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE).observe().toBlocking().toFuture().get(); + fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); + } catch (ExecutionException e) { + e.printStackTrace(); + if (e.getCause() instanceof HystrixBadRequestException) { + // success + } else { + fail("We expect a " + HystrixBadRequestException.class.getSimpleName() + " but got a " + e.getClass().getSimpleName()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + } + + /** + * Test a checked Exception being thrown + */ + @Test + public void testCheckedExceptionViaExecute() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); + try { + command.observe().toBlocking().single(); + fail("we expect to receive a " + Exception.class.getSimpleName()); + } catch (Exception e) { + assertEquals("simulated checked exception message", e.getCause().getMessage()); + } + + assertEquals("simulated checked exception message", command.getFailedExecutionException().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + } + + /** + * 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 t = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + try { + command.observe().subscribe(new Observer() { + + @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()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a java.lang.Error being thrown + */ + @Test + public void testErrorThrownViaExecute() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker); + try { + command.observe().toBlocking().single(); + 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("simulated java.lang.Error message", e.getCause().getCause().getMessage()); + } + + assertEquals("simulated java.lang.Error message", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a java.lang.Error being thrown + */ + @Test + public void testErrorThrownViaQueue() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker); + try { + command.observe().toBlocking().toFuture().get(); + fail("we expect to receive an Exception"); + } catch (Exception e) { + // one cause down from ExecutionException to HystrixRuntime + // then 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 ExecutionException -> HystrixRuntimeException -> wrapper Exception -> actual Error + assertEquals("simulated java.lang.Error message", e.getCause().getCause().getCause().getMessage()); + } + + assertEquals("simulated java.lang.Error message", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a java.lang.Error being thrown + * + * @throws InterruptedException + */ + @Test + public void testErrorThrownViaObserve() throws InterruptedException { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker); + final AtomicReference t = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + try { + command.observe().subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + t.set(e); + latch.countDown(); + } + + @Override + public void onNext(Boolean args) { + + } + + }); + } catch (Exception e) { + e.printStackTrace(); + fail("we should not get anything thrown, it should be emitted via the Observer#onError method"); + } + + latch.await(1, TimeUnit.SECONDS); + assertNotNull(t.get()); + t.get().printStackTrace(); + + assertTrue(t.get() instanceof HystrixRuntimeException); + // the actual error is an extra cause level deep because Hystrix needs to wrap Throwable/Error as it's public + // methods only support Exception and it's not a strong enough reason to break backwards compatibility and jump to version 2.x + assertEquals("simulated java.lang.Error message", t.get().getCause().getCause().getMessage()); + assertEquals("simulated java.lang.Error message", command.getFailedExecutionException().getCause().getMessage()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isFailedExecution()); + + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on successful execution + */ + @Test + public void testExecutionHookSuccessfulCommand() { + //test with observe().toBlocking().single() + TestHystrixCommand command = new SuccessfulTestCommand(); + command.observe().toBlocking().single(); + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we expect a successful response from run() + assertNotNull(command.builder.executionHook.runSuccessResponse); + // we do not expect an exception + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should not be run as we were successful + assertEquals(0, command.builder.executionHook.startFallback.get()); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().single() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().single() since run() succeeded + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception since run() succeeded + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // test with observe().toBlocking().toFuture() + command = new SuccessfulTestCommand(); + try { + command.observe().toBlocking().toFuture().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we expect a successful response from run() + assertNotNull(command.builder.executionHook.runSuccessResponse); + // we do not expect an exception + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should not be run as we were successful + assertEquals(0, command.builder.executionHook.startFallback.get()); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().toFuture() since run() succeeded + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception since run() succeeded + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on successful execution with "fire and forget" approach + */ + @Test + public void testExecutionHookSuccessfulCommandViaFireAndForget() { + TestHystrixCommand command = new SuccessfulTestCommand(); + try { + // do not block on "get()" ... fire this asynchronously + command.observe().toBlocking().toFuture(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // wait for command to execute without calling get on the future + while (!command.isExecutionComplete()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException("interrupted"); + } + } + + /* All the hooks should still work even though we didn't call get() on the future */ + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we expect a successful response from run() + assertNotNull(command.builder.executionHook.runSuccessResponse); + // we do not expect an exception + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should not be run as we were successful + assertEquals(0, command.builder.executionHook.startFallback.get()); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().toFuture() since run() succeeded + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception since run() succeeded + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on successful execution with multiple get() calls to Future + */ + @Test + public void testExecutionHookSuccessfulCommandWithMultipleGetsOnFuture() { + TestHystrixCommand command = new SuccessfulTestCommand(); + try { + Future f = command.observe().toBlocking().toFuture(); + f.get(); + f.get(); + f.get(); + f.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + /* Despite multiple calls to get() we should only have 1 call to the hooks. */ + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we expect a successful response from run() + assertNotNull(command.builder.executionHook.runSuccessResponse); + // we do not expect an exception + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should not be run as we were successful + assertEquals(0, command.builder.executionHook.startFallback.get()); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().toFuture() since run() succeeded + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception since run() succeeded + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on failed execution without a fallback + */ + @Test + public void testExecutionHookRunFailureWithoutFallback() { + // test with observe().toBlocking().single() + TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); + try { + command.observe().toBlocking().single(); + fail("Expecting exception"); + } catch (Exception e) { + // ignore + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response + assertNull(command.builder.executionHook.runSuccessResponse); + // we should have an exception + assertNotNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run since run() failed + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since fallback is not implemented + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since it's not implemented and throws an exception + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().single() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response from observe().toBlocking().single() since we do not have a fallback and run() failed + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should have an exception since run() failed + assertNotNull(command.builder.executionHook.endExecuteFailureException); + // run() failure + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // test with observe().toBlocking().toFuture() + command = new UnknownFailureTestCommandWithoutFallback(); + try { + command.observe().toBlocking().toFuture().get(); + fail("Expecting exception"); + } catch (Exception e) { + // ignore + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response + assertNull(command.builder.executionHook.runSuccessResponse); + // we should have an exception + assertNotNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run since run() failed + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since fallback is not implemented + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since it's not implemented and throws an exception + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response from observe().toBlocking().toFuture() since we do not have a fallback and run() failed + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should have an exception since run() failed + assertNotNull(command.builder.executionHook.endExecuteFailureException); + // run() failure + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + + } + + /** + * Execution hook on failed execution with a fallback + */ + @Test + public void testExecutionHookRunFailureWithFallback() { + // test with observe().toBlocking().single() + TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + command.observe().toBlocking().single(); + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response from run since run() failed + assertNull(command.builder.executionHook.runSuccessResponse); + // we should have an exception since run() failed + assertNotNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run since run() failed + assertEquals(1, command.builder.executionHook.startFallback.get()); + // a response since fallback is implemented + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it's implemented and succeeds + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().single() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().single() since we expect a fallback despite failure of run() + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception because we expect a fallback + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // test with observe().toBlocking().toFuture() + command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); + try { + command.observe().toBlocking().toFuture().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response from run since run() failed + assertNull(command.builder.executionHook.runSuccessResponse); + // we should have an exception since run() failed + assertNotNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run since run() failed + assertEquals(1, command.builder.executionHook.startFallback.get()); + // a response since fallback is implemented + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it's implemented and succeeds + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().toFuture() since we expect a fallback despite failure of run() + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception because we expect a fallback + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on failed execution with a fallback failure + */ + @Test + public void testExecutionHookRunFailureWithFallbackFailure() { + // test with observe().toBlocking().single() + TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); + try { + command.observe().toBlocking().single(); + fail("Expecting exception"); + } catch (Exception e) { + // ignore + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response because run() and fallback fail + assertNull(command.builder.executionHook.runSuccessResponse); + // we should have an exception because run() and fallback fail + assertNotNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run since run() failed + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since fallback fails + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since it's implemented but fails + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().single() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response because run() and fallback fail + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should have an exception because run() and fallback fail + assertNotNull(command.builder.executionHook.endExecuteFailureException); + // run() failure + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // test with observe().toBlocking().toFuture() + command = new KnownFailureTestCommandWithFallbackFailure(); + try { + command.observe().toBlocking().toFuture().get(); + fail("Expecting exception"); + } catch (Exception e) { + // ignore + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response because run() and fallback fail + assertNull(command.builder.executionHook.runSuccessResponse); + // we should have an exception because run() and fallback fail + assertNotNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run since run() failed + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since fallback fails + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since it's implemented but fails + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response because run() and fallback fail + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should have an exception because run() and fallback fail + assertNotNull(command.builder.executionHook.endExecuteFailureException); + // run() failure + assertEquals(FailureType.COMMAND_EXCEPTION, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on timeout without a fallback + */ + @Test + public void testExecutionHookTimeoutWithoutFallback() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + try { + System.out.println("start at : " + System.currentTimeMillis()); + command.observe().toBlocking().toFuture().get(); + fail("Expecting exception"); + } catch (Exception e) { + // ignore + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response because of timeout and no fallback + assertNull(command.builder.executionHook.runSuccessResponse); + // we should not have an exception because run() didn't fail, it timed out + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run due to timeout + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since no fallback + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since no fallback implementation + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // execution occurred + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response because of timeout and no fallback + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should have an exception because of timeout and no fallback + assertNotNull(command.builder.executionHook.endExecuteFailureException); + // timeout failure + assertEquals(FailureType.TIMEOUT, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + + // we need to wait for the thread to complete before the onThreadComplete hook will be called + // try { + // Thread.sleep(400); + // } catch (InterruptedException e) { + // // ignore + // } + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on timeout with a fallback + */ + @Test + public void testExecutionHookTimeoutWithFallback() { + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + try { + command.observe().toBlocking().toFuture().get(); + } catch (Exception e) { + throw new RuntimeException("not expecting", e); + } + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we should not have a response because of timeout + assertNull(command.builder.executionHook.runSuccessResponse); + // we should not have an exception because run() didn't fail, it timed out + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run due to timeout + assertEquals(1, command.builder.executionHook.startFallback.get()); + // response since we have a fallback + assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + // null since fallback succeeds + assertNull(command.builder.executionHook.fallbackFailureException); + + // execution occurred + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response because of fallback + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception because of fallback + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + // assertEquals(1, command.builder.executionHook.threadStart.get()); + + // we need to wait for the thread to complete before the onThreadComplete hook will be called + // try { + // Thread.sleep(400); + // } catch (InterruptedException e) { + // // ignore + // } + // assertEquals(1, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on rejected with a fallback + */ + /* + * @Test + * public void testExecutionHookRejectedWithFallback() { + * TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + * SingleThreadedPool pool = new SingleThreadedPool(1); + * + * try { + * // fill the queue + * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe().toBlocking().toFuture(); + * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe().toBlocking().toFuture(); + * } catch (Exception e) { + * // ignore + * } + * + * TestCommandRejection command = new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); + * try { + * // now execute one that will be rejected + * command.observe().toBlocking().toFuture().get(); + * } catch (Exception e) { + * throw new RuntimeException("not expecting", e); + * } + * + * assertTrue(command.isResponseRejected()); + * + * // the run() method should not run as we're rejected + * assertEquals(0, command.builder.executionHook.startRun.get()); + * // we should not have a response because of rejection + * assertNull(command.builder.executionHook.runSuccessResponse); + * // we should not have an exception because we didn't run + * assertNull(command.builder.executionHook.runFailureException); + * + * // the fallback() method should be run due to rejection + * assertEquals(1, command.builder.executionHook.startFallback.get()); + * // response since we have a fallback + * assertNotNull(command.builder.executionHook.fallbackSuccessResponse); + * // null since fallback succeeds + * assertNull(command.builder.executionHook.fallbackFailureException); + * + * // execution occurred + * assertEquals(1, command.builder.executionHook.startExecute.get()); + * // we should have a response because of fallback + * assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + * // we should not have an exception because of fallback + * assertNull(command.builder.executionHook.endExecuteFailureException); + * + * // thread execution + * // assertEquals(0, command.builder.executionHook.threadStart.get()); + * // assertEquals(0, command.builder.executionHook.threadComplete.get()); + * } + */ + /** + * Execution hook on short-circuit with a fallback + */ + @Test + public void testExecutionHookShortCircuitedWithFallbackViaQueue() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); + try { + // now execute one that will be short-circuited + command.observe().toBlocking().toFuture().get(); + fail("we expect an error as there is no fallback"); + } catch (Exception e) { + // expecting + } + + assertTrue(command.isResponseShortCircuited()); + + // the run() method should not run as we're rejected + assertEquals(0, command.builder.executionHook.startRun.get()); + // we should not have a response because of rejection + assertNull(command.builder.executionHook.runSuccessResponse); + // we should not have an exception because we didn't run + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run due to rejection + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since we don't have a fallback + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since fallback fails and throws an exception + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // execution occurred + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response because fallback fails + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we won't have an exception because short-circuit doesn't have one + assertNull(command.builder.executionHook.endExecuteFailureException); + // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(0, command.builder.executionHook.threadStart.get()); + // assertEquals(0, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on short-circuit with a fallback + */ + @Test + public void testExecutionHookShortCircuitedWithFallbackViaExecute() { + TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); + KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); + try { + // now execute one that will be short-circuited + command.observe().toBlocking().single(); + fail("we expect an error as there is no fallback"); + } catch (Exception e) { + // expecting + } + + assertTrue(command.isResponseShortCircuited()); + + // the run() method should not run as we're rejected + assertEquals(0, command.builder.executionHook.startRun.get()); + // we should not have a response because of rejection + assertNull(command.builder.executionHook.runSuccessResponse); + // we should not have an exception because we didn't run + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should be run due to rejection + assertEquals(1, command.builder.executionHook.startFallback.get()); + // no response since we don't have a fallback + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since fallback fails and throws an exception + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // execution occurred + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response because fallback fails + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we won't have an exception because short-circuit doesn't have one + assertNull(command.builder.executionHook.endExecuteFailureException); + // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT + assertEquals(FailureType.SHORTCIRCUIT, command.builder.executionHook.endExecuteFailureType); + + // thread execution + // assertEquals(0, command.builder.executionHook.threadStart.get()); + // assertEquals(0, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on successful execution with semaphore isolation + */ + @Test + public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { + // test with observe().toBlocking().single() + TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); + command.observe().toBlocking().single(); + + assertFalse(command.isExecutedInThread()); + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we expect a successful response from run() + assertNotNull(command.builder.executionHook.runSuccessResponse); + // we do not expect an exception + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should not be run as we were successful + assertEquals(0, command.builder.executionHook.startFallback.get()); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().single() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().single() since run() succeeded + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception since run() succeeded + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + + // test with observe().toBlocking().toFuture() + command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); + try { + command.observe().toBlocking().toFuture().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + assertFalse(command.isExecutedInThread()); + + // the run() method should run as we're not short-circuited or rejected + assertEquals(1, command.builder.executionHook.startRun.get()); + // we expect a successful response from run() + assertNotNull(command.builder.executionHook.runSuccessResponse); + // we do not expect an exception + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should not be run as we were successful + assertEquals(0, command.builder.executionHook.startFallback.get()); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // null since it didn't run + assertNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().toFuture() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should have a response from observe().toBlocking().toFuture() since run() succeeded + assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); + // we should not have an exception since run() succeeded + assertNull(command.builder.executionHook.endExecuteFailureException); + + // thread execution + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Execution hook on successful execution with semaphore isolation + */ + @Test + public void testExecutionHookFailureWithSemaphoreIsolation() { + // test with observe().toBlocking().single() + final TryableSemaphoreActual semaphore = + new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(0)); + + TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 200); + try { + command.observe().toBlocking().single(); + fail("we expect a failure"); + } catch (Exception e) { + // expected + } + + assertFalse(command.isExecutedInThread()); + assertTrue(command.isResponseRejected()); + + // the run() method should not run as we are rejected + assertEquals(0, command.builder.executionHook.startRun.get()); + // null as run() does not get invoked + assertNull(command.builder.executionHook.runSuccessResponse); + // null as run() does not get invoked + assertNull(command.builder.executionHook.runFailureException); + + // the fallback() method should run because of rejection + assertEquals(1, command.builder.executionHook.startFallback.get()); + // null since there is no fallback + assertNull(command.builder.executionHook.fallbackSuccessResponse); + // not null since the fallback is not implemented + assertNotNull(command.builder.executionHook.fallbackFailureException); + + // the observe().toBlocking().single() method was used + assertEquals(1, command.builder.executionHook.startExecute.get()); + // we should not have a response since fallback has nothing + assertNull(command.builder.executionHook.endExecuteSuccessResponse); + // we won't have an exception because rejection doesn't have one + assertNull(command.builder.executionHook.endExecuteFailureException); + // but we do expect to receive a onError call with FailureType.SHORTCIRCUIT + assertEquals(FailureType.REJECTED_SEMAPHORE_EXECUTION, command.builder.executionHook.endExecuteFailureType); + + // thread execution + assertEquals(0, command.builder.executionHook.threadStart.get()); + assertEquals(0, command.builder.executionHook.threadComplete.get()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * Test a command execution that fails but has a fallback. + */ + @Test + public void testExecutionFailureWithFallbackImplementedButDisabled() { + TestHystrixCommand commandEnabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), true); + try { + assertEquals(false, commandEnabled.observe().toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + fail("We should have received a response from the fallback."); + } + + TestHystrixCommand commandDisabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), false); + try { + assertEquals(false, commandDisabled.observe().toBlocking().single()); + fail("expect exception thrown"); + } catch (Exception e) { + // expected + } + + assertEquals("we failed with a simulated issue", commandDisabled.getFailedExecutionException().getMessage()); + + assertTrue(commandDisabled.isFailedExecution()); + + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, commandDisabled.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, commandDisabled.builder.metrics.getHealthCounts().getErrorPercentage()); + + // assertEquals(2, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + } + + /** + * Test that we can still use thread isolation if desired. + */ + @Test(timeout = 500) + public void testSynchronousExecutionTimeoutValueViaExecute() { + HystrixAsyncCommand.Setter properties = HystrixAsyncCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + .withExecutionIsolationThreadTimeoutInMilliseconds(50)); + + System.out.println(">>>>> Begin: " + System.currentTimeMillis()); + + HystrixAsyncCommand command = new HystrixAsyncCommand(properties) { + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + p.onSuccess("hello"); + } + + }, Schedulers.immediate()); + } + + @Override + protected HystrixFuture getFallback() { + if (isResponseTimedOut()) { + return HystrixFutureUtil.just("timed-out"); + } else { + return HystrixFutureUtil.just("abc"); + } + } + }; + + System.out.println(">>>>> Start: " + System.currentTimeMillis()); + String value = command.observe().toBlocking().single(); + System.out.println(">>>>> End: " + System.currentTimeMillis()); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + // Thread isolated + assertTrue(command.isExecutedInThread()); + } + + @Test(timeout = 500) + public void testSynchronousExecutionUsingThreadIsolationTimeoutValueViaObserve() { + HystrixAsyncCommand.Setter properties = HystrixAsyncCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + .withExecutionIsolationThreadTimeoutInMilliseconds(50)); + + HystrixAsyncCommand command = new HystrixAsyncCommand(properties) { + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + p.onSuccess("hello"); + } + + }, Schedulers.immediate()); + } + + @Override + protected HystrixFuture getFallback() { + if (isResponseTimedOut()) { + return HystrixFutureUtil.just("timed-out"); + } else { + return HystrixFutureUtil.just("abc"); + } + } + }; + + String value = command.observe().toBlocking().last(); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + // Thread isolated + assertTrue(command.isExecutedInThread()); + } + + @Test(timeout = 500) + public void testAsyncExecutionTimeoutValueViaObserve() { + HystrixAsyncCommand.Setter properties = HystrixAsyncCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("TestKey")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withExecutionIsolationThreadTimeoutInMilliseconds(50)); + + HystrixAsyncCommand command = new HystrixAsyncCommand(properties) { + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + System.out.println("********** interrupted on timeout"); + e.printStackTrace(); + } + // should never reach here + p.onSuccess("hello"); + } + + }, Schedulers.newThread()); + } + + @Override + protected HystrixFuture getFallback() { + if (isResponseTimedOut()) { + return HystrixFutureUtil.just("timed-out"); + } else { + return HystrixFutureUtil.just("abc"); + } + } + }; + + String value = command.observe().toBlocking().last(); + assertTrue(command.isResponseTimedOut()); + assertEquals("expected fallback value", "timed-out", value); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * See https://github.com/Netflix/Hystrix/issues/212 + */ + @Test + public void testObservableTimeoutNoFallbackThreadContext() { + TestSubscriber ts = new TestSubscriber(); + + final AtomicReference onErrorThread = new AtomicReference(); + final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); + + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); + command.toObservable().doOnError(new Action1() { + + @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 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()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + /** + * See https://github.com/Netflix/Hystrix/issues/212 + */ + @Test + public void testObservableTimeoutFallbackThreadContext() { + TestSubscriber ts = new TestSubscriber(); + + final AtomicReference onErrorThread = new AtomicReference(); + final AtomicBoolean isRequestContextInitialized = new AtomicBoolean(); + + TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); + command.toObservable().doOnNext(new Action1() { + + @Override + public void call(Boolean t1) { + System.out.println("onNext: " + t1); + System.out.println("onNext Thread: " + Thread.currentThread()); + System.out.println("ThreadContext in onNext: " + HystrixRequestContext.isCurrentThreadInitialized()); + onErrorThread.set(Thread.currentThread()); + isRequestContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + } + + }).subscribe(ts); + + ts.awaitTerminalEvent(); + + System.out.println("events: " + ts.getOnNextEvents()); + + assertTrue(isRequestContextInitialized.get()); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); + + List onNexts = ts.getOnNextEvents(); + assertEquals(1, onNexts.size()); + assertFalse(onNexts.get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isResponseTimedOut()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } + + @Test + public void testRejectedViaSemaphoreIsolation() { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(2); + final List executionThreads = Collections.synchronizedList(new ArrayList(2)); + final List responseThreads = Collections.synchronizedList(new ArrayList(2)); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + + Runnable r = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + try { + executionThreads.add(Thread.currentThread()); + results.add(new TestSemaphoreCommand(circuitBreaker, 1, 200).toObservable().map(new Func1() { + + @Override + public Boolean call(Boolean b) { + responseThreads.add(Thread.currentThread()); + return b; + } + + }).toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r); + Thread t2 = new Thread(r); + + t1.start(); + t2.start(); + try { + t1.join(); + t2.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + // one thread should have returned values + assertEquals(1, results.size()); + assertTrue(results.contains(Boolean.TRUE)); + // the other thread should have thrown an Exception + assertTrue(exceptionReceived.get()); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + // the rest should not be involved in this test + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + System.out.println("**** DONE"); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + @Test + public void testRejectedViaThreadIsolation() throws InterruptedException { + final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); + final ArrayBlockingQueue results = new ArrayBlockingQueue(10); + final List executionThreads = Collections.synchronizedList(new ArrayList(20)); + final List responseThreads = Collections.synchronizedList(new ArrayList(10)); + + final AtomicBoolean exceptionReceived = new AtomicBoolean(); + final CountDownLatch scheduleLatch = new CountDownLatch(2); + final CountDownLatch successLatch = new CountDownLatch(1); + final AtomicInteger count = new AtomicInteger(); + + Runnable r = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { + + @Override + public void run() { + final boolean shouldExecute = count.incrementAndGet() < 3; + try { + executionThreads.add(Thread.currentThread()); + results.add(new TestThreadIsolationWithSemaphoreSetSmallCommand(circuitBreaker, 2, new Action0() { + + @Override + public void call() { + // make sure it's deterministic and we put 2 threads into the pool before the 3rd is submitted + if (shouldExecute) { + try { + scheduleLatch.countDown(); + successLatch.await(); + } catch (InterruptedException e) { + } + } + } + + }).toObservable().map(new Func1() { + + @Override + public Boolean call(Boolean b) { + responseThreads.add(Thread.currentThread()); + return b; + } + + }).finallyDo(new Action0() { + + @Override + public void call() { + if (!shouldExecute) { + // the final thread that shouldn't execute releases the latch once it has run + // so it is deterministic that the other two fill the thread pool until this one rejects + successLatch.countDown(); + } + } + + }).toBlocking().single()); + } catch (Exception e) { + e.printStackTrace(); + exceptionReceived.set(true); + } + } + + }); + + // 2 threads, the second should be rejected by the semaphore and return fallback + Thread t1 = new Thread(r); + Thread t2 = new Thread(r); + Thread t3 = new Thread(r); + + t1.start(); + t2.start(); + // wait for the previous 2 thread to be running before starting otherwise it can race + scheduleLatch.await(500, TimeUnit.MILLISECONDS); + t3.start(); + try { + t1.join(); + t2.join(); + t3.join(); + } catch (Exception e) { + e.printStackTrace(); + fail("failed waiting on threads"); + } + + // we should have 2 of the 3 return results + assertEquals(2, results.size()); + // the other thread should have thrown an Exception + assertTrue(exceptionReceived.get()); + + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + // the rest should not be involved in this test + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + } + + /* ******************************************************************************************************** */ + /* *************************************** Request Context Testing Below ********************************** */ + /* ******************************************************************************************************** */ + + private RequestContextTestResults testRequestContextOnSuccess(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + p.onSuccess(true); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnNextEvents().size()); + assertTrue(results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnGracefulFailure(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + p.onError(new RuntimeException("graceful onError")); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnErrorEvents().size()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnBadFailure(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + throw new RuntimeException("bad onError"); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnErrorEvents().size()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + s.onError(new RuntimeException("onError")); + } + + }, userScheduler); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onSuccess(false); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(0, results.ts.getOnErrorEvents().size()); + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(isolation) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(0)) + .setThreadPool(new HystrixThreadPool() { + + @Override + public ThreadPoolExecutor getExecutor() { + return null; + } + + @Override + public void markThreadExecution() { + + } + + @Override + public void markThreadCompletion() { + + } + + @Override + public boolean isQueueSpaceAvailable() { + // always return false so we reject everything + return false; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + })) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + s.onError(new RuntimeException("onError")); + } + + }, userScheduler); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onSuccess(false); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(0, results.ts.getOnErrorEvents().size()); + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() == -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseRejected()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + if (isolation == ExecutionIsolationStrategy.SEMAPHORE) { + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + } else { + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + } + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(isolation)) + .setCircuitBreaker(new TestCircuitBreaker().setForceShortCircuit(true))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + s.onError(new RuntimeException("onError")); + } + + }, userScheduler); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onSuccess(false); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(0, results.ts.getOnErrorEvents().size()); + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() == -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseShortCircuited()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnTimeout(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionIsolationThreadTimeoutInMilliseconds(50))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore the interrupted exception + } + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Run => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnErrorEvents().size()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseTimedOut()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private RequestContextTestResults testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy isolation, final Scheduler userScheduler) { + final RequestContextTestResults results = new RequestContextTestResults(); + TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionIsolationThreadTimeoutInMilliseconds(50))) { + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore the interrupted exception + } + } + + }, userScheduler); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise s) { + results.isContextInitialized.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.originThread.set(Thread.currentThread()); + s.onSuccess(false); + } + + }, userScheduler); + } + + }; + + results.command = command; + + command.toObservable().doOnEach(new Action1>() { + + @Override + public void call(Notification n) { + System.out.println("timeoutWithFallback notification: " + n + " " + Thread.currentThread()); + results.isContextInitializedObserveOn.set(HystrixRequestContext.isCurrentThreadInitialized()); + results.observeOnThread.set(Thread.currentThread()); + } + + }).subscribe(results.ts); + results.ts.awaitTerminalEvent(); + + System.out.println("Fallback => Initialized: " + results.isContextInitialized.get() + " Thread: " + results.originThread.get()); + System.out.println("Observed => Initialized: " + results.isContextInitializedObserveOn.get() + " Thread: " + results.observeOnThread.get()); + + assertEquals(1, results.ts.getOnNextEvents().size()); + assertEquals(false, results.ts.getOnNextEvents().get(0)); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertFalse(command.isSuccessfulExecution()); + assertTrue(command.isResponseTimedOut()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + return results; + } + + private final class RequestContextTestResults { + volatile TestHystrixCommand command; + final AtomicReference originThread = new AtomicReference(); + final AtomicBoolean isContextInitialized = new AtomicBoolean(); + TestSubscriber ts = new TestSubscriber(); + final AtomicBoolean isContextInitializedObserveOn = new AtomicBoolean(); + final AtomicReference observeOnThread = new AtomicReference(); + } + + /* *************************************** testSuccessfuleRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testSuccessfulRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testSuccessfulRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testSuccessfulRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testSuccessfulRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testSuccessfulRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testSuccessfulRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnSuccess(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testGracefulFailureRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testGracefulFailureRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testGracefulFailureRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testGracefulFailureRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testGracefulFailureRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testGracefulFailureRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testGracefulFailureRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnGracefulFailure(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testBadFailureRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testBadFailureRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testBadFailureRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testBadFailureRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testBadFailureRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testBadFailureRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testBadFailureRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnBadFailure(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testFailureWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testFailureWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testFailureWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("hystrix-OWNER_ONE")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testFailureWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testFailureWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnFailureWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testRejectionWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testRejectionWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // fallback is performed by the calling thread + + assertTrue(results.isContextInitializedObserveOn.get()); + System.out.println("results.observeOnThread.get(): " + results.observeOnThread.get() + " " + Thread.currentThread()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // rejected so we stay on calling thread + + // thread isolated so even though we're rejected we mark that it attempted execution in a thread + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); + + // thread isolated so even though we're rejected we mark that it attempted execution in a thread + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testRejectionWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnRejectionWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler for getFallback + + // thread isolated so even though we're rejected we mark that it attempted execution in a thread + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testShortCircuitedWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // all synchronous + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // fallback is performed by the calling thread + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().equals(Thread.currentThread())); // rejected so we stay on calling thread + + // thread isolated ... but rejected so not executed in a thread + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // we capture and set the context once the user provided Observable emits + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler from getFallback + + // thread isolated ... but rejected so not executed in a thread + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testShortCircuitedWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnShortCircuitedWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler from getFallback + + // thread isolated ... but rejected so not executed in a thread + assertFalse(results.command.isExecutedInThread()); + } + + /* *************************************** testTimeoutRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testTimeoutRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testTimeoutRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeout(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* *************************************** testTimeoutWithFallbackRequestContext *********************************** */ + + /** + * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread to perform fallback + //(this use case is a little odd as it should generally not be the case that we are "timing out" a synchronous observable on semaphore isolation) + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation. User provided thread [RxNewThread] does everything. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // semaphore isolated + assertFalse(results.command.isExecutedInThread()); + } + + /** + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + */ + @Test + public void testTimeoutWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); + + assertTrue(results.isContextInitialized.get()); + assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread for fallback + + assertTrue(results.isContextInitializedObserveOn.get()); + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // fallback uses the timeout thread + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and thread isolation. User provided thread [RxNetThread] executes Observable and then [RxComputation] observes the onNext calls. + * + * NOTE: RequestContext will NOT exist on that thread. + * + * An async Observable running on its own thread will not have access to the request context unless the user manages the context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousObservable() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.newThread()); + + assertFalse(results.isContextInitialized.get()); // it won't have request context as it's on a user provided thread/scheduler + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /** + * Async Observable and semaphore isolation WITH functioning RequestContext + * + * Use HystrixContextScheduler to make the user provided scheduler capture context. + */ + @Test + public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousObservableAndCapturedContextScheduler() { + RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, new HystrixContextScheduler(Schedulers.newThread())); + + assertTrue(results.isContextInitialized.get()); // the user scheduler captures context + assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context + assertTrue(results.observeOnThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler + + // thread isolated + assertTrue(results.command.isExecutedInThread()); + } + + /* ******************************************************************************** */ + /* ******************************************************************************** */ + /* private HystrixCommand class implementations for unit testing */ + /* ******************************************************************************** */ + /* ******************************************************************************** */ + + /** + * Used by UnitTest command implementations to provide base defaults for constructor and a builder pattern for the arguments being passed in. + */ + /* package */static abstract class TestHystrixCommand extends HystrixAsyncCommand { + + final TestCommandBuilder builder; + + TestHystrixCommand(TestCommandBuilder builder) { + super(builder.owner, builder.dependencyKey, builder.threadPoolKey, builder.circuitBreaker, builder.threadPool, + builder.commandPropertiesDefaults, builder.threadPoolPropertiesDefaults, builder.metrics, + builder.fallbackSemaphore, builder.executionSemaphore, TEST_PROPERTIES_FACTORY, builder.executionHook); + this.builder = builder; + } + + static TestCommandBuilder testPropsBuilder() { + return new TestCommandBuilder(); + } + + static class TestCommandBuilder { + TestCircuitBreaker _cb = new TestCircuitBreaker(); + HystrixCommandGroupKey owner = CommandGroupForUnitTest.OWNER_ONE; + HystrixCommandKey dependencyKey = null; + HystrixThreadPoolKey threadPoolKey = null; + HystrixCircuitBreaker circuitBreaker = _cb; + HystrixThreadPool threadPool = null; + HystrixCommandProperties.Setter commandPropertiesDefaults = HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE); + HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults = HystrixThreadPoolProperties.Setter.getUnitTestPropertiesBuilder(); + HystrixCommandMetrics metrics = _cb.metrics; + TryableSemaphoreActual fallbackSemaphore = null; + TryableSemaphoreActual executionSemaphore = null; + TestExecutionHook executionHook = new TestExecutionHook(); + + TestCommandBuilder setOwner(HystrixCommandGroupKey owner) { + this.owner = owner; + return this; + } + + TestCommandBuilder setCommandKey(HystrixCommandKey dependencyKey) { + this.dependencyKey = dependencyKey; + return this; + } + + TestCommandBuilder setThreadPoolKey(HystrixThreadPoolKey threadPoolKey) { + this.threadPoolKey = threadPoolKey; + return this; + } + + TestCommandBuilder setCircuitBreaker(HystrixCircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; + return this; + } + + TestCommandBuilder setThreadPool(HystrixThreadPool threadPool) { + this.threadPool = threadPool; + return this; + } + + TestCommandBuilder setCommandPropertiesDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) { + this.commandPropertiesDefaults = commandPropertiesDefaults; + return this; + } + + TestCommandBuilder setThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) { + this.threadPoolPropertiesDefaults = threadPoolPropertiesDefaults; + return this; + } + + TestCommandBuilder setMetrics(HystrixCommandMetrics metrics) { + this.metrics = metrics; + return this; + } + + TestCommandBuilder setFallbackSemaphore(TryableSemaphoreActual fallbackSemaphore) { + this.fallbackSemaphore = fallbackSemaphore; + return this; + } + + TestCommandBuilder setExecutionSemaphore(TryableSemaphoreActual executionSemaphore) { + this.executionSemaphore = executionSemaphore; + return this; + } + + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class SuccessfulTestCommand extends TestHystrixCommand { + + public SuccessfulTestCommand() { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)); + } + + public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setCommandPropertiesDefaults(properties)); + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.just(true, Schedulers.computation()); + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class DynamicOwnerTestCommand extends TestHystrixCommand { + + public DynamicOwnerTestCommand(HystrixCommandGroupKey owner) { + super(testPropsBuilder().setOwner(owner)); + } + + @Override + protected HystrixFuture run() { + System.out.println("successfully executed"); + return HystrixFutureUtil.just(true, Schedulers.computation()); + } + + } + + /** + * Successful execution - no fallback implementation. + */ + private static class DynamicOwnerAndKeyTestCommand extends TestHystrixCommand { + + 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 HystrixFuture run() { + System.out.println("successfully executed"); + return HystrixFutureUtil.just(true, Schedulers.computation()); + } + + } + + /** + * Failed execution with unknown exception (not HystrixException) - no fallback implementation. + */ + private static class UnknownFailureTestCommandWithoutFallback extends TestHystrixCommand { + + private UnknownFailureTestCommandWithoutFallback() { + super(testPropsBuilder()); + } + + @Override + protected HystrixFuture run() { + // TODO duplicate with error inside async Observable + System.out.println("*** simulated failed execution ***"); + throw new RuntimeException("we failed with an unknown issue"); + } + + } + + /** + * Failed execution with known exception (HystrixException) - no fallback implementation. + */ + private static class KnownFailureTestCommandWithoutFallback extends TestHystrixCommand { + + private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected HystrixFuture run() { + // TODO duplicate with error inside async Observable + System.out.println("*** simulated failed execution ***"); + throw new RuntimeException("we failed with a simulated issue"); + } + + } + + /** + * Failed execution - fallback implementation successfully returns value. + */ + private static class KnownFailureTestCommandWithFallback extends TestHystrixCommand { + + 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).withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); + } + + @Override + protected HystrixFuture run() { + // TODO duplicate with error inside async Observable + System.out.println("*** simulated failed execution ***"); + throw new RuntimeException("we failed with a simulated issue"); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.just(false, Schedulers.computation()); + } + } + + /** + * Failed execution - fallback implementation throws exception. + */ + private static class KnownFailureTestCommandWithFallbackFailure extends TestHystrixCommand { + + private KnownFailureTestCommandWithFallbackFailure() { + super(testPropsBuilder()); + } + + @Override + protected HystrixFuture run() { + System.out.println("*** simulated failed execution ***"); + throw new RuntimeException("we failed with a simulated issue"); + } + + @Override + protected HystrixFuture getFallback() { + // TODO duplicate with error inside async Observable + throw new RuntimeException("failed while getting fallback"); + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCommand extends TestHystrixCommand { + + private final boolean cacheEnabled; + private volatile boolean executed = false; + private final String value; + + public SuccessfulCacheableCommand(TestCircuitBreaker circuitBreaker, boolean cacheEnabled, String value) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD))); + this.value = value; + this.cacheEnabled = cacheEnabled; + } + + @Override + protected HystrixFuture run() { + executed = true; + System.out.println("successfully executed"); + return HystrixFutureUtil.just(value, Schedulers.computation()); + } + + public boolean isCommandRunningInThread() { + return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return value; + else + return null; + } + } + + /** + * A Command implementation that supports caching. + */ + private static class SuccessfulCacheableCommandViaSemaphore extends TestHystrixCommand { + + 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 HystrixFuture run() { + executed = true; + System.out.println("successfully executed"); + return HystrixFutureUtil.just(value, Schedulers.computation()); + } + + public boolean isCommandRunningInThread() { + return super.getProperties().executionIsolationStrategy().get().equals(ExecutionIsolationStrategy.THREAD); + } + + @Override + public String getCacheKey() { + if (cacheEnabled) + return value; + else + return null; + } + } + + /** + * A Command implementation that supports caching and execution takes a while. + *

+ * Used to test scenario where Futures are returned with a backing call still executing. + */ + private static class SlowCacheableCommand extends TestHystrixCommand { + + 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 HystrixFuture run() { + executed = true; + final Promise p = Promise.create(); + Observable.just(value).delay(duration, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()) + .doOnNext(new Action1() { + + @Override + public void call(String t1) { + System.out.println("successfully executed"); + p.onSuccess(t1); + } + + }).subscribe(); + + return p.createFuture(); + } + + @Override + public String getCacheKey() { + return value; + } + } + + /** + * Successful execution - no fallback implementation, circuit-breaker disabled. + */ + private static class TestCommandWithoutCircuitBreaker extends TestHystrixCommand { + + private TestCommandWithoutCircuitBreaker() { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withCircuitBreakerEnabled(false))); + } + + @Override + protected HystrixFuture run() { + System.out.println("successfully executed"); + return HystrixFutureUtil.just(true, Schedulers.computation()); + } + + } + + /** + * This should timeout. + */ + private static class TestCommandWithTimeout extends TestHystrixCommand { + + private final long timeout; + + 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 TestCommandWithTimeout(long timeout, int fallbackBehavior) { + this(timeout, fallbackBehavior, ExecutionIsolationStrategy.SEMAPHORE); + } + + private TestCommandWithTimeout(long timeout, int fallbackBehavior, ExecutionIsolationStrategy isolationStrategy) { + super(testPropsBuilder().setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolationStrategy).withExecutionIsolationThreadTimeoutInMilliseconds((int) timeout))); + this.timeout = timeout; + this.fallbackBehavior = fallbackBehavior; + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + System.out.println("***** running"); + try { + Thread.sleep(timeout * 10); + } catch (InterruptedException e) { + e.printStackTrace(); + // ignore and sleep some more to simulate a dependency that doesn't obey interrupts + try { + Thread.sleep(timeout * 2); + } catch (Exception e2) { + // ignore + } + System.out.println("after interruption with extra sleep"); + } + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + + @Override + protected HystrixFuture getFallback() { + if (fallbackBehavior == FALLBACK_SUCCESS) { + return HystrixFutureUtil.just(false); + } else if (fallbackBehavior == FALLBACK_FAILURE) { + // TODO duplicate with error inside async Observable + throw new RuntimeException("failed on fallback"); + } else { + // FALLBACK_NOT_IMPLEMENTED + return super.getFallback(); + } + } + } + + private static class NoRequestCacheTimeoutWithoutFallback extends TestHystrixCommand { + public NoRequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationThreadTimeoutInMilliseconds(200))); + + // we want it to timeout + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); + // e.printStackTrace(); + } + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + + @Override + public String getCacheKey() { + return null; + } + } + + /** + * The run() will take time. No fallback implementation. + */ + private static class TestSemaphoreCommand extends TestHystrixCommand { + + private final long executionSleep; + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, int executionSemaphoreCount, long executionSleep) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(executionSemaphoreCount))); + this.executionSleep = executionSleep; + } + + private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphoreActual semaphore, long executionSleep) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) + .setExecutionSemaphore(semaphore)); + this.executionSleep = executionSleep; + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(executionSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + } + + /** + * The run() will take time. No fallback implementation. + * + * Used for making sure Thread and Semaphore isolation are separated from each other. + */ + private static class TestThreadIsolationWithSemaphoreSetSmallCommand extends TestHystrixCommand { + + private final Action0 action; + + private TestThreadIsolationWithSemaphoreSetSmallCommand(TestCircuitBreaker circuitBreaker, int poolSize, Action0 action) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(TestThreadIsolationWithSemaphoreSetSmallCommand.class.getSimpleName())) + .setThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter.getUnitTestPropertiesBuilder() + .withCoreSize(poolSize).withMaxQueueSize(0)) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter() + .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + .withExecutionIsolationSemaphoreMaxConcurrentRequests(1))); + this.action = action; + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + action.call(); + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + } + + /** + * 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 { + + private final CountDownLatch startLatch, waitLatch; + + /** + * + * @param circuitBreaker + * @param semaphore + * @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, TryableSemaphoreActual semaphore, + CountDownLatch startLatch, CountDownLatch waitLatch) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)) + .setExecutionSemaphore(semaphore)); + this.startLatch = startLatch; + this.waitLatch = waitLatch; + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + // signals caller that run has started + startLatch.countDown(); + + try { + // waits for caller to countDown latch + waitLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + p.onSuccess(false); + return; + } + + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + } + + /** + * The run() will take time. Contains fallback. + */ + private static class TestSemaphoreCommandWithFallback extends TestHystrixCommand { + + private final long executionSleep; + private final HystrixFuture 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 = HystrixFutureUtil.just(fallback); + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(executionSleep); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + + @Override + protected HystrixFuture getFallback() { + return fallback; + } + + } + + private static class RequestCacheNullPointerExceptionCase extends TestHystrixCommand { + public RequestCacheNullPointerExceptionCase(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationThreadTimeoutInMilliseconds(200))); + // we want it to timeout + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.just(false, Schedulers.computation()); + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class RequestCacheTimeoutWithoutFallback extends TestHystrixCommand { + public RequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder().setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationThreadTimeoutInMilliseconds(200))); + // we want it to timeout + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.from(new Action1>() { + + @Override + public void call(Promise p) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.out.println(">>>> Sleep Interrupted: " + e.getMessage()); + // e.printStackTrace(); + } + p.onSuccess(true); + } + + }, Schedulers.computation()); + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class RequestCacheThreadRejectionWithoutFallback extends TestHystrixCommand { + + final CountDownLatch completionLatch; + + public RequestCacheThreadRejectionWithoutFallback(TestCircuitBreaker circuitBreaker, CountDownLatch completionLatch) { + super(testPropsBuilder() + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)) + .setCircuitBreaker(circuitBreaker) + .setMetrics(circuitBreaker.metrics) + .setThreadPool(new HystrixThreadPool() { + + @Override + public ThreadPoolExecutor getExecutor() { + return null; + } + + @Override + public void markThreadExecution() { + + } + + @Override + public void markThreadCompletion() { + + } + + @Override + public boolean isQueueSpaceAvailable() { + // always return false so we reject everything + return false; + } + + @Override + public Scheduler getScheduler() { + return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this); + } + + })); + this.completionLatch = completionLatch; + } + + @Override + protected HystrixFuture run() { + try { + if (completionLatch.await(1000, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("timed out waiting on completionLatch"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return HystrixFutureUtil.just(true); + } + + @Override + public String getCacheKey() { + return "A"; + } + } + + private static class BadRequestCommand extends TestHystrixCommand { + + public BadRequestCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationStrategy isolationType) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker) + .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationStrategy(isolationType))); + } + + @Override + protected HystrixFuture run() { + throw new HystrixBadRequestException("Message to developer that they passed in bad data or something like that."); + } + + @Override + protected HystrixFuture getFallback() { + return HystrixFutureUtil.just(false, Schedulers.computation()); + } + + @Override + protected String getCacheKey() { + return "one"; + } + + } + + private static class CommandWithErrorThrown extends TestHystrixCommand { + + public CommandWithErrorThrown(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected HystrixFuture run() { + // TODO duplicate with error inside async Observable + throw new Error("simulated java.lang.Error message"); + } + + } + + private static class CommandWithCheckedException extends TestHystrixCommand { + + public CommandWithCheckedException(TestCircuitBreaker circuitBreaker) { + super(testPropsBuilder() + .setCircuitBreaker(circuitBreaker).setMetrics(circuitBreaker.metrics)); + } + + @Override + protected HystrixFuture run() { + return HystrixFutureUtil.error(new IOException("simulated checked exception message")); + } + + } + + enum CommandKeyForUnitTest implements HystrixCommandKey { + KEY_ONE, KEY_TWO; + } + + enum CommandGroupForUnitTest implements HystrixCommandGroupKey { + OWNER_ONE, OWNER_TWO; + } + + enum ThreadPoolKeyForUnitTest implements HystrixThreadPoolKey { + THREAD_POOL_ONE, THREAD_POOL_TWO; + } + + private static HystrixPropertiesStrategy TEST_PROPERTIES_FACTORY = new TestPropertiesFactory(); + + private static class TestPropertiesFactory extends HystrixPropertiesStrategy { + + @Override + public HystrixCommandProperties getCommandProperties(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { + if (builder == null) { + builder = HystrixCommandPropertiesTest.getUnitTestPropertiesSetter(); + } + return HystrixCommandPropertiesTest.asMock(builder); + } + + @Override + public HystrixThreadPoolProperties getThreadPoolProperties(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter builder) { + if (builder == null) { + builder = HystrixThreadPoolProperties.Setter.getUnitTestPropertiesBuilder(); + } + return HystrixThreadPoolProperties.Setter.asMock(builder); + } + + @Override + public HystrixCollapserProperties getCollapserProperties(HystrixCollapserKey collapserKey, HystrixCollapserProperties.Setter builder) { + throw new IllegalStateException("not expecting collapser properties"); + } + + @Override + public String getCommandPropertiesCacheKey(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { + return null; + } + + @Override + public String getThreadPoolPropertiesCacheKey(HystrixThreadPoolKey threadPoolKey, com.netflix.hystrix.HystrixThreadPoolProperties.Setter builder) { + return null; + } + + @Override + public String getCollapserPropertiesCacheKey(HystrixCollapserKey collapserKey, com.netflix.hystrix.HystrixCollapserProperties.Setter builder) { + return null; + } + + } + + private static class TestExecutionHook extends HystrixCommandExecutionHook { + + AtomicInteger startExecute = new AtomicInteger(); + + @Override + public void onStart(HystrixInvokable commandInstance) { + super.onStart(commandInstance); + startExecute.incrementAndGet(); + } + + Object endExecuteSuccessResponse = null; + + @Override + public T onComplete(HystrixInvokable commandInstance, T response) { + endExecuteSuccessResponse = response; + return super.onComplete(commandInstance, response); + } + + Exception endExecuteFailureException = null; + FailureType endExecuteFailureType = null; + + @Override + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { + endExecuteFailureException = e; + endExecuteFailureType = failureType; + return super.onError(commandInstance, failureType, e); + } + + AtomicInteger startRun = new AtomicInteger(); + + @Override + public void onRunStart(HystrixInvokable commandInstance) { + super.onRunStart(commandInstance); + startRun.incrementAndGet(); + } + + Object runSuccessResponse = null; + + @Override + public T onRunSuccess(HystrixInvokable commandInstance, T response) { + runSuccessResponse = response; + return super.onRunSuccess(commandInstance, response); + } + + Exception runFailureException = null; + + @Override + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { + runFailureException = e; + return super.onRunError(commandInstance, e); + } + + AtomicInteger startFallback = new AtomicInteger(); + + @Override + public void onFallbackStart(HystrixInvokable commandInstance) { + super.onFallbackStart(commandInstance); + startFallback.incrementAndGet(); + } + + Object fallbackSuccessResponse = null; + + @Override + public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { + fallbackSuccessResponse = response; + return super.onFallbackSuccess(commandInstance, response); + } + + Exception fallbackFailureException = null; + + @Override + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { + fallbackFailureException = e; + return super.onFallbackError(commandInstance, e); + } + + AtomicInteger threadStart = new AtomicInteger(); + + @Override + public void onThreadStart(HystrixInvokable commandInstance) { + super.onThreadStart(commandInstance); + threadStart.incrementAndGet(); + } + + AtomicInteger threadComplete = new AtomicInteger(); + + @Override + public void onThreadComplete(HystrixInvokable commandInstance) { + super.onThreadComplete(commandInstance); + threadComplete.incrementAndGet(); + } + + } + +} diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java index 6e83c42cc..3ee9783e1 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java @@ -574,7 +574,7 @@ protected String getFallback() { public class MyHystrixCommandExecutionHook extends HystrixCommandExecutionHook { @Override - public T onComplete(final HystrixExecutable command, final T response) { + public T onComplete(final HystrixInvokable command, final T response) { logHC(command, response); @@ -583,7 +583,7 @@ public T onComplete(final HystrixExecutable command, final T response) { private int counter = 0; - private void logHC(HystrixExecutable command, T response) { + private void logHC(HystrixInvokable command, T response) { if(command instanceof HystrixExecutableInfo) { HystrixExecutableInfo commandInfo = (HystrixExecutableInfo)command; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java index dc04fb0d5..a00e4e671 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCollapserTest.java @@ -1,6 +1,11 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.lang.ref.Reference; import java.lang.ref.SoftReference; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java index 5e9cd4ed0..7ee149be2 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandMetricsTest.java @@ -1,6 +1,7 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java index 47d6b8ccd..019e21ca4 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandPropertiesTest.java @@ -1,6 +1,6 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.After; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java index c439bbd07..2dbf0a28a 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixCommandTest.java @@ -34,10 +34,10 @@ import rx.schedulers.Schedulers; 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.HystrixExecutableBase.TryableSemaphore; -import com.netflix.hystrix.HystrixExecutableBase.TryableSemaphoreActual; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; @@ -530,64 +530,7 @@ public void testObserveSuccess() { * Test a successful command execution. */ @Test - public void testObserveOnScheduler() throws Exception { - for (int i = 0; i < 5; i++) { - final AtomicReference commandThread = new AtomicReference(); - final AtomicReference subscribeThread = new AtomicReference(); - - TestHystrixCommand command = new TestHystrixCommand(TestHystrixCommand.testPropsBuilder()) { - - @Override - protected Boolean run() { - commandThread.set(Thread.currentThread()); - return true; - } - }; - - final CountDownLatch latch = new CountDownLatch(1); - - command.toObservable(Schedulers.newThread()).subscribe(new Observer() { - - @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-")); - assertFalse(subscribeThread.get().getName().startsWith("hystrix-")); - assertTrue(subscribeThread.get().getName().startsWith("Rx")); - } - } - - /** - * Test a successful command execution. - */ - @Test - public void testObserveOnComputationSchedulerByDefaultForThreadIsolation() throws Exception { + public void testCallbackThreadForThreadIsolation() throws Exception { final AtomicReference commandThread = new AtomicReference(); final AtomicReference subscribeThread = new AtomicReference(); @@ -635,14 +578,14 @@ public void onNext(Boolean args) { System.out.println("Subscribe Thread: " + subscribeThread.get()); assertTrue(commandThread.get().getName().startsWith("hystrix-")); - assertTrue(subscribeThread.get().getName().startsWith("RxComputationThreadPool")); + assertTrue(subscribeThread.get().getName().startsWith("hystrix-")); } /** * Test a successful command execution. */ @Test - public void testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation() throws Exception { + public void testCallbackThreadForSemaphoreIsolation() throws Exception { final AtomicReference commandThread = new AtomicReference(); final AtomicReference subscribeThread = new AtomicReference(); @@ -4261,7 +4204,7 @@ public void call(Throwable t1) { ts.awaitTerminalEvent(); assertTrue(isRequestContextInitialized.get()); - assertTrue(onErrorThread.get().getName().startsWith("RxComputationThreadPool")); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); List errors = ts.getOnErrorEvents(); assertEquals(1, errors.size()); @@ -5201,7 +5144,7 @@ private static class TestExecutionHook extends HystrixCommandExecutionHook { AtomicInteger startExecute = new AtomicInteger(); @Override - public void onStart(HystrixExecutable commandInstance) { + public void onStart(HystrixInvokable commandInstance) { super.onStart(commandInstance); startExecute.incrementAndGet(); } @@ -5209,7 +5152,7 @@ public void onStart(HystrixExecutable commandInstance) { Object endExecuteSuccessResponse = null; @Override - public T onComplete(HystrixExecutable commandInstance, T response) { + public T onComplete(HystrixInvokable commandInstance, T response) { endExecuteSuccessResponse = response; return super.onComplete(commandInstance, response); } @@ -5218,7 +5161,7 @@ public T onComplete(HystrixExecutable commandInstance, T response) { FailureType endExecuteFailureType = null; @Override - public Exception onError(HystrixExecutable commandInstance, FailureType failureType, Exception e) { + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { endExecuteFailureException = e; endExecuteFailureType = failureType; return super.onError(commandInstance, failureType, e); @@ -5227,7 +5170,7 @@ public Exception onError(HystrixExecutable commandInstance, FailureType f AtomicInteger startRun = new AtomicInteger(); @Override - public void onRunStart(HystrixExecutable commandInstance) { + public void onRunStart(HystrixInvokable commandInstance) { super.onRunStart(commandInstance); startRun.incrementAndGet(); } @@ -5235,7 +5178,7 @@ public void onRunStart(HystrixExecutable commandInstance) { Object runSuccessResponse = null; @Override - public T onRunSuccess(HystrixExecutable commandInstance, T response) { + public T onRunSuccess(HystrixInvokable commandInstance, T response) { runSuccessResponse = response; return super.onRunSuccess(commandInstance, response); } @@ -5243,7 +5186,7 @@ public T onRunSuccess(HystrixExecutable commandInstance, T response) { Exception runFailureException = null; @Override - public Exception onRunError(HystrixExecutable commandInstance, Exception e) { + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { runFailureException = e; return super.onRunError(commandInstance, e); } @@ -5251,7 +5194,7 @@ public Exception onRunError(HystrixExecutable commandInstance, Exception AtomicInteger startFallback = new AtomicInteger(); @Override - public void onFallbackStart(HystrixExecutable commandInstance) { + public void onFallbackStart(HystrixInvokable commandInstance) { super.onFallbackStart(commandInstance); startFallback.incrementAndGet(); } @@ -5259,7 +5202,7 @@ public void onFallbackStart(HystrixExecutable commandInstance) { Object fallbackSuccessResponse = null; @Override - public T onFallbackSuccess(HystrixExecutable commandInstance, T response) { + public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { fallbackSuccessResponse = response; return super.onFallbackSuccess(commandInstance, response); } @@ -5267,7 +5210,7 @@ public T onFallbackSuccess(HystrixExecutable commandInstance, T response) Exception fallbackFailureException = null; @Override - public Exception onFallbackError(HystrixExecutable commandInstance, Exception e) { + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { fallbackFailureException = e; return super.onFallbackError(commandInstance, e); } @@ -5275,7 +5218,7 @@ public Exception onFallbackError(HystrixExecutable commandInstance, Excep AtomicInteger threadStart = new AtomicInteger(); @Override - public void onThreadStart(HystrixExecutable commandInstance) { + public void onThreadStart(HystrixInvokable commandInstance) { super.onThreadStart(commandInstance); threadStart.incrementAndGet(); } @@ -5283,7 +5226,7 @@ public void onThreadStart(HystrixExecutable commandInstance) { AtomicInteger threadComplete = new AtomicInteger(); @Override - public void onThreadComplete(HystrixExecutable commandInstance) { + public void onThreadComplete(HystrixInvokable commandInstance) { super.onThreadComplete(commandInstance); threadComplete.incrementAndGet(); } diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixFutureUtil.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixFutureUtil.java new file mode 100644 index 000000000..281b6a6ff --- /dev/null +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixFutureUtil.java @@ -0,0 +1,61 @@ +package com.netflix.hystrix; + +import rx.Scheduler; +import rx.Scheduler.Worker; +import rx.functions.Action0; +import rx.functions.Action1; + +import com.netflix.hystrix.HystrixAsyncCommand.HystrixFuture; +import com.netflix.hystrix.HystrixAsyncCommand.Promise; + +public class HystrixFutureUtil { + + public static HystrixFuture just(T t) { + Promise p = Promise.create(); + p.onSuccess(t); + return HystrixFuture.create(p); + } + + public static HystrixFuture error(Throwable t) { + Promise p = Promise.create(); + p.onError(t); + return HystrixFuture.create(p); + } + + public static HystrixFuture just(final T t, Scheduler s) { + return from(new Action1>() { + + @Override + public void call(Promise p) { + p.onSuccess(t); + } + }, s); + } + + public static HystrixFuture from(final Action1> action, Scheduler s) { + final Promise p = Promise.create(); + final Worker worker = s.createWorker(); + worker.schedule(new Action0() { + + @Override + public void call() { + try { + action.call(p); + } catch (Exception e) { + p.onError(e); + } finally { + worker.unsubscribe(); + } + } + + }); + return HystrixFuture.create(p, new Action0() { + + @Override + public void call() { + worker.unsubscribe(); + } + + }); + } +} diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java index a154cb92e..7597915fe 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCollapserTest.java @@ -17,9 +17,7 @@ import static org.junit.Assert.assertEquals; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; @@ -63,8 +61,8 @@ public void cleanup() { @Test public void testTwoRequests() throws Exception { TestCollapserTimer timer = new TestCollapserTimer(); - Future response1 = new TestRequestCollapser(timer, counter, 1).queue(); - Future response2 = new TestRequestCollapser(timer, counter, 2).queue(); + Future response1 = new TestRequestCollapser(timer, counter, 1).observe().toBlocking().toFuture(); + Future response2 = new TestRequestCollapser(timer, counter, 2).observe().toBlocking().toFuture(); timer.incrementTime(10); // let time pass that equals the default delay/period assertEquals("1", response1.get()); @@ -200,7 +198,7 @@ private static class TestCollapserCommand extends TestHystrixCommand { } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java index c74cb5ffc..0551bc69d 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixObservableCommandTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; @@ -40,9 +41,9 @@ import rx.schedulers.Schedulers; import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.AbstractCommand.TryableSemaphoreActual; import com.netflix.hystrix.HystrixCircuitBreakerTest.TestCircuitBreaker; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; -import com.netflix.hystrix.HystrixExecutableBase.TryableSemaphoreActual; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; @@ -125,7 +126,7 @@ public void testExecutionSuccess() { assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); @@ -164,7 +165,7 @@ public void testExecutionMultipleTimes() { SuccessfulTestCommand command = new SuccessfulTestCommand(); assertFalse(command.isExecutionComplete()); // first should succeed - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); System.out.println(">> completed, checking metrics"); assertTrue(command.isExecutionComplete()); assertFalse(command.isExecutedInThread()); @@ -172,7 +173,7 @@ public void testExecutionMultipleTimes() { assertTrue(command.isSuccessfulExecution()); try { // second should fail - command.execute(); + command.observe().toBlocking().single(); fail("we should not allow this ... it breaks the state of request logs"); } catch (IllegalStateException e) { e.printStackTrace(); @@ -181,7 +182,7 @@ public void testExecutionMultipleTimes() { try { // queue should also fail - command.queue(); + command.observe().toBlocking().toFuture(); fail("we should not allow this ... it breaks the state of request logs"); } catch (IllegalStateException e) { e.printStackTrace(); @@ -200,7 +201,7 @@ public void testExecutionKnownFailureWithNoFallback() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); TestHystrixCommand command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (HystrixRuntimeException e) { e.printStackTrace(); @@ -244,7 +245,7 @@ public void testExecutionKnownFailureWithNoFallback() { public void testExecutionUnknownFailureWithNoFallback() { TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (HystrixRuntimeException e) { e.printStackTrace(); @@ -286,7 +287,7 @@ public void testExecutionUnknownFailureWithNoFallback() { public void testExecutionFailureWithFallback() { TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); try { - assertEquals(false, command.execute()); + assertEquals(false, command.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); @@ -324,7 +325,7 @@ public void testExecutionFailureWithFallback() { public void testExecutionFailureWithFallbackFailure() { TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (HystrixRuntimeException e) { System.out.println("------------------------------------------------"); @@ -363,7 +364,7 @@ public void testExecutionFailureWithFallbackFailure() { public void testQueueSuccess() { TestHystrixCommand command = new SuccessfulTestCommand(); try { - Future future = command.queue(); + Future future = command.observe().toBlocking().toFuture(); assertEquals(true, future.get()); } catch (Exception e) { e.printStackTrace(); @@ -401,7 +402,7 @@ public void testQueueKnownFailureWithNoFallback() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); TestHystrixCommand command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we shouldn't get here"); } catch (Exception e) { e.printStackTrace(); @@ -445,7 +446,7 @@ public void testQueueKnownFailureWithNoFallback() { public void testQueueUnknownFailureWithNoFallback() { TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we shouldn't get here"); } catch (Exception e) { e.printStackTrace(); @@ -488,7 +489,7 @@ public void testQueueUnknownFailureWithNoFallback() { public void testQueueFailureWithFallback() { TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); try { - Future future = command.queue(); + Future future = command.observe().toBlocking().toFuture(); assertEquals(false, future.get()); } catch (Exception e) { e.printStackTrace(); @@ -525,7 +526,7 @@ public void testQueueFailureWithFallback() { public void testQueueFailureWithFallbackFailure() { TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we shouldn't get here"); } catch (Exception e) { if (e.getCause() instanceof HystrixRuntimeException) { @@ -744,7 +745,7 @@ public void testObserveOnImmediateSchedulerByDefaultForSemaphoreIsolation() thro .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))) { @Override - protected Observable run() { + protected Observable construct() { commandThread.set(Thread.currentThread()); return Observable.just(true); } @@ -803,21 +804,21 @@ public void testCircuitBreakerTripsAfterFailures() { /* fail 3 times and then it should trip the circuit and stop executing */ // failure 1 KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt1.execute(); + attempt1.observe().toBlocking().single(); assertTrue(attempt1.isResponseFromFallback()); assertFalse(attempt1.isCircuitBreakerOpen()); assertFalse(attempt1.isResponseShortCircuited()); // failure 2 KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt2.execute(); + attempt2.observe().toBlocking().single(); assertTrue(attempt2.isResponseFromFallback()); assertFalse(attempt2.isCircuitBreakerOpen()); assertFalse(attempt2.isResponseShortCircuited()); // failure 3 KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt3.execute(); + attempt3.observe().toBlocking().single(); assertTrue(attempt3.isResponseFromFallback()); assertFalse(attempt3.isResponseShortCircuited()); // it should now be 'open' and prevent further executions @@ -825,7 +826,7 @@ public void testCircuitBreakerTripsAfterFailures() { // attempt 4 KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt4.execute(); + attempt4.observe().toBlocking().single(); assertTrue(attempt4.isResponseFromFallback()); // this should now be true as the response will be short-circuited assertTrue(attempt4.isResponseShortCircuited()); @@ -859,21 +860,21 @@ public void testCircuitBreakerTripsAfterFailuresViaQueue() { /* fail 3 times and then it should trip the circuit and stop executing */ // failure 1 KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt1.queue().get(); + attempt1.observe().toBlocking().toFuture().get(); assertTrue(attempt1.isResponseFromFallback()); assertFalse(attempt1.isCircuitBreakerOpen()); assertFalse(attempt1.isResponseShortCircuited()); // failure 2 KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt2.queue().get(); + attempt2.observe().toBlocking().toFuture().get(); assertTrue(attempt2.isResponseFromFallback()); assertFalse(attempt2.isCircuitBreakerOpen()); assertFalse(attempt2.isResponseShortCircuited()); // failure 3 KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt3.queue().get(); + attempt3.observe().toBlocking().toFuture().get(); assertTrue(attempt3.isResponseFromFallback()); assertFalse(attempt3.isResponseShortCircuited()); // it should now be 'open' and prevent further executions @@ -881,7 +882,7 @@ public void testCircuitBreakerTripsAfterFailuresViaQueue() { // attempt 4 KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt4.queue().get(); + attempt4.observe().toBlocking().toFuture().get(); assertTrue(attempt4.isResponseFromFallback()); // this should now be true as the response will be short-circuited assertTrue(attempt4.isResponseShortCircuited()); @@ -922,7 +923,7 @@ public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() { /* fail 3 times and then it should trip the circuit and stop executing */ // failure 1 KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt1.execute(); + attempt1.observe().toBlocking().single(); assertTrue(attempt1.isResponseFromFallback()); assertFalse(attempt1.isCircuitBreakerOpen()); assertFalse(attempt1.isResponseShortCircuited()); @@ -930,7 +931,7 @@ public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() { // failure 2 with a different command, same circuit breaker KnownFailureTestCommandWithoutFallback attempt2 = new KnownFailureTestCommandWithoutFallback(circuitBreaker); try { - attempt2.execute(); + attempt2.observe().toBlocking().single(); } catch (Exception e) { // ignore ... this doesn't have a fallback so will throw an exception } @@ -941,7 +942,7 @@ public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() { // failure 3 of the Hystrix, 2nd for this particular HystrixCommand KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt3.execute(); + attempt3.observe().toBlocking().single(); assertTrue(attempt2.isFailedExecution()); assertTrue(attempt3.isResponseFromFallback()); assertFalse(attempt3.isResponseShortCircuited()); @@ -952,7 +953,7 @@ public void testCircuitBreakerAcrossMultipleCommandsButSameCircuitBreaker() { // attempt 4 KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker); - attempt4.execute(); + attempt4.observe().toBlocking().single(); assertTrue(attempt4.isResponseFromFallback()); // this should now be true as the response will be short-circuited assertTrue(attempt4.isResponseShortCircuited()); @@ -987,21 +988,21 @@ public void testCircuitBreakerAcrossMultipleCommandsAndDifferentDependency() { // failure 1 KnownFailureTestCommandWithFallback attempt1 = new KnownFailureTestCommandWithFallback(circuitBreaker_one); - attempt1.execute(); + attempt1.observe().toBlocking().single(); assertTrue(attempt1.isResponseFromFallback()); assertFalse(attempt1.isCircuitBreakerOpen()); assertFalse(attempt1.isResponseShortCircuited()); // failure 2 with a different HystrixCommand implementation and different Hystrix KnownFailureTestCommandWithFallback attempt2 = new KnownFailureTestCommandWithFallback(circuitBreaker_two); - attempt2.execute(); + attempt2.observe().toBlocking().single(); assertTrue(attempt2.isResponseFromFallback()); assertFalse(attempt2.isCircuitBreakerOpen()); assertFalse(attempt2.isResponseShortCircuited()); // failure 3 but only 2nd of the Hystrix.ONE KnownFailureTestCommandWithFallback attempt3 = new KnownFailureTestCommandWithFallback(circuitBreaker_one); - attempt3.execute(); + attempt3.observe().toBlocking().single(); assertTrue(attempt3.isResponseFromFallback()); assertFalse(attempt3.isResponseShortCircuited()); @@ -1013,7 +1014,7 @@ public void testCircuitBreakerAcrossMultipleCommandsAndDifferentDependency() { // attempt 4 (3rd attempt for Hystrix.ONE) KnownFailureTestCommandWithFallback attempt4 = new KnownFailureTestCommandWithFallback(circuitBreaker_one); - attempt4.execute(); + attempt4.observe().toBlocking().single(); // this should NOW flip to true as this is the 3rd failure for Hystrix.ONE assertTrue(attempt3.isCircuitBreakerOpen()); assertTrue(attempt3.isResponseFromFallback()); @@ -1060,7 +1061,7 @@ public void testCircuitBreakerAcrossMultipleCommandsAndDifferentDependency() { public void testExecutionSuccessWithCircuitBreakerDisabled() { TestHystrixCommand command = new TestCommandWithoutCircuitBreaker(); try { - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); fail("We received an exception."); @@ -1091,7 +1092,7 @@ public void testExecutionSuccessWithCircuitBreakerDisabled() { public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.SEMAPHORE); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { // e.printStackTrace(); @@ -1140,7 +1141,7 @@ public void testExecutionTimeoutWithNoFallbackUsingSemaphoreIsolation() { public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.SEMAPHORE); try { - assertEquals(false, command.execute()); + assertEquals(false, command.observe().toBlocking().single()); // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertTrue(command.isResponseTimedOut()); @@ -1177,7 +1178,7 @@ public void testExecutionTimeoutWithFallbackUsingSemaphoreIsolation() { public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.SEMAPHORE); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { if (e instanceof HystrixRuntimeException) { @@ -1220,7 +1221,7 @@ public void testExecutionTimeoutFallbackFailureUsingSemaphoreIsolation() { public void testExecutionTimeoutWithNoFallbackUsingThreadIsolation() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED, ExecutionIsolationStrategy.THREAD); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { // e.printStackTrace(); @@ -1269,7 +1270,7 @@ public void testExecutionTimeoutWithNoFallbackUsingThreadIsolation() { public void testExecutionTimeoutWithFallbackUsingThreadIsolation() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS, ExecutionIsolationStrategy.THREAD); try { - assertEquals(false, command.execute()); + assertEquals(false, command.observe().toBlocking().single()); // the time should be 50+ since we timeout at 50ms assertTrue("Execution Time is: " + command.getExecutionTimeInMilliseconds(), command.getExecutionTimeInMilliseconds() >= 50); assertTrue(command.isResponseTimedOut()); @@ -1306,7 +1307,7 @@ public void testExecutionTimeoutWithFallbackUsingThreadIsolation() { public void testExecutionTimeoutFallbackFailureUsingThreadIsolation() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE, ExecutionIsolationStrategy.THREAD); try { - command.execute(); + command.observe().toBlocking().single(); fail("we shouldn't get here"); } catch (Exception e) { if (e instanceof HystrixRuntimeException) { @@ -1351,7 +1352,7 @@ public void testCircuitBreakerOnExecutionTimeout() { try { assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); - command.execute(); + command.observe().toBlocking().single(); assertTrue(command.isResponseFromFallback()); assertFalse(command.isCircuitBreakerOpen()); @@ -1397,7 +1398,7 @@ public void testCircuitBreakerOnExecutionTimeout() { public void testCountersOnExecutionTimeout() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); try { - command.execute(); + command.observe().toBlocking().single(); /* wait long enough for the command to have finished */ Thread.sleep(200); @@ -1446,14 +1447,15 @@ public void testCountersOnExecutionTimeout() { /** * Test a queued command execution timeout where the command didn't implement getFallback. *

- * 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. + * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. */ @Test public void testQueuedExecutionTimeoutWithNoFallback() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we shouldn't get here"); } catch (Exception e) { e.printStackTrace(); @@ -1495,14 +1497,15 @@ public void testQueuedExecutionTimeoutWithNoFallback() { /** * Test a queued command execution timeout where the command implemented getFallback. *

- * 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. + * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. */ @Test public void testQueuedExecutionTimeoutWithFallback() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); try { - assertEquals(false, command.queue().get()); + assertEquals(false, command.observe().toBlocking().toFuture().get()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); @@ -1531,14 +1534,15 @@ public void testQueuedExecutionTimeoutWithFallback() { /** * Test a queued command execution timeout where the command implemented getFallback but it fails. *

- * 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. + * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. */ @Test public void testQueuedExecutionTimeoutFallbackFailure() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_FAILURE); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we shouldn't get here"); } catch (Exception e) { if (e instanceof ExecutionException && e.getCause() instanceof HystrixRuntimeException) { @@ -1576,8 +1580,9 @@ public void testQueuedExecutionTimeoutFallbackFailure() { /** * Test a queued command execution timeout where the command didn't implement getFallback. *

- * 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. + * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. */ @Test public void testObservedExecutionTimeoutWithNoFallback() { @@ -1625,8 +1630,9 @@ public void testObservedExecutionTimeoutWithNoFallback() { /** * Test a queued command execution timeout where the command implemented getFallback. *

- * 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. + * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. */ @Test public void testObservedExecutionTimeoutWithFallback() { @@ -1661,8 +1667,9 @@ public void testObservedExecutionTimeoutWithFallback() { /** * Test a queued command execution timeout where the command implemented getFallback but it fails. *

- * 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. + * We specifically want to protect against developers queuing commands and using observe().toBlocking().toFuture().get() without a timeout (such as observe().toBlocking().toFuture().get(3000, + * TimeUnit.Milliseconds)) and ending up blocking + * indefinitely by skipping the timeout protection of the observe().toBlocking().single() command. */ @Test public void testObservedExecutionTimeoutFallbackFailure() { @@ -1710,12 +1717,12 @@ public void testObservedExecutionTimeoutFallbackFailure() { public void testShortCircuitFallbackCounter() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker().setForceShortCircuit(true); try { - new KnownFailureTestCommandWithFallback(circuitBreaker).execute(); + new KnownFailureTestCommandWithFallback(circuitBreaker).observe().toBlocking().single(); assertEquals(1, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); KnownFailureTestCommandWithFallback command = new KnownFailureTestCommandWithFallback(circuitBreaker); - command.execute(); + command.observe().toBlocking().single(); assertEquals(2, circuitBreaker.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); // will be -1 because it never attempted execution @@ -1757,7 +1764,7 @@ public void testExecutionSemaphoreWithQueue() { final TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); // single thread should work try { - boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200).queue().get(); + boolean result = new TestSemaphoreCommand(circuitBreaker, 1, 200).observe().toBlocking().toFuture().get(); assertTrue(result); } catch (Exception e) { // we shouldn't fail on this one @@ -1774,7 +1781,7 @@ public void testExecutionSemaphoreWithQueue() { @Override public void run() { try { - new TestSemaphoreCommand(circuitBreaker, semaphore, 200).queue().get(); + new TestSemaphoreCommand(circuitBreaker, semaphore, 200).observe().toBlocking().toFuture().get(); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -1826,7 +1833,7 @@ public void testExecutionSemaphoreWithExecution() { // single thread should work try { TestSemaphoreCommand command = new TestSemaphoreCommand(circuitBreaker, 1, 200); - boolean result = command.execute(); + boolean result = command.observe().toBlocking().single(); assertFalse(command.isExecutedInThread()); assertTrue(result); } catch (Exception e) { @@ -1846,7 +1853,7 @@ public void testExecutionSemaphoreWithExecution() { @Override public void run() { try { - results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200).execute()); + results.add(new TestSemaphoreCommand(circuitBreaker, semaphore, 200).observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -1909,7 +1916,7 @@ public void testRejectedExecutionSemaphoreWithFallback() { @Override public void run() { try { - results.add(new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false).execute()); + results.add(new TestSemaphoreCommandWithFallback(circuitBreaker, 1, 200, false).observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); exceptionReceived.set(true); @@ -1980,7 +1987,7 @@ public void testSemaphorePermitsInUse() { final Runnable sharedSemaphoreRunnable = new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { public void run() { try { - new LatchedSemaphoreCommand(circuitBreaker, sharedSemaphore, startLatch, sharedLatch).execute(); + new LatchedSemaphoreCommand(circuitBreaker, sharedSemaphore, startLatch, sharedLatch).observe().toBlocking().single(); } catch (Exception e) { e.printStackTrace(); } @@ -2008,7 +2015,7 @@ public void run() { final Thread isolatedThread = new Thread(new HystrixContextRunnable(HystrixPlugins.getInstance().getConcurrencyStrategy(), new Runnable() { public void run() { try { - new LatchedSemaphoreCommand(circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch).execute(); + new LatchedSemaphoreCommand(circuitBreaker, isolatedSemaphore, startLatch, isolatedLatch).observe().toBlocking().single(); } catch (Exception e) { e.printStackTrace(); failureCount.incrementAndGet(); @@ -2071,7 +2078,7 @@ public void testDynamicOwner() { assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); @@ -2094,7 +2101,7 @@ public void testDynamicOwnerFails() { assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); fail("we should have thrown an exception as we need an owner"); // semaphore isolated @@ -2111,9 +2118,9 @@ public void testDynamicOwnerFails() { public void testDynamicKey() { try { DynamicOwnerAndKeyTestCommand command1 = new DynamicOwnerAndKeyTestCommand(CommandGroupForUnitTest.OWNER_ONE, CommandKeyForUnitTest.KEY_ONE); - assertEquals(true, command1.execute()); + assertEquals(true, command1.observe().toBlocking().single()); DynamicOwnerAndKeyTestCommand command2 = new DynamicOwnerAndKeyTestCommand(CommandGroupForUnitTest.OWNER_ONE, CommandKeyForUnitTest.KEY_TWO); - assertEquals(true, command2.execute()); + assertEquals(true, command2.observe().toBlocking().single()); // 2 different circuit breakers should be created assertNotSame(command1.getCircuitBreaker(), command2.getCircuitBreaker()); @@ -2137,8 +2144,8 @@ public void testRequestCache1UsingThreadIsolation() { assertTrue(command1.isCommandRunningInThread()); - Future f1 = command1.queue(); - Future f2 = command2.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); @@ -2192,8 +2199,8 @@ public void testRequestCache2UsingThreadIsolation() { assertTrue(command1.isCommandRunningInThread()); - Future f1 = command1.queue(); - Future f2 = command2.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); @@ -2245,9 +2252,9 @@ public void testRequestCache3UsingThreadIsolation() { assertTrue(command1.isCommandRunningInThread()); - Future f1 = command1.queue(); - Future f2 = command2.queue(); - Future f3 = command3.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); @@ -2307,10 +2314,10 @@ public void testRequestCacheWithSlowExecution() { SlowCacheableCommand command3 = new SlowCacheableCommand(circuitBreaker, "A", 100); SlowCacheableCommand command4 = new SlowCacheableCommand(circuitBreaker, "A", 100); - Future f1 = command1.queue(); - Future f2 = command2.queue(); - Future f3 = command3.queue(); - Future f4 = command4.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); + Future f4 = command4.observe().toBlocking().toFuture(); try { assertEquals("A", f2.get()); @@ -2383,9 +2390,9 @@ public void testNoRequestCache3UsingThreadIsolation() { assertTrue(command1.isCommandRunningInThread()); - Future f1 = command1.queue(); - Future f2 = command2.queue(); - Future f3 = command3.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); @@ -2447,9 +2454,9 @@ public void testRequestCacheViaQueueUsingSemaphoreIsolation() { assertFalse(command1.isCommandRunningInThread()); - Future f1 = command1.queue(); - Future f2 = command2.queue(); - Future f3 = command3.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); @@ -2515,9 +2522,9 @@ public void testNoRequestCacheViaQueueUsingSemaphoreIsolation() { assertFalse(command1.isCommandRunningInThread()); - Future f1 = command1.queue(); - Future f2 = command2.queue(); - Future f3 = command3.queue(); + Future f1 = command1.observe().toBlocking().toFuture(); + Future f2 = command2.observe().toBlocking().toFuture(); + Future f3 = command3.observe().toBlocking().toFuture(); try { assertEquals("A", f1.get()); @@ -2579,9 +2586,9 @@ public void testRequestCacheViaExecuteUsingSemaphoreIsolation() { assertFalse(command1.isCommandRunningInThread()); - String f1 = command1.execute(); - String f2 = command2.execute(); - String f3 = command3.execute(); + String f1 = command1.observe().toBlocking().single(); + String f2 = command2.observe().toBlocking().single(); + String f3 = command3.observe().toBlocking().single(); assertEquals("A", f1); assertEquals("B", f2); @@ -2639,9 +2646,9 @@ public void testNoRequestCacheViaExecuteUsingSemaphoreIsolation() { assertFalse(command1.isCommandRunningInThread()); - String f1 = command1.execute(); - String f2 = command2.execute(); - String f3 = command3.execute(); + String f1 = command1.observe().toBlocking().single(); + String f2 = command2.observe().toBlocking().single(); + String f3 = command3.observe().toBlocking().single(); assertEquals("A", f1); assertEquals("B", f2); @@ -2692,7 +2699,7 @@ public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); NoRequestCacheTimeoutWithoutFallback r1 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); try { - System.out.println("r1 value: " + r1.execute()); + System.out.println("r1 value: " + r1.observe().toBlocking().single()); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { @@ -2702,7 +2709,7 @@ public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { NoRequestCacheTimeoutWithoutFallback r2 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); try { - r2.execute(); + r2.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { @@ -2711,7 +2718,7 @@ public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { } NoRequestCacheTimeoutWithoutFallback r3 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); - Future f3 = r3.queue(); + Future f3 = r3.observe().toBlocking().toFuture(); try { f3.get(); // we should have thrown an exception @@ -2726,7 +2733,7 @@ public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { NoRequestCacheTimeoutWithoutFallback r4 = new NoRequestCacheTimeoutWithoutFallback(circuitBreaker); try { - r4.execute(); + r4.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { @@ -2756,14 +2763,14 @@ public void testNoRequestCacheOnTimeoutThrowsException() throws Exception { public void testRequestCacheOnTimeoutCausesNullPointerException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); // Expect it to time out - all results should be false - assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).execute()); - assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).execute()); // return from cache #1 - assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).execute()); // return from cache #2 + assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single()); + assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single()); // return from cache #1 + assertFalse(new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single()); // return from cache #2 Thread.sleep(500); // timeout on command is set to 200ms - Boolean value = new RequestCacheNullPointerExceptionCase(circuitBreaker).execute(); // return from cache #3 + Boolean value = new RequestCacheNullPointerExceptionCase(circuitBreaker).observe().toBlocking().single(); // return from cache #3 assertFalse(value); RequestCacheNullPointerExceptionCase c = new RequestCacheNullPointerExceptionCase(circuitBreaker); - Future f = c.queue(); // return from cache #4 + Future f = c.observe().toBlocking().toFuture(); // return from cache #4 // the bug is that we're getting a null Future back, rather than a Future that returns false assertNotNull(f); assertFalse(f.get()); @@ -2814,7 +2821,7 @@ public void testRequestCacheOnTimeoutThrowsException() throws Exception { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); RequestCacheTimeoutWithoutFallback r1 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); try { - System.out.println("r1 value: " + r1.execute()); + System.out.println("r1 value: " + r1.observe().toBlocking().single()); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { @@ -2824,7 +2831,7 @@ public void testRequestCacheOnTimeoutThrowsException() throws Exception { RequestCacheTimeoutWithoutFallback r2 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); try { - r2.execute(); + r2.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { @@ -2833,7 +2840,7 @@ public void testRequestCacheOnTimeoutThrowsException() throws Exception { } RequestCacheTimeoutWithoutFallback r3 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); - Future f3 = r3.queue(); + Future f3 = r3.observe().toBlocking().toFuture(); try { f3.get(); // we should have thrown an exception @@ -2848,7 +2855,7 @@ public void testRequestCacheOnTimeoutThrowsException() throws Exception { RequestCacheTimeoutWithoutFallback r4 = new RequestCacheTimeoutWithoutFallback(circuitBreaker); try { - r4.execute(); + r4.observe().toBlocking().single(); // we should have thrown an exception fail("expected a timeout"); } catch (HystrixRuntimeException e) { @@ -2880,7 +2887,7 @@ public void testRequestCacheOnThreadRejectionThrowsException() throws Exception CountDownLatch completionLatch = new CountDownLatch(1); RequestCacheThreadRejectionWithoutFallback r1 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { - System.out.println("r1: " + r1.execute()); + System.out.println("r1: " + r1.observe().toBlocking().single()); // we should have thrown an exception fail("expected a rejection"); } catch (HystrixRuntimeException e) { @@ -2891,7 +2898,7 @@ public void testRequestCacheOnThreadRejectionThrowsException() throws Exception RequestCacheThreadRejectionWithoutFallback r2 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { - System.out.println("r2: " + r2.execute()); + System.out.println("r2: " + r2.observe().toBlocking().single()); // we should have thrown an exception fail("expected a rejection"); } catch (HystrixRuntimeException e) { @@ -2902,13 +2909,12 @@ public void testRequestCacheOnThreadRejectionThrowsException() throws Exception RequestCacheThreadRejectionWithoutFallback r3 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { - System.out.println("f3: " + r3.queue().get()); + System.out.println("f3: " + r3.observe().toBlocking().toFuture().get()); // we should have thrown an exception fail("expected a rejection"); - } catch (HystrixRuntimeException e) { - // e.printStackTrace(); + } catch (ExecutionException e) { assertTrue(r3.isResponseRejected()); - // what we want + assertTrue(e.getCause() instanceof HystrixRuntimeException); } // let the command finish (only 1 should actually be blocked on this due to the response cache) @@ -2917,7 +2923,7 @@ public void testRequestCacheOnThreadRejectionThrowsException() throws Exception // then another after the command has completed RequestCacheThreadRejectionWithoutFallback r4 = new RequestCacheThreadRejectionWithoutFallback(circuitBreaker, completionLatch); try { - System.out.println("r4: " + r4.execute()); + System.out.println("r4: " + r4.observe().toBlocking().single()); // we should have thrown an exception fail("expected a rejection"); } catch (HystrixRuntimeException e) { @@ -2954,10 +2960,10 @@ public void testBasicExecutionWorksWithoutRequestVariable() { HystrixRequestContext.setContextOnCurrentThread(null); TestHystrixCommand command = new SuccessfulTestCommand(); - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); TestHystrixCommand command2 = new SuccessfulTestCommand(); - assertEquals(true, command2.queue().get()); + assertEquals(true, command2.observe().toBlocking().toFuture().get()); // we should be able to execute without a RequestVariable if ... // 1) We don't have a cacheKey @@ -2984,10 +2990,10 @@ public void testCacheKeyExecutionRequiresRequestVariable() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); SuccessfulCacheableCommand command = new SuccessfulCacheableCommand(circuitBreaker, true, "one"); - assertEquals(true, command.execute()); + assertEquals(true, command.observe().toBlocking().single()); SuccessfulCacheableCommand command2 = new SuccessfulCacheableCommand(circuitBreaker, true, "two"); - assertEquals(true, command2.queue().get()); + assertEquals(true, command2.observe().toBlocking().toFuture().get()); fail("We expect an exception because cacheKey requires RequestVariable."); @@ -3005,7 +3011,7 @@ public void testCacheKeyExecutionRequiresRequestVariable() { public void testBadRequestExceptionViaExecuteInThread() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); try { - new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).execute(); + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().single(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (HystrixBadRequestException e) { // success @@ -3027,7 +3033,7 @@ public void testBadRequestExceptionViaExecuteInThread() { public void testBadRequestExceptionViaQueueInThread() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); try { - new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).queue().get(); + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().toFuture().get(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (ExecutionException e) { e.printStackTrace(); @@ -3055,13 +3061,13 @@ public void testBadRequestExceptionViaQueueInThreadOnResponseFromCache() { // execute once to cache the value try { - new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).execute(); + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().single(); } catch (Throwable e) { // ignore } try { - new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).queue().get(); + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.THREAD).observe().toBlocking().toFuture().get(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (ExecutionException e) { e.printStackTrace(); @@ -3087,7 +3093,7 @@ public void testBadRequestExceptionViaQueueInThreadOnResponseFromCache() { public void testBadRequestExceptionViaExecuteInSemaphore() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); try { - new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE).execute(); + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE).observe().toBlocking().single(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (HystrixBadRequestException e) { // success @@ -3109,7 +3115,7 @@ public void testBadRequestExceptionViaExecuteInSemaphore() { public void testBadRequestExceptionViaQueueInSemaphore() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); try { - new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE).queue().get(); + new BadRequestCommand(circuitBreaker, ExecutionIsolationStrategy.SEMAPHORE).observe().toBlocking().toFuture().get(); fail("we expect to receive a " + HystrixBadRequestException.class.getSimpleName()); } catch (ExecutionException e) { e.printStackTrace(); @@ -3136,7 +3142,7 @@ public void testCheckedExceptionViaExecute() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CommandWithCheckedException command = new CommandWithCheckedException(circuitBreaker); try { - command.execute(); + command.observe().toBlocking().single(); fail("we expect to receive a " + Exception.class.getSimpleName()); } catch (Exception e) { assertEquals("simulated checked exception message", e.getCause().getMessage()); @@ -3215,7 +3221,7 @@ public void testErrorThrownViaExecute() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker); try { - command.execute(); + command.observe().toBlocking().single(); 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 @@ -3245,7 +3251,7 @@ public void testErrorThrownViaQueue() { TestCircuitBreaker circuitBreaker = new TestCircuitBreaker(); CommandWithErrorThrown command = new CommandWithErrorThrown(circuitBreaker); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we expect to receive an Exception"); } catch (Exception e) { // one cause down from ExecutionException to HystrixRuntime @@ -3330,9 +3336,9 @@ public void onNext(Boolean args) { */ @Test public void testExecutionHookSuccessfulCommand() { - //test with execute() + //test with observe().toBlocking().single() TestHystrixCommand command = new SuccessfulTestCommand(); - command.execute(); + command.observe().toBlocking().single(); // the run() method should run as we're not short-circuited or rejected assertEquals(1, command.builder.executionHook.startRun.get()); @@ -3348,9 +3354,9 @@ public void testExecutionHookSuccessfulCommand() { // null since it didn't run assertNull(command.builder.executionHook.fallbackFailureException); - // the execute() method was used + // the observe().toBlocking().single() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from execute() since run() succeeded + // we should have a response from observe().toBlocking().single() since run() succeeded assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception since run() succeeded assertNull(command.builder.executionHook.endExecuteFailureException); @@ -3359,10 +3365,10 @@ public void testExecutionHookSuccessfulCommand() { // assertEquals(1, command.builder.executionHook.threadStart.get()); // assertEquals(1, command.builder.executionHook.threadComplete.get()); - // test with queue() + // test with observe().toBlocking().toFuture() command = new SuccessfulTestCommand(); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); } catch (Exception e) { throw new RuntimeException(e); } @@ -3381,9 +3387,9 @@ public void testExecutionHookSuccessfulCommand() { // null since it didn't run assertNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded + // we should have a response from observe().toBlocking().toFuture() since run() succeeded assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception since run() succeeded assertNull(command.builder.executionHook.endExecuteFailureException); @@ -3404,7 +3410,7 @@ public void testExecutionHookSuccessfulCommandViaFireAndForget() { TestHystrixCommand command = new SuccessfulTestCommand(); try { // do not block on "get()" ... fire this asynchronously - command.queue(); + command.observe().toBlocking().toFuture(); } catch (Exception e) { throw new RuntimeException(e); } @@ -3434,9 +3440,9 @@ public void testExecutionHookSuccessfulCommandViaFireAndForget() { // null since it didn't run assertNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded + // we should have a response from observe().toBlocking().toFuture() since run() succeeded assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception since run() succeeded assertNull(command.builder.executionHook.endExecuteFailureException); @@ -3456,7 +3462,7 @@ public void testExecutionHookSuccessfulCommandViaFireAndForget() { public void testExecutionHookSuccessfulCommandWithMultipleGetsOnFuture() { TestHystrixCommand command = new SuccessfulTestCommand(); try { - Future f = command.queue(); + Future f = command.observe().toBlocking().toFuture(); f.get(); f.get(); f.get(); @@ -3481,9 +3487,9 @@ public void testExecutionHookSuccessfulCommandWithMultipleGetsOnFuture() { // null since it didn't run assertNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded + // we should have a response from observe().toBlocking().toFuture() since run() succeeded assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception since run() succeeded assertNull(command.builder.executionHook.endExecuteFailureException); @@ -3501,10 +3507,10 @@ public void testExecutionHookSuccessfulCommandWithMultipleGetsOnFuture() { */ @Test public void testExecutionHookRunFailureWithoutFallback() { - // test with execute() + // test with observe().toBlocking().single() TestHystrixCommand command = new UnknownFailureTestCommandWithoutFallback(); try { - command.execute(); + command.observe().toBlocking().single(); fail("Expecting exception"); } catch (Exception e) { // ignore @@ -3524,9 +3530,9 @@ public void testExecutionHookRunFailureWithoutFallback() { // not null since it's not implemented and throws an exception assertNotNull(command.builder.executionHook.fallbackFailureException); - // the execute() method was used + // the observe().toBlocking().single() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from execute() since we do not have a fallback and run() failed + // we should not have a response from observe().toBlocking().single() since we do not have a fallback and run() failed assertNull(command.builder.executionHook.endExecuteSuccessResponse); // we should have an exception since run() failed assertNotNull(command.builder.executionHook.endExecuteFailureException); @@ -3537,10 +3543,10 @@ public void testExecutionHookRunFailureWithoutFallback() { // assertEquals(1, command.builder.executionHook.threadStart.get()); // assertEquals(1, command.builder.executionHook.threadComplete.get()); - // test with queue() + // test with observe().toBlocking().toFuture() command = new UnknownFailureTestCommandWithoutFallback(); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("Expecting exception"); } catch (Exception e) { // ignore @@ -3560,9 +3566,9 @@ public void testExecutionHookRunFailureWithoutFallback() { // not null since it's not implemented and throws an exception assertNotNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should not have a response from queue() since we do not have a fallback and run() failed + // we should not have a response from observe().toBlocking().toFuture() since we do not have a fallback and run() failed assertNull(command.builder.executionHook.endExecuteSuccessResponse); // we should have an exception since run() failed assertNotNull(command.builder.executionHook.endExecuteFailureException); @@ -3583,9 +3589,9 @@ public void testExecutionHookRunFailureWithoutFallback() { */ @Test public void testExecutionHookRunFailureWithFallback() { - // test with execute() + // test with observe().toBlocking().single() TestHystrixCommand command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); - command.execute(); + command.observe().toBlocking().single(); // the run() method should run as we're not short-circuited or rejected assertEquals(1, command.builder.executionHook.startRun.get()); @@ -3601,9 +3607,9 @@ public void testExecutionHookRunFailureWithFallback() { // null since it's implemented and succeeds assertNull(command.builder.executionHook.fallbackFailureException); - // the execute() method was used + // the observe().toBlocking().single() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from execute() since we expect a fallback despite failure of run() + // we should have a response from observe().toBlocking().single() since we expect a fallback despite failure of run() assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception because we expect a fallback assertNull(command.builder.executionHook.endExecuteFailureException); @@ -3612,10 +3618,10 @@ public void testExecutionHookRunFailureWithFallback() { // assertEquals(1, command.builder.executionHook.threadStart.get()); // assertEquals(1, command.builder.executionHook.threadComplete.get()); - // test with queue() + // test with observe().toBlocking().toFuture() command = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker()); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); } catch (Exception e) { throw new RuntimeException(e); } @@ -3634,9 +3640,9 @@ public void testExecutionHookRunFailureWithFallback() { // null since it's implemented and succeeds assertNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since we expect a fallback despite failure of run() + // we should have a response from observe().toBlocking().toFuture() since we expect a fallback despite failure of run() assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception because we expect a fallback assertNull(command.builder.executionHook.endExecuteFailureException); @@ -3654,10 +3660,10 @@ public void testExecutionHookRunFailureWithFallback() { */ @Test public void testExecutionHookRunFailureWithFallbackFailure() { - // test with execute() + // test with observe().toBlocking().single() TestHystrixCommand command = new KnownFailureTestCommandWithFallbackFailure(); try { - command.execute(); + command.observe().toBlocking().single(); fail("Expecting exception"); } catch (Exception e) { // ignore @@ -3677,7 +3683,7 @@ public void testExecutionHookRunFailureWithFallbackFailure() { // not null since it's implemented but fails assertNotNull(command.builder.executionHook.fallbackFailureException); - // the execute() method was used + // the observe().toBlocking().single() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); // we should not have a response because run() and fallback fail assertNull(command.builder.executionHook.endExecuteSuccessResponse); @@ -3690,10 +3696,10 @@ public void testExecutionHookRunFailureWithFallbackFailure() { // assertEquals(1, command.builder.executionHook.threadStart.get()); // assertEquals(1, command.builder.executionHook.threadComplete.get()); - // test with queue() + // test with observe().toBlocking().toFuture() command = new KnownFailureTestCommandWithFallbackFailure(); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("Expecting exception"); } catch (Exception e) { // ignore @@ -3713,7 +3719,7 @@ public void testExecutionHookRunFailureWithFallbackFailure() { // not null since it's implemented but fails assertNotNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); // we should not have a response because run() and fallback fail assertNull(command.builder.executionHook.endExecuteSuccessResponse); @@ -3738,7 +3744,7 @@ public void testExecutionHookTimeoutWithoutFallback() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_NOT_IMPLEMENTED); try { System.out.println("start at : " + System.currentTimeMillis()); - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("Expecting exception"); } catch (Exception e) { // ignore @@ -3789,7 +3795,7 @@ public void testExecutionHookTimeoutWithoutFallback() { public void testExecutionHookTimeoutWithFallback() { TestHystrixCommand command = new TestCommandWithTimeout(50, TestCommandWithTimeout.FALLBACK_SUCCESS); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); } catch (Exception e) { throw new RuntimeException("not expecting", e); } @@ -3841,8 +3847,8 @@ public void testExecutionHookTimeoutWithFallback() { * * try { * // fill the queue - * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); - * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).queue(); + * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe().toBlocking().toFuture(); + * new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS).observe().toBlocking().toFuture(); * } catch (Exception e) { * // ignore * } @@ -3850,7 +3856,7 @@ public void testExecutionHookTimeoutWithFallback() { * TestCommandRejection command = new TestCommandRejection(circuitBreaker, pool, 500, 600, TestCommandRejection.FALLBACK_SUCCESS); * try { * // now execute one that will be rejected - * command.queue().get(); + * command.observe().toBlocking().toFuture().get(); * } catch (Exception e) { * throw new RuntimeException("not expecting", e); * } @@ -3892,7 +3898,7 @@ public void testExecutionHookShortCircuitedWithFallbackViaQueue() { KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); try { // now execute one that will be short-circuited - command.queue().get(); + command.observe().toBlocking().toFuture().get(); fail("we expect an error as there is no fallback"); } catch (Exception e) { // expecting @@ -3940,7 +3946,7 @@ public void testExecutionHookShortCircuitedWithFallbackViaExecute() { KnownFailureTestCommandWithoutFallback command = new KnownFailureTestCommandWithoutFallback(circuitBreaker); try { // now execute one that will be short-circuited - command.execute(); + command.observe().toBlocking().single(); fail("we expect an error as there is no fallback"); } catch (Exception e) { // expecting @@ -3984,9 +3990,9 @@ public void testExecutionHookShortCircuitedWithFallbackViaExecute() { */ @Test public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { - // test with execute() + // test with observe().toBlocking().single() TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); - command.execute(); + command.observe().toBlocking().single(); assertFalse(command.isExecutedInThread()); @@ -4004,9 +4010,9 @@ public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { // null since it didn't run assertNull(command.builder.executionHook.fallbackFailureException); - // the execute() method was used + // the observe().toBlocking().single() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from execute() since run() succeeded + // we should have a response from observe().toBlocking().single() since run() succeeded assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception since run() succeeded assertNull(command.builder.executionHook.endExecuteFailureException); @@ -4015,10 +4021,10 @@ public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { assertEquals(0, command.builder.executionHook.threadStart.get()); assertEquals(0, command.builder.executionHook.threadComplete.get()); - // test with queue() + // test with observe().toBlocking().toFuture() command = new TestSemaphoreCommand(new TestCircuitBreaker(), 1, 10); try { - command.queue().get(); + command.observe().toBlocking().toFuture().get(); } catch (Exception e) { throw new RuntimeException(e); } @@ -4039,9 +4045,9 @@ public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { // null since it didn't run assertNull(command.builder.executionHook.fallbackFailureException); - // the queue() method was used + // the observe().toBlocking().toFuture() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); - // we should have a response from queue() since run() succeeded + // we should have a response from observe().toBlocking().toFuture() since run() succeeded assertNotNull(command.builder.executionHook.endExecuteSuccessResponse); // we should not have an exception since run() succeeded assertNull(command.builder.executionHook.endExecuteFailureException); @@ -4059,13 +4065,13 @@ public void testExecutionHookSuccessfulCommandWithSemaphoreIsolation() { */ @Test public void testExecutionHookFailureWithSemaphoreIsolation() { - // test with execute() + // test with observe().toBlocking().single() final TryableSemaphoreActual semaphore = new TryableSemaphoreActual(HystrixProperty.Factory.asProperty(0)); TestSemaphoreCommand command = new TestSemaphoreCommand(new TestCircuitBreaker(), semaphore, 200); try { - command.execute(); + command.observe().toBlocking().single(); fail("we expect a failure"); } catch (Exception e) { // expected @@ -4088,7 +4094,7 @@ public void testExecutionHookFailureWithSemaphoreIsolation() { // not null since the fallback is not implemented assertNotNull(command.builder.executionHook.fallbackFailureException); - // the execute() method was used + // the observe().toBlocking().single() method was used assertEquals(1, command.builder.executionHook.startExecute.get()); // we should not have a response since fallback has nothing assertNull(command.builder.executionHook.endExecuteSuccessResponse); @@ -4112,7 +4118,7 @@ public void testExecutionHookFailureWithSemaphoreIsolation() { public void testExecutionFailureWithFallbackImplementedButDisabled() { TestHystrixCommand commandEnabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), true); try { - assertEquals(false, commandEnabled.execute()); + assertEquals(false, commandEnabled.observe().toBlocking().single()); } catch (Exception e) { e.printStackTrace(); fail("We should have received a response from the fallback."); @@ -4120,7 +4126,7 @@ public void testExecutionFailureWithFallbackImplementedButDisabled() { TestHystrixCommand commandDisabled = new KnownFailureTestCommandWithFallback(new TestCircuitBreaker(), false); try { - assertEquals(false, commandDisabled.execute()); + assertEquals(false, commandDisabled.observe().toBlocking().single()); fail("expect exception thrown"); } catch (Exception e) { // expected @@ -4162,7 +4168,7 @@ public void testSynchronousExecutionTimeoutValueViaExecute() { HystrixObservableCommand command = new HystrixObservableCommand(properties) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4180,7 +4186,7 @@ public void call(Subscriber t1) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { if (isResponseTimedOut()) { return Observable.just("timed-out"); } else { @@ -4190,7 +4196,7 @@ protected Observable getFallback() { }; System.out.println(">>>>> Start: " + System.currentTimeMillis()); - String value = command.execute(); + String value = command.observe().toBlocking().single(); System.out.println(">>>>> End: " + System.currentTimeMillis()); assertTrue(command.isResponseTimedOut()); assertEquals("expected fallback value", "timed-out", value); @@ -4209,7 +4215,7 @@ public void testSynchronousExecutionUsingThreadIsolationTimeoutValueViaObserve() HystrixObservableCommand command = new HystrixObservableCommand(properties) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4227,7 +4233,7 @@ public void call(Subscriber t1) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { if (isResponseTimedOut()) { return Observable.just("timed-out"); } else { @@ -4253,7 +4259,7 @@ public void testAsyncExecutionTimeoutValueViaObserve() { HystrixObservableCommand command = new HystrixObservableCommand(properties) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4272,7 +4278,7 @@ public void call(Subscriber t1) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { if (isResponseTimedOut()) { return Observable.just("timed-out"); } else { @@ -4316,7 +4322,7 @@ public void call(Throwable t1) { ts.awaitTerminalEvent(); assertTrue(isRequestContextInitialized.get()); - assertTrue(onErrorThread.get().getName().startsWith("RxComputationThreadPool")); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); List errors = ts.getOnErrorEvents(); assertEquals(1, errors.size()); @@ -4384,7 +4390,7 @@ public void call(Boolean t1) { System.out.println("events: " + ts.getOnNextEvents()); assertTrue(isRequestContextInitialized.get()); - assertTrue(onErrorThread.get().getName().startsWith("RxComputationThreadPool")); + assertTrue(onErrorThread.get().getName().startsWith("HystrixTimer")); List onNexts = ts.getOnNextEvents(); assertEquals(1, onNexts.size()); @@ -4595,7 +4601,7 @@ private RequestContextTestResults testRequestContextOnSuccess(ExecutionIsolation .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4658,7 +4664,7 @@ private RequestContextTestResults testRequestContextOnGracefulFailure(ExecutionI .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4720,7 +4726,7 @@ private RequestContextTestResults testRequestContextOnBadFailure(ExecutionIsolat .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4782,7 +4788,7 @@ private RequestContextTestResults testRequestContextOnFailureWithFallback(Execut .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4794,7 +4800,7 @@ public void call(Subscriber s) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.create(new OnSubscribe() { @Override @@ -4890,7 +4896,7 @@ public Scheduler getScheduler() { })) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4902,7 +4908,7 @@ public void call(Subscriber s) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.create(new OnSubscribe() { @Override @@ -4975,7 +4981,7 @@ private RequestContextTestResults testRequestContextOnShortCircuitedWithFallback .setCircuitBreaker(new TestCircuitBreaker().setForceShortCircuit(true))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -4987,7 +4993,7 @@ public void call(Subscriber s) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.create(new OnSubscribe() { @Override @@ -5052,7 +5058,7 @@ private RequestContextTestResults testRequestContextOnTimeout(ExecutionIsolation .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionIsolationThreadTimeoutInMilliseconds(50))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -5118,7 +5124,7 @@ private RequestContextTestResults testRequestContextOnTimeoutWithFallback(Execut .setCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(isolation).withExecutionIsolationThreadTimeoutInMilliseconds(50))) { @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -5134,7 +5140,7 @@ public void call(Subscriber s) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.create(new OnSubscribe() { @Override @@ -5912,7 +5918,7 @@ public void testTimeoutRequestContextWithSemaphoreIsolatedSynchronousObservable( assertTrue(results.originThread.get().equals(Thread.currentThread())); // all synchronous assertTrue(results.isContextInitializedObserveOn.get()); - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout schedules on RxComputation since the original thread was timed out + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // semaphore isolated assertFalse(results.command.isExecutedInThread()); @@ -5933,7 +5939,7 @@ public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservable assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout schedules on RxComputation since the original thread was timed out + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // semaphore isolated assertFalse(results.command.isExecutedInThread()); @@ -5952,7 +5958,7 @@ public void testTimeoutRequestContextWithSemaphoreIsolatedAsynchronousObservable assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout schedules on RxComputation since the original thread was timed out + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // semaphore isolated assertFalse(results.command.isExecutedInThread()); @@ -5969,7 +5975,7 @@ public void testTimeoutRequestContextWithThreadIsolatedSynchronousObservable() { assertTrue(results.originThread.get().getName().startsWith("hystrix-OWNER_ONE")); // thread isolated on a HystrixThreadPool assertTrue(results.isContextInitializedObserveOn.get()); - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout schedules on RxComputation since the original thread was timed out + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // thread isolated assertTrue(results.command.isExecutedInThread()); @@ -5990,7 +5996,7 @@ public void testTimeoutRequestContextWithThreadIsolatedAsynchronousObservable() assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the timeout captures the context so it exists - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout schedules on RxComputation since the original thread was timed out + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // thread isolated assertTrue(results.command.isExecutedInThread()); @@ -6009,7 +6015,7 @@ public void testTimeoutRequestContextWithThreadIsolatedAsynchronousObservableAnd assertTrue(results.originThread.get().getName().startsWith("RxNewThread")); // the user provided thread/scheduler assertTrue(results.isContextInitializedObserveOn.get()); // the user scheduler captures context - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout schedules on RxComputation since the original thread was timed out + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout schedules on HystrixTimer since the original thread was timed out // thread isolated assertTrue(results.command.isExecutedInThread()); @@ -6018,18 +6024,18 @@ public void testTimeoutRequestContextWithThreadIsolatedAsynchronousObservableAnd /* *************************************** testTimeoutWithFallbackRequestContext *********************************** */ /** - * Synchronous Observable and semaphore isolation. Only [Main] thread is involved in this. + * Synchronous Observable and semaphore isolation. */ @Test public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.SEMAPHORE, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); - assertTrue(results.originThread.get().getName().startsWith("RxComputation")); // timeout uses RxComputation thread + assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread //(this use case is a little odd as it should generally not be the case that we are "timing out" a synchronous observable on semaphore isolation) assertTrue(results.isContextInitializedObserveOn.get()); - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // timeout uses RxComputation thread + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread // semaphore isolated assertFalse(results.command.isExecutedInThread()); @@ -6076,17 +6082,17 @@ public void testTimeoutWithFallbackRequestContextWithSemaphoreIsolatedAsynchrono } /** - * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [RxComputation] + * Synchronous Observable and thread isolation. Work done on [hystrix-OWNER_ONE] thread and then observed on [HystrixTimer] */ @Test public void testTimeoutWithFallbackRequestContextWithThreadIsolatedSynchronousObservable() { RequestContextTestResults results = testRequestContextOnTimeoutWithFallback(ExecutionIsolationStrategy.THREAD, Schedulers.immediate()); assertTrue(results.isContextInitialized.get()); - assertTrue(results.originThread.get().getName().startsWith("RxComputation")); // timeout uses RxComputation thread for fallback + assertTrue(results.originThread.get().getName().startsWith("HystrixTimer")); // timeout uses HystrixTimer thread for fallback assertTrue(results.isContextInitializedObserveOn.get()); - assertTrue(results.observeOnThread.get().getName().startsWith("RxComputation")); // fallback uses the timeout thread + assertTrue(results.observeOnThread.get().getName().startsWith("HystrixTimer")); // fallback uses the timeout thread // thread isolated assertTrue(results.command.isExecutedInThread()); @@ -6132,6 +6138,198 @@ public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousO assertTrue(results.command.isExecutedInThread()); } + /** + * Test support of multiple onNext events. + */ + @Test + public void testExecutionSuccessWithMultipleEvents() { + try { + TestCommandWithMultipleValues command = new TestCommandWithMultipleValues(); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + assertEquals(Arrays.asList(1, 2, 3), command.observe().toList().toBlocking().single()); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + assertEquals(null, command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(0, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test behavior when some onNext are received and then a failure. + */ + @Test + public void testExecutionPartialSuccess() { + try { + TestPartialSuccess command = new TestPartialSuccess(); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + TestSubscriber ts = new TestSubscriber(); + command.toObservable().subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(1, 2, 3)); + assertEquals(1, ts.getOnErrorEvents().size()); + // it ends as a failure so we see the metrics count as FAILURE + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + // but it will say it is both SUCCESSful and FAILED + assertTrue(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + // we will have an exception + assertNotNull(command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test behavior when some onNext are received and then a failure. + */ + @Test + public void testExecutionPartialSuccessWithFallback() { + try { + TestPartialSuccessWithFallback command = new TestPartialSuccessWithFallback(); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + TestSubscriber ts = new TestSubscriber(); + command.toObservable().subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 1, 2, 3, 4)); + ts.assertNoErrors(); + + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + // but it will say it is both SUCCESSful and FAILED + assertTrue(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertNotNull(command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + + /** + * Test behavior when some onNext are received and then a failure. + */ + @Test + public void testExecutionPartialSuccessWithMoreIntelligentFallback() { + try { + TestPartialSuccessWithIntelligentFallback command = new TestPartialSuccessWithIntelligentFallback(); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + TestSubscriber ts = new TestSubscriber(); + command.toObservable().subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4)); + ts.assertNoErrors(); + + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FAILURE)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.TIMEOUT)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SUCCESS)); + + // but it will say it is both SUCCESSful and FAILED + assertTrue(command.isSuccessfulExecution()); + assertTrue(command.isFailedExecution()); + + assertNotNull(command.getFailedExecutionException()); + + assertTrue(command.getExecutionTimeInMilliseconds() > -1); + assertTrue(command.isSuccessfulExecution()); + + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE)); + assertEquals(1, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED)); + assertEquals(0, command.builder.metrics.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE)); + + assertEquals(100, command.builder.metrics.getHealthCounts().getErrorPercentage()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + + // semaphore isolated + assertFalse(command.isExecutedInThread()); + } catch (Exception e) { + e.printStackTrace(); + fail("We received an exception."); + } + } + /* ******************************************************************************** */ /* ******************************************************************************** */ /* private HystrixCommand class implementations for unit testing */ @@ -6141,7 +6339,7 @@ public void testTimeoutWithFallbackRequestContextWithThreadIsolatedAsynchronousO /** * Used by UnitTest command implementations to provide base defaults for constructor and a builder pattern for the arguments being passed in. */ - /* package */ static abstract class TestHystrixCommand extends HystrixObservableCommand { + /* package */static abstract class TestHystrixCommand extends HystrixObservableCommand { final TestCommandBuilder builder; @@ -6238,12 +6436,105 @@ public SuccessfulTestCommand(HystrixCommandProperties.Setter properties) { } @Override - protected Observable run() { + protected Observable construct() { return Observable.just(true).subscribeOn(Schedulers.computation()); } } + /** + * Successful execution - no fallback implementation. + */ + private static class TestCommandWithMultipleValues extends TestHystrixCommand { + + public TestCommandWithMultipleValues() { + this(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)); + } + + public TestCommandWithMultipleValues(HystrixCommandProperties.Setter properties) { + super(testPropsBuilder().setCommandPropertiesDefaults(properties)); + } + + @Override + protected Observable construct() { + return Observable.just(1, 2, 3).subscribeOn(Schedulers.computation()); + } + + } + + private static class TestPartialSuccess extends TestHystrixCommand { + + TestPartialSuccess() { + super(TestHystrixCommand.testPropsBuilder()); + } + + @Override + protected Observable construct() { + return Observable.just(1, 2, 3) + .concatWith(Observable. error(new RuntimeException("forced error"))) + .subscribeOn(Schedulers.computation()); + } + + } + + private static class TestPartialSuccessWithFallback extends TestHystrixCommand { + + TestPartialSuccessWithFallback() { + super(TestHystrixCommand.testPropsBuilder()); + } + + @Override + protected Observable construct() { + return Observable.just(1, 2, 3) + .concatWith(Observable. error(new RuntimeException("forced error"))) + .subscribeOn(Schedulers.computation()); + } + + @Override + protected Observable onFailureResumeWithFallback() { + return Observable.just(1, 2, 3, 4); + } + + } + + /** + * Test how a fallback could be done on a streaming response where it is partially successful + * by retaining state of what has been seen. + */ + private static class TestPartialSuccessWithIntelligentFallback extends TestHystrixCommand { + + TestPartialSuccessWithIntelligentFallback() { + super(TestHystrixCommand.testPropsBuilder()); + } + + volatile int lastSeen = 0; + + @Override + protected Observable construct() { + return Observable.just(1, 2, 3) + .concatWith(Observable. error(new RuntimeException("forced error"))) + .doOnNext(new Action1() { + + @Override + public void call(Integer t1) { + lastSeen = t1; + } + + }) + .subscribeOn(Schedulers.computation()); + } + + @Override + protected Observable onFailureResumeWithFallback() { + if (lastSeen < 4) { + return Observable.range(lastSeen + 1, 4 - lastSeen); + } else { + return Observable.empty(); + } + } + + } + /** * Successful execution - no fallback implementation. */ @@ -6254,7 +6545,7 @@ public DynamicOwnerTestCommand(HystrixCommandGroupKey owner) { } @Override - protected Observable run() { + protected Observable construct() { System.out.println("successfully executed"); return Observable.just(true).subscribeOn(Schedulers.computation()); } @@ -6272,7 +6563,7 @@ public DynamicOwnerAndKeyTestCommand(HystrixCommandGroupKey owner, HystrixComman } @Override - protected Observable run() { + protected Observable construct() { System.out.println("successfully executed"); return Observable.just(true).subscribeOn(Schedulers.computation()); } @@ -6289,7 +6580,7 @@ private UnknownFailureTestCommandWithoutFallback() { } @Override - protected Observable run() { + protected Observable construct() { // TODO duplicate with error inside async Observable System.out.println("*** simulated failed execution ***"); throw new RuntimeException("we failed with an unknown issue"); @@ -6307,7 +6598,7 @@ private KnownFailureTestCommandWithoutFallback(TestCircuitBreaker circuitBreaker } @Override - protected Observable run() { + protected Observable construct() { // TODO duplicate with error inside async Observable System.out.println("*** simulated failed execution ***"); throw new RuntimeException("we failed with a simulated issue"); @@ -6330,14 +6621,14 @@ public KnownFailureTestCommandWithFallback(TestCircuitBreaker circuitBreaker, bo } @Override - protected Observable run() { + protected Observable construct() { // TODO duplicate with error inside async Observable System.out.println("*** simulated failed execution ***"); throw new RuntimeException("we failed with a simulated issue"); } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.just(false).subscribeOn(Schedulers.computation()); } } @@ -6352,13 +6643,13 @@ private KnownFailureTestCommandWithFallbackFailure() { } @Override - protected Observable run() { + protected Observable construct() { System.out.println("*** simulated failed execution ***"); throw new RuntimeException("we failed with a simulated issue"); } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { // TODO duplicate with error inside async Observable throw new RuntimeException("failed while getting fallback"); } @@ -6381,7 +6672,7 @@ public SuccessfulCacheableCommand(TestCircuitBreaker circuitBreaker, boolean cac } @Override - protected Observable run() { + protected Observable construct() { executed = true; System.out.println("successfully executed"); return Observable.just(value).subscribeOn(Schedulers.computation()); @@ -6417,7 +6708,7 @@ public SuccessfulCacheableCommandViaSemaphore(TestCircuitBreaker circuitBreaker, } @Override - protected Observable run() { + protected Observable construct() { executed = true; System.out.println("successfully executed"); return Observable.just(value).subscribeOn(Schedulers.computation()); @@ -6454,7 +6745,7 @@ public SlowCacheableCommand(TestCircuitBreaker circuitBreaker, String value, int } @Override - protected Observable run() { + protected Observable construct() { executed = true; return Observable.just(value).delay(duration, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()) .doOnNext(new Action1() { @@ -6483,7 +6774,7 @@ private TestCommandWithoutCircuitBreaker() { } @Override - protected Observable run() { + protected Observable construct() { System.out.println("successfully executed"); return Observable.just(true).subscribeOn(Schedulers.computation()); } @@ -6514,7 +6805,7 @@ private TestCommandWithTimeout(long timeout, int fallbackBehavior, ExecutionIsol } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6540,7 +6831,7 @@ public void call(Subscriber sub) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { if (fallbackBehavior == FALLBACK_SUCCESS) { return Observable.just(false); } else if (fallbackBehavior == FALLBACK_FAILURE) { @@ -6548,7 +6839,7 @@ protected Observable getFallback() { throw new RuntimeException("failed on fallback"); } else { // FALLBACK_NOT_IMPLEMENTED - return super.getFallback(); + return super.onFailureResumeWithFallback(); } } } @@ -6562,7 +6853,7 @@ public NoRequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6610,7 +6901,7 @@ private TestSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaphore } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6649,7 +6940,7 @@ private TestThreadIsolationWithSemaphoreSetSmallCommand(TestCircuitBreaker circu } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6692,7 +6983,7 @@ private LatchedSemaphoreCommand(TestCircuitBreaker circuitBreaker, TryableSemaph } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6734,7 +7025,7 @@ private TestSemaphoreCommandWithFallback(TestCircuitBreaker circuitBreaker, int } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6753,7 +7044,7 @@ public void call(Subscriber s) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return fallback; } @@ -6767,7 +7058,7 @@ public RequestCacheNullPointerExceptionCase(TestCircuitBreaker circuitBreaker) { } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6786,7 +7077,7 @@ public void call(Subscriber s) { } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.just(false).subscribeOn(Schedulers.computation()); } @@ -6804,7 +7095,7 @@ public RequestCacheTimeoutWithoutFallback(TestCircuitBreaker circuitBreaker) { } @Override - protected Observable run() { + protected Observable construct() { return Observable.create(new OnSubscribe() { @Override @@ -6871,7 +7162,7 @@ public Scheduler getScheduler() { } @Override - protected Observable run() { + protected Observable construct() { try { if (completionLatch.await(1000, TimeUnit.MILLISECONDS)) { throw new RuntimeException("timed out waiting on completionLatch"); @@ -6897,12 +7188,12 @@ public BadRequestCommand(TestCircuitBreaker circuitBreaker, ExecutionIsolationSt } @Override - protected Observable run() { + protected Observable construct() { throw new HystrixBadRequestException("Message to developer that they passed in bad data or something like that."); } @Override - protected Observable getFallback() { + protected Observable onFailureResumeWithFallback() { return Observable.just(false).subscribeOn(Schedulers.computation()); } @@ -6921,7 +7212,7 @@ public CommandWithErrorThrown(TestCircuitBreaker circuitBreaker) { } @Override - protected Observable run() { + protected Observable construct() { // TODO duplicate with error inside async Observable throw new Error("simulated java.lang.Error message"); } @@ -6936,7 +7227,7 @@ public CommandWithCheckedException(TestCircuitBreaker circuitBreaker) { } @Override - protected Observable run() { + protected Observable construct() { return Observable.error(new IOException("simulated checked exception message")); } @@ -7001,7 +7292,7 @@ private static class TestExecutionHook extends HystrixCommandExecutionHook { AtomicInteger startExecute = new AtomicInteger(); @Override - public void onStart(HystrixExecutable commandInstance) { + public void onStart(HystrixInvokable commandInstance) { super.onStart(commandInstance); startExecute.incrementAndGet(); } @@ -7009,7 +7300,7 @@ public void onStart(HystrixExecutable commandInstance) { Object endExecuteSuccessResponse = null; @Override - public T onComplete(HystrixExecutable commandInstance, T response) { + public T onComplete(HystrixInvokable commandInstance, T response) { endExecuteSuccessResponse = response; return super.onComplete(commandInstance, response); } @@ -7018,7 +7309,7 @@ public T onComplete(HystrixExecutable commandInstance, T response) { FailureType endExecuteFailureType = null; @Override - public Exception onError(HystrixExecutable commandInstance, FailureType failureType, Exception e) { + public Exception onError(HystrixInvokable commandInstance, FailureType failureType, Exception e) { endExecuteFailureException = e; endExecuteFailureType = failureType; return super.onError(commandInstance, failureType, e); @@ -7027,7 +7318,7 @@ public Exception onError(HystrixExecutable commandInstance, FailureType f AtomicInteger startRun = new AtomicInteger(); @Override - public void onRunStart(HystrixExecutable commandInstance) { + public void onRunStart(HystrixInvokable commandInstance) { super.onRunStart(commandInstance); startRun.incrementAndGet(); } @@ -7035,7 +7326,7 @@ public void onRunStart(HystrixExecutable commandInstance) { Object runSuccessResponse = null; @Override - public T onRunSuccess(HystrixExecutable commandInstance, T response) { + public T onRunSuccess(HystrixInvokable commandInstance, T response) { runSuccessResponse = response; return super.onRunSuccess(commandInstance, response); } @@ -7043,7 +7334,7 @@ public T onRunSuccess(HystrixExecutable commandInstance, T response) { Exception runFailureException = null; @Override - public Exception onRunError(HystrixExecutable commandInstance, Exception e) { + public Exception onRunError(HystrixInvokable commandInstance, Exception e) { runFailureException = e; return super.onRunError(commandInstance, e); } @@ -7051,7 +7342,7 @@ public Exception onRunError(HystrixExecutable commandInstance, Exception AtomicInteger startFallback = new AtomicInteger(); @Override - public void onFallbackStart(HystrixExecutable commandInstance) { + public void onFallbackStart(HystrixInvokable commandInstance) { super.onFallbackStart(commandInstance); startFallback.incrementAndGet(); } @@ -7059,7 +7350,7 @@ public void onFallbackStart(HystrixExecutable commandInstance) { Object fallbackSuccessResponse = null; @Override - public T onFallbackSuccess(HystrixExecutable commandInstance, T response) { + public T onFallbackSuccess(HystrixInvokable commandInstance, T response) { fallbackSuccessResponse = response; return super.onFallbackSuccess(commandInstance, response); } @@ -7067,7 +7358,7 @@ public T onFallbackSuccess(HystrixExecutable commandInstance, T response) Exception fallbackFailureException = null; @Override - public Exception onFallbackError(HystrixExecutable commandInstance, Exception e) { + public Exception onFallbackError(HystrixInvokable commandInstance, Exception e) { fallbackFailureException = e; return super.onFallbackError(commandInstance, e); } @@ -7075,7 +7366,7 @@ public Exception onFallbackError(HystrixExecutable commandInstance, Excep AtomicInteger threadStart = new AtomicInteger(); @Override - public void onThreadStart(HystrixExecutable commandInstance) { + public void onThreadStart(HystrixInvokable commandInstance) { super.onThreadStart(commandInstance); threadStart.incrementAndGet(); } @@ -7083,7 +7374,7 @@ public void onThreadStart(HystrixExecutable commandInstance) { AtomicInteger threadComplete = new AtomicInteger(); @Override - public void onThreadComplete(HystrixExecutable commandInstance) { + public void onThreadComplete(HystrixInvokable commandInstance) { super.onThreadComplete(commandInstance); threadComplete.incrementAndGet(); } diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java index 4e5b17585..50bde1348 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestCacheTest.java @@ -1,6 +1,8 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java index e744a7418..e856bec47 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixRequestLogTest.java @@ -1,6 +1,6 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java index 9af48d5ea..030bfa16f 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixTest.java @@ -1,6 +1,8 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java index df8d66076..605895d51 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/HystrixThreadPoolTest.java @@ -1,6 +1,8 @@ package com.netflix.hystrix; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.util.concurrent.TimeUnit; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestObservableFunctionTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestObservableFunctionTest.java index 6710dab3b..a1fffbe98 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestObservableFunctionTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/collapser/CollapsedRequestObservableFunctionTest.java @@ -1,6 +1,7 @@ package com.netflix.hystrix.collapser; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java index 0acaed4aa..8e7aba177 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java @@ -1,12 +1,11 @@ package com.netflix.hystrix.strategy; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; -import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; -import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHookDefault; import org.junit.After; import org.junit.Test; @@ -20,6 +19,8 @@ import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifierDefault; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHookDefault; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherDefault; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java index e0b295a83..5b7ccbc9e 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/concurrency/HystrixConcurrencyStrategyTest.java @@ -1,6 +1,6 @@ package com.netflix.hystrix.strategy.concurrency; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java index 352d7479a..4fad24064 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java @@ -1,6 +1,6 @@ package com.netflix.hystrix.strategy.metrics; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java index b9ec9c87c..b932575be 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertiesChainedArchaiusPropertyTest.java @@ -1,6 +1,7 @@ package com.netflix.hystrix.strategy.properties; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.util.concurrent.atomic.AtomicInteger; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java index eb9e987cb..ebb2da304 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/strategy/properties/HystrixPropertyTest.java @@ -1,6 +1,6 @@ package com.netflix.hystrix.strategy.properties; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionThreadingUtilityTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionThreadingUtilityTest.java index 6aa68e84d..a59bfc7a9 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionThreadingUtilityTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/util/ExceptionThreadingUtilityTest.java @@ -1,6 +1,6 @@ package com.netflix.hystrix.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java index 86d61d56f..3338468f8 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingNumberTest.java @@ -1,6 +1,7 @@ package com.netflix.hystrix.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.concurrent.atomic.AtomicInteger; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java index 492a4f68f..6a82e1d3a 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixRollingPercentileTest.java @@ -1,6 +1,7 @@ package com.netflix.hystrix.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.concurrent.atomic.AtomicInteger; diff --git a/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java b/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java index 680f0697c..a87478180 100644 --- a/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java +++ b/hystrix-core/src/test/java/com/netflix/hystrix/util/HystrixTimerTest.java @@ -1,6 +1,9 @@ package com.netflix.hystrix.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.lang.ref.Reference; import java.util.concurrent.atomic.AtomicInteger;