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

netflix.ocelli.retrys.ExponentialBackoff Maven / Gradle / Ivy

There is a newer version: 0.1.0-rc.2
Show newest version
package netflix.ocelli.retrys;

import java.util.Random;
import java.util.concurrent.TimeUnit;

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

/**
 * Func1 to be passed to retryWhen which implements a robust random exponential backoff.  The
 * random part of the exponential backoff ensures that some randomness is inserted so that multiple
 * clients blocked on a non-responsive resource spread out the retries to mitigate a thundering
 * herd.  
 * 
 * This class maintains retry count state and should be instantiated for entire top level request.
 * 
 * @author elandau
 */
public class ExponentialBackoff implements Func1, Observable> {
    private static final int MAX_SHIFT = 30;
    
    private final int maxRetrys;
    private final long maxDelay;
    private final long slice;
    private final TimeUnit units;
    private final Func1 retryable;

    private static final Random rand = new Random();
    
    private int tryCount;
    
    /**
     * Construct an exponential backoff 
     * 
     * @param maxRetrys Maximum number of retires to attempt
     * @param slice - Time interval multiplied by backoff amount
     * @param maxDelay - Upper bound allowable backoff delay
     * @param units - Time unit for slice and maxDelay
     * @param retryable - Function that returns true if the error is retryable or false if not.  
     */
    public ExponentialBackoff(int maxRetrys, long slice, long maxDelay, TimeUnit units, Func1 retryable) {
        this.maxDelay = maxDelay;
        this.maxRetrys = maxRetrys;
        this.slice = slice;
        this.units = units;
        this.retryable = retryable;
        
        this.tryCount = 0;
    }
    
    @Override
    public Observable call(Observable error) {
        return error.flatMap(new Func1>() {
            @Override
            public Observable call(Throwable e) {
                // First make sure the error is actually retryable
                if (!retryable.call(e)) {
                    return Observable.error(e);
                }
                
                if (tryCount >= maxRetrys) {
                    return Observable.error(new Exception("Failed with " + tryCount + " retries", e));
                }
                
                // Calculate the number of slices to wait
                int slices = (1 << Math.min(MAX_SHIFT, tryCount));
                slices = (slices + rand.nextInt(slices+1)) / 2;
                long delay = slices * slice;
                if (maxDelay > 0 && delay > maxDelay) {
                    delay = maxDelay;
                }
                tryCount++;
                return Observable.timer(delay, units);
            }
        });
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy