/*
*
* Copyright 2016 Robert Winkler
*
* 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 io.github.resilience4j.retry.internal;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.event.RetryEvent;
import io.github.resilience4j.retry.event.RetryOnErrorEvent;
import io.github.resilience4j.retry.event.RetryOnSuccessEvent;
import io.reactivex.Flowable;
import io.reactivex.processors.FlowableProcessor;
import io.reactivex.processors.PublishProcessor;
import io.vavr.CheckedConsumer;
import io.vavr.control.Option;
import io.vavr.control.Try;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class RetryContext implements Retry {
private final AtomicInteger numOfAttempts = new AtomicInteger(0);
private AtomicReference<Exception> lastException = new AtomicReference<>();
private AtomicReference<RuntimeException> lastRuntimeException = new AtomicReference<>();
private final Metrics metrics;
private final FlowableProcessor<RetryEvent> eventPublisher;
private String name;
private int maxAttempts;
private Function<Integer, Long> intervalFunction;
private Predicate<Throwable> exceptionPredicate;
/*package*/ static CheckedConsumer<Long> sleepFunction = Thread::sleep;
public RetryContext(String name, RetryConfig config){
this.name = name;
this.maxAttempts = config.getMaxAttempts();
this.intervalFunction = config.getIntervalFunction();
this.exceptionPredicate = config.getExceptionPredicate();
PublishProcessor<RetryEvent> publisher = PublishProcessor.create();
this.metrics = this.new RetryContextMetrics();
this.eventPublisher = publisher.toSerialized();
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return name;
}
/**
* {@inheritDoc}
*/
@Override
public void onSuccess() {
int currentNumOfAttempts = numOfAttempts.get();
if(currentNumOfAttempts > 0){
Throwable throwable = Option.of(lastException.get()).getOrElse(lastRuntimeException.get());
publishRetryEvent(() -> new RetryOnSuccessEvent(getName(), currentNumOfAttempts, throwable));
}
}
private void throwOrSleepAfterException() throws Exception {
int currentNumOfAttempts = numOfAttempts.incrementAndGet();
if(currentNumOfAttempts >= maxAttempts){
Exception throwable = lastException.get();
publishRetryEvent(() -> new RetryOnErrorEvent(getName(), currentNumOfAttempts, throwable));
throw throwable;
}else{
waitIntervalAfterFailure();
}
}
private void throwOrSleepAfterRuntimeException(){
int currentNumOfAttempts = numOfAttempts.incrementAndGet();
if(currentNumOfAttempts >= maxAttempts){
RuntimeException throwable = lastRuntimeException.get();
publishRetryEvent(() -> new RetryOnErrorEvent(getName(), currentNumOfAttempts, throwable));
throw throwable;
}else{
waitIntervalAfterFailure();
}
}
private void waitIntervalAfterFailure() {
// wait interval until the next attempt should start
long interval = intervalFunction.apply(numOfAttempts.get());
Try.run(() -> sleepFunction.accept(interval))
.getOrElseThrow(ex -> lastRuntimeException.get());
}
/**
* {@inheritDoc}
*/
@Override
public void onError(Exception exception) throws Throwable{
if(exceptionPredicate.test(exception)){
lastException.set(exception);
throwOrSleepAfterException();
}else{
throw exception;
}
}
/**
* {@inheritDoc}
*/
@Override
public void onRuntimeError(RuntimeException runtimeException){
if(exceptionPredicate.test(runtimeException)){
lastRuntimeException.set(runtimeException);
throwOrSleepAfterRuntimeException();
}else{
throw runtimeException;
}
}
private void publishRetryEvent(Supplier<RetryEvent> event) {
if(eventPublisher.hasSubscribers()) {
eventPublisher.onNext(event.get());
}
}
/**
* {@inheritDoc}
*/
@Override
public Flowable<RetryEvent> getEventStream() {
return eventPublisher;
}
/**
* {@inheritDoc}
*/
@Override
public Metrics getMetrics() {
return this.metrics;
}
/**
* {@inheritDoc}
*/
public final class RetryContextMetrics implements Metrics {
private RetryContextMetrics() {
}
/**
* @return current number of retry attempts made.
*/
@Override
public int getNumAttempts() {
return numOfAttempts.get();
}
/**
* @return the maximum allowed retries to make.
*/
@Override
public int getMaxAttempts() {
return maxAttempts;
}
}
}