/* * Copyright (C) 2017 The Android Open Source Project * * 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.android.dialer.common.backoff; import com.android.dialer.common.Assert; /** * Given an initial delay, D, a maximum total backoff time, T, and a maximum number of backoffs, N, * this class calculates a base multiplier, b, and a scaling factor, g, such that the initial * backoff is g*b = D and the sum of the scaled backoffs is T = g*b + g*b^2 + g*b^3 + ... g*b^N * *
T/D = (1 - b^N)/(1 - b) but this cannot be written as a simple equation for b in terms of T, N * and D so instead use Newton's method (https://en.wikipedia.org/wiki/Newton%27s_method) to find an * approximate value for b. * *
Example usage using the {@code ExponentialBackoff} would be: * *
* // Retry with exponential backoff for up to 2 minutes, with an initial delay of 100 millis * // and a maximum of 10 retries * long initialDelayMillis = 100; * int maxTries = 10; * double base = ExponentialBaseCalculator.findBase( * initialDelayMillis, * TimeUnit.MINUTES.toMillis(2), * maxTries); * ExponentialBackoff backoff = new ExponentialBackoff(initialDelayMillis, base, maxTries); * while (backoff.isInRange()) { * ... * long delay = backoff.getNextBackoff(); * // Wait for the indicated time... * } **/ public final class ExponentialBaseCalculator { private static final int MAX_STEPS = 1000; private static final double DEFAULT_TOLERANCE_MILLIS = 1; /** * Calculate an exponential backoff base multiplier such that the first backoff delay will be as * specified and the sum of the delays after doing the indicated maximum number of backoffs will * be as specified. * * @throws IllegalArgumentException if the initial delay is greater than the total backoff time * @throws IllegalArgumentException if the maximum number of backoffs is not greater than 1 * @throws IllegalStateException if it fails to find an acceptable base multiplier */ public static double findBase( long initialDelayMillis, long totalBackoffTimeMillis, int maximumBackoffs) { Assert.checkArgument(initialDelayMillis < totalBackoffTimeMillis); Assert.checkArgument(maximumBackoffs > 1); long scaledTotalTime = Math.round(((double) totalBackoffTimeMillis) / initialDelayMillis); double scaledTolerance = DEFAULT_TOLERANCE_MILLIS / initialDelayMillis; return getBaseImpl(scaledTotalTime, maximumBackoffs, scaledTolerance); } /** * T/D = (1 - b^N)/(1 - b) but this cannot be written as a simple equation for b in terms of T, D * and N so instead we use Newtons method to find an approximate value for b. * *
Let f(b) = (1 - b^N)/(1 - b) - T/D then we want to find b* such that f(b*) = 0, or more * precisely |f(b*)| < tolerance * *
Using Newton's method we can interatively find b* as follows: b1 = b0 - f(b0)/f'(b0), where * b0 is the current best guess for b* and b1 is the next best guess. * *
f'(b) = (f(b) + T/D - N*b^(N - 1))/(1 - b) * *
so * *
b1 = b0 - f(b0)(1 - b0)/(f(b0) + T/D - N*b0^(N - 1)) */ private static double getBaseImpl(long t, int n, double tolerance) { double b0 = 2; // Initial guess for b* double b0n = Math.pow(b0, n); double fb0 = f(b0, t, b0n); if (Math.abs(fb0) < tolerance) { // Initial guess was pretty good return b0; } for (int i = 0; i < MAX_STEPS; i++) { double fpb0 = fp(b0, t, n, fb0, b0n); double b1 = b0 - fb0 / fpb0; double b1n = Math.pow(b1, n); double fb1 = f(b1, t, b1n); if (Math.abs(fb1) < tolerance) { // Found an acceptable value return b1; } b0 = b1; b0n = b1n; fb0 = fb1; } throw new IllegalStateException("Failed to find base. Too many iterations."); } // Evaluate f(b), the function we are trying to find the zero for. // Note: passing b^N as a parameter so it only has to be calculated once private static double f(double b, long t, double bn) { return (1 - bn) / (1 - b) - t; } // Evaluate f'(b), the derivative of the function we are trying to find the zero for. // Note: passing f(b) and b^N as parameters for efficiency private static double fp(double b, long t, int n, double fb, double bn) { return (fb + t - n * bn / b) / (1 - b); } }