package com.netflix.schlep.rx;
import java.util.concurrent.TimeUnit;
import rx.Observable;
import rx.Observer;
import rx.Scheduler;
import rx.Subscription;
import rx.util.functions.Action0;
import rx.util.functions.Func1;
public class OperationRetry {
public static <T> Func1<Observer<T>, Subscription> retry(final Observable<T> source) {
return new Func1<Observer<T>, Subscription>() {
@Override
public Subscription call(final Observer<T> observer) {
return source.subscribe(new RetryObserver<T>(observer, new TightRetryPolicy()));
}
};
}
public static <T> Func1<Observer<T>, Subscription> retry(final Observable<T> source, RetryPolicy policy) {
return new Func1<Observer<T>, Subscription>() {
@Override
public Subscription call(final Observer<T> observer) {
return source.subscribe(new RetryObserver<T>(observer, new TightRetryPolicy()));
}
};
}
public static <T> Func1<Observer<T>, Subscription> retry(final Observable<T> source, RetryPolicy policy, Scheduler scheduler) {
return new Func1<Observer<T>, Subscription>() {
@Override
public Subscription call(final Observer<T> observer) {
return source.subscribe(new RetryObserver<T>(observer, new TightRetryPolicy()));
}
};
}
/**
* Encapsulate a retry context from the beginning of an operation
* @author elandau
*
*/
public static class Context {
private long attemptCount = 0;
private long startTime = System.nanoTime();
/**
* Return the number of attempts, or number of times +1 nextBackoffDelay was called
* @return
*/
public long getAttemptCount() {
return attemptCount;
}
public long incAttemptCount() {
return ++attemptCount;
}
/**
* Return elapsed time since the context was created
* @return Ellapsed time in msec
*/
public long getEllapsedTime() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
}
}
/**
* Abstraction for a retry policy
* @author elandau
*
*/
public static interface RetryPolicy {
/**
* Return delay in msec or -1 if done
*
* @param context
* @return
*/
public long getDelay(Context context);
}
public static class TightRetryPolicy implements RetryPolicy {
@Override
public long getDelay(Context context) {
return 0;
}
}
public static class NoRetryPolicy implements RetryPolicy {
@Override
public long getDelay(Context context) {
return -1;
}
}
public static class CountingRetryPolicy implements RetryPolicy {
final long maxCount;
public CountingRetryPolicy(long maxCount) {
this.maxCount = maxCount;
}
@Override
public long getDelay(Context context) {
if (context.incAttemptCount() > this.maxCount)
return -1;
return 0;
}
}
public static class CountingDelayRetryPolicy extends CountingRetryPolicy {
final long delay;
public CountingDelayRetryPolicy(long delay, long maxCount) {
super(maxCount);
this.delay = delay;
}
public long getDelay(Context context) {
if (super.getDelay(context) == -1) {
return -1;
}
return delay;
}
}
public static class RetryObserver<T> implements Observer<T> {
private final Observer<T> observer;
private final RetryPolicy policy;
public RetryObserver(Observer<T> observer, RetryPolicy policy) {
this.observer = observer;
this.policy = policy;
}
@Override
public void onCompleted() {
this.observer.onCompleted();
}
@Override
public void onError(Throwable e) {
this.observer.onError(e);
}
@Override
public void onNext(final T args) {
final Context ctx = new Context();
while (true) {
try {
observer.onNext(args);
return;
}
catch (Throwable t) {
System.out.println(t.getMessage());
long delay = policy.getDelay(ctx);
if (delay == -1) {
observer.onError(t);
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(t);
}
}
}
}
}
public static class ScheduledRetryObserver<T> implements Observer<T> {
private final Observer<T> observer;
private final RetryPolicy policy;
private final Scheduler scheduler;
public ScheduledRetryObserver(Observer<T> observer, RetryPolicy policy, Scheduler scheduler) {
this.observer = observer;
this.policy = policy;
this.scheduler = scheduler;
}
@Override
public void onCompleted() {
this.observer.onCompleted();
}
@Override
public void onError(Throwable e) {
this.observer.onError(e);
}
@Override
public void onNext(final T args) {
final Context ctx = new Context();
new Action0() {
@Override
public void call() {
try {
observer.onNext(args);
return;
}
catch (Throwable t) {
System.out.println(t.getMessage());
long delay = policy.getDelay(ctx);
if (delay == -1) {
observer.onError(t);
}
scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
}
}
}.call();
}
}
}