/* * Copyright (c) 2016 Couchbase, Inc. * * 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.couchbase.client.core.time; import java.util.concurrent.TimeUnit; /** * Delay which increases exponentially on every attempt. * * Considering retry attempts start at 1, attempt 0 would be the initial call and will always yield 0 (or the lower bound). * Then each retry step will by default yield <code>1 * 2 ^ (attemptNumber-1)</code>. Actually each step can be based on a * different number than 1 unit of time using the <code>growBy</code> parameter: <code>growBy * 2 ^ (attemptNumber-1)</code>. * * By default with growBy = 1 this gives us 0 (initial attempt), 1, 2, 4, 8, 16, 32... * * Each of the resulting values that is below the <code>lowerBound</code> will be replaced by the lower bound, and * each value over the <code>upperBound</code> will be replaced by the upper bound. * * For example, given the following <code>Delay.exponential(TimeUnit.MILLISECONDS, 4000, 0, 500)</code> * * * the upper of 4000 means the delay will be capped at 4s * * the lower of 0 is useful to allow for immediate execution of original attempt, attempt 0 (if we ever call the * delay with a parameter of 0) * * the growBy of 500 means that we take steps based on 500ms * * This yields the following delays: <code>0ms, 500ms, 1s, 2s, 4s, 4s, 4s,...</code> * * In detail : <code>0, 500 * 2^0, 500 * 2^1, 500 * 2^2, 500 * 2^3, max(4000, 500 * 2^4), max(4000, 500 * 2^5),...</code>. * * Finally, the powers used in the computation can be changed from powers of two by default to another base using the * powersOf parameter. * * @author Michael Nitschinger * @since 1.1.0 */ public class ExponentialDelay extends Delay { private final long lower; private final long upper; private final double growBy; private final int powersOf; ExponentialDelay(TimeUnit unit, long upper, long lower, double growBy, int powersOf) { super(unit); this.lower = lower; this.upper = upper; this.growBy = growBy; this.powersOf = powersOf <= 2 ? 2 : powersOf; } @Override public long calculate(long attempt) { long calc; if (attempt <= 0) { //safeguard against underflow calc = 0; } else if (powersOf == 2) { calc = calculatePowerOfTwo(attempt); } else { calc = calculateAlternatePower(attempt); } return applyBounds(calc); } protected long calculateAlternatePower(long attempt) { //round will cap at Long.MAX_VALUE and pow should prevent overflows double step = Math.pow(this.powersOf, attempt - 1); //attempt > 0 return Math.round(step * growBy); } //fastpath with bitwise operator protected long calculatePowerOfTwo(long attempt) { long step; if (attempt >= 64) { //safeguard against overflow in the bitshift operation step = Long.MAX_VALUE; } else { step = (1L << (attempt - 1)); } //round will cap at Long.MAX_VALUE return Math.round(step * growBy); } private long applyBounds(long calculatedValue) { if (calculatedValue < lower) { return lower; } if (calculatedValue > upper) { return upper; } return calculatedValue; } @Override public String toString() { final StringBuilder sb = new StringBuilder("ExponentialDelay{"); sb.append("growBy ").append(growBy); sb.append(" " + unit()); sb.append(", powers of ").append(powersOf); sb.append("; lower=").append(lower); sb.append(", upper=").append(upper); sb.append('}'); return sb.toString(); } }