All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avanza.astrix.ft.hystrix.HystrixObservableCommandFacade Maven / Gradle / Ivy

/*
 * Copyright 2014 Avanza Bank AB
 *
 * 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.avanza.astrix.ft.hystrix;

import java.util.function.Supplier;

import com.avanza.astrix.core.ServiceUnavailableException;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.hystrix.HystrixObservableCommand.Setter;
import com.netflix.hystrix.exception.HystrixRuntimeException;

import rx.Observable;
import rx.functions.Func1;

/**
 * @author Elias Lindholm (elilin)
 */
class HystrixObservableCommandFacade {

	public static  Observable observe(final Supplier> observableFactory, Setter settings) {
		Observable> faultToleranceProtectedObservable = new HystrixObservableCommand>(settings) {
			@Override
			protected Observable> construct() {
				return observableFactory.get().map(new Func1>() {
					@Override
					public Result call(T t1) {
						return Result.success(t1);
					}
				}).onErrorResumeNext(new Func1>>() {
					@Override
					public Observable> call(Throwable t1) {
						if (t1 instanceof ServiceUnavailableException) {
							return Observable.error(t1);
						}
						// Wrap all exception thrown by underlying observable
						// in Result.exception. This will by-pass hystrix
						// circuit-breaker logic and not count as a failure.
						// I.e we don't want the circuit-breaker to open 
						// due to exceptions thrown by the underlying service.
						return Observable.just(Result.exception(t1));
					}
				});
			}
			
			@Override
			protected Observable> resumeWithFallback() {
				/* 
				 * This method will will be invoked in any of these circumstances:
				 *  - Underlying observable did not emit event before timeout, "service timeout"
				 *  - Circuit Breaker rejected subscription to underlying observable, "circuit open"
				 *  - Bulk Head rejected subscription to underlying observable, "too many outstanding requests"
				 *  - Underlying observable threw ServiceUnavailableException
				 *  
				 *  Either way, just return a ServiceUnavailableException.
				 * 
				 */
				return Observable.just(Result.exception(createServiceUnavailableException()));
			}

			private ServiceUnavailableException createServiceUnavailableException() {
				if (isResponseRejected()) {
					return new ServiceUnavailableException(String.format("cause=%s service=%s", 
															"REJECTED_EXECUTION", getCommandKey().name()));
				}
				if (isResponseTimedOut()) {
					return new ServiceUnavailableException(String.format("cause=%s service=%s executionTime=%s", 
															"TIMEOUT", getCommandKey().name(), getExecutionTimeInMilliseconds()));
				}
				if (isResponseShortCircuited()) {
					return new ServiceUnavailableException(String.format("cause=%s service=%s", 
															"SHORT_CIRCUITED", getCommandKey().name()));
				}
				if (isFailedExecution() && (getFailedExecutionException() instanceof ServiceUnavailableException)) {
					ServiceUnavailableException result = (ServiceUnavailableException) getFailedExecutionException();
					appendStackTrace(result, new ServiceUnavailableException(String.format("service=%s", getCommandKey().name())));
					return result;
				}
				return new ServiceUnavailableException(String.format("cause=%s service=%s", 
															"UNKNOWN", getCommandKey().name()));
			}
		}.observe(); // Eagerly start execution of underlying observable to fulfill contract of BeanProxy.proxyAsyncInvocation
		return faultToleranceProtectedObservable
								.flatMap(resultWrapper -> resultWrapper.toObservable())
								.onErrorResumeNext(error -> {
									if (error instanceof HystrixRuntimeException) {
										HystrixRuntimeException e = (HystrixRuntimeException) error;
										if (e.getCause() != null) {
											// Can this happen?
											return Observable.error(e.getCause());
										}
										return Observable.error(new ServiceUnavailableException(e.getFailureType().toString()));
									}
									return Observable.error(error);
		});
	}
	
	private static void appendStackTrace(Throwable target, Throwable trace) {
		Throwable lastThowableInChain = target;
		while (lastThowableInChain.getCause() != null) {
			lastThowableInChain = lastThowableInChain.getCause();
		}
		lastThowableInChain.initCause(trace);
	}
	
	private static class Result {
		private final T value;
		private final Throwable exception;
		
		public Result(T value, Throwable exception) {
			this.value = value;
			this.exception = exception;
		}

		public static  Result success(T result) {
			return new Result(result, null);
		}
		
		/**
		 * @param throwable
		 * @return
		 */
		public static  Result exception(Throwable throwable) {
			return new Result(null, throwable);
		}
		
		public Observable toObservable() {
			if (this.exception != null) {
				return Observable.error(this.exception);
			}
			return Observable.just(this.value);
		}
		
		
	}
	

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy