abstractclassAbstractCommand<R>implementsHystrixInvokableInfo<R>, HystrixObservable<R>{.../**
* This decorates "Hystrix" functionality around the run() Observable.
*
* @return R
*/private Observable<R>executeCommandAndObserve(final AbstractCommand<R> _cmd){final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread();final Action1<R> markEmits =newAction1<R>(){@Overridepublicvoidcall(R r){if(shouldOutputOnNextEvents()){
executionResult = executionResult.addEvent(HystrixEventType.EMIT);
eventNotifier.markEvent(HystrixEventType.EMIT, commandKey);}if(commandIsScalar()){long latency = System.currentTimeMillis()- executionResult.getStartTimestamp();
eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(),(int) latency, executionResult.getOrderedList());
eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey);
executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS);
circuitBreaker.markSuccess();}}};final Action0 markOnCompleted =newAction0(){@Overridepublicvoidcall(){if(!commandIsScalar()){long latency = System.currentTimeMillis()- executionResult.getStartTimestamp();
eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(),(int) latency, executionResult.getOrderedList());
eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey);
executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS);
circuitBreaker.markSuccess();}}};final Func1<Throwable, Observable<R>> handleFallback =newFunc1<Throwable, Observable<R>>(){@Overridepublic Observable<R>call(Throwable t){
Exception e =getExceptionFromThrowable(t);
executionResult = executionResult.setExecutionException(e);if(e instanceofRejectedExecutionException){returnhandleThreadPoolRejectionViaFallback(e);}elseif(t instanceofHystrixTimeoutException){returnhandleTimeoutViaFallback();}elseif(t instanceofHystrixBadRequestException){returnhandleBadRequestByEmittingError(e);}else{/*
* Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
*/if(e instanceofHystrixBadRequestException){
eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);return Observable.error(e);}returnhandleFailureViaFallback(e);}}};final Action1<Notification<?super R>> setRequestContext =newAction1<Notification<?super R>>(){@Overridepublicvoidcall(Notification<?super R> rNotification){setRequestContextIfNeeded(currentRequestContext);}};
Observable<R> execution;if(properties.executionTimeoutEnabled().get()){
execution =executeCommandWithSpecifiedIsolation(_cmd).lift(newHystrixObservableTimeoutOperator<R>(_cmd));}else{
execution =executeCommandWithSpecifiedIsolation(_cmd);}return execution.doOnNext(markEmits).doOnCompleted(markOnCompleted).onErrorResumeNext(handleFallback).doOnEach(setRequestContext);}private Observable<R>executeCommandWithSpecifiedIsolation(final AbstractCommand<R> _cmd){if(properties.executionIsolationStrategy().get()== 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)return Observable.defer(newFunc0<Observable<R>>(){@Overridepublic Observable<R>call(){
executionResult = executionResult.setExecutionOccurred();if(!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)){return Observable.error(newIllegalStateException("execution attempted while in state : "+ commandState.get().name()));}
metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.THREAD);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 logicreturn Observable.error(newRuntimeException("timed out before executing run()"));}if(threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.STARTED)){//we have not been unsubscribed, so should proceed
HystrixCounters.incrementGlobalConcurrentThreads();
threadPool.markThreadExecution();// store the command that is being run
endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());
executionResult = executionResult.setExecutedInThread();/**
* If any of these hooks throw an exception, then it appears as if the actual execution threw an error
*/try{
executionHook.onThreadStart(_cmd);
executionHook.onRunStart(_cmd);
executionHook.onExecutionStart(_cmd);returngetUserExecutionObservable(_cmd);}catch(Throwable ex){return Observable.error(ex);}}else{//command has already been unsubscribed, so return immediatelyreturn Observable.error(newRuntimeException("unsubscribed before executing run()"));}}}).doOnTerminate(newAction0(){@Overridepublicvoidcall(){if(threadState.compareAndSet(ThreadState.STARTED, ThreadState.TERMINAL)){handleThreadEnd(_cmd);}if(threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.TERMINAL)){//if it was never started and received terminal, then no need to clean up (I don't think this is possible)}//if it was unsubscribed, then other cleanup handled it}}).doOnUnsubscribe(newAction0(){@Overridepublicvoidcall(){if(threadState.compareAndSet(ThreadState.STARTED, ThreadState.UNSUBSCRIBED)){handleThreadEnd(_cmd);}if(threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.UNSUBSCRIBED)){//if it was never started and was cancelled, then no need to clean up}//if it was terminal, then other cleanup handled it}}).subscribeOn(threadPool.getScheduler(newFunc0<Boolean>(){@Overridepublic Boolean call(){return properties.executionIsolationThreadInterruptOnTimeout().get()&& _cmd.isCommandTimedOut.get()== TimedOutStatus.TIMED_OUT;}}));}else{return Observable.defer(newFunc0<Observable<R>>(){@Overridepublic Observable<R>call(){
executionResult = executionResult.setExecutionOccurred();if(!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)){return Observable.error(newIllegalStateException("execution attempted while in state : "+ commandState.get().name()));}
metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.SEMAPHORE);// semaphore isolated// store the command that is being run
endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());try{
executionHook.onRunStart(_cmd);
executionHook.onExecutionStart(_cmd);returngetUserExecutionObservable(_cmd);//the getUserExecutionObservable method already wraps sync exceptions, so this shouldn't throw}catch(Throwable ex){//If the above hooks throw, then use that as the result of the run methodreturn Observable.error(ex);}}});}}...privatestaticclassHystrixObservableTimeoutOperator<R>implementsOperator<R, R>{final AbstractCommand<R> originalCommand;publicHystrixObservableTimeoutOperator(final AbstractCommand<R> originalCommand){this.originalCommand = originalCommand;}@Overridepublic Subscriber<?super R>call(final Subscriber<?super R> child){final CompositeSubscription s =newCompositeSubscription();// 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 =newHystrixContextRunnable(originalCommand.concurrencyStrategy,newRunnable(){@Overridepublicvoidrun(){
child.onError(newHystrixTimeoutException());}});
TimerListener listener =newTimerListener(){@Overridepublicvoidtick(){// 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 or did not startif(originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)){// report timeout failure
originalCommand.eventNotifier.markEvent(HystrixEventType.TIMEOUT, originalCommand.commandKey);// shut down the original request
s.unsubscribe();
timeoutRunnable.run();//if it did not start, then we need to mark a command start for concurrency metrics, and then issue the timeout}}@OverridepublicintgetIntervalTimeInMilliseconds(){return originalCommand.properties.executionTimeoutInMilliseconds().get();}};final Reference<TimerListener> tl = HystrixTimer.getInstance().addTimerListener(listener);// set externally so execute/queue can see this
originalCommand.timeoutTimer.set(tl);/**
* If this subscriber receives values it means the parent succeeded/completed
*/
Subscriber<R> parent =newSubscriber<R>(){@OverridepublicvoidonCompleted(){if(isNotTimedOut()){// stop timer and pass notification through
tl.clear();
child.onCompleted();}}@OverridepublicvoidonError(Throwable e){if(isNotTimedOut()){// stop timer and pass notification through
tl.clear();
child.onError(e);}}@OverridepublicvoidonNext(R v){if(isNotTimedOut()){
child.onNext(v);}}privatebooleanisNotTimedOut(){// if already marked COMPLETED (by onNext) or succeeds in setting to COMPLETEDreturn 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;}}...}