001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util.backoff; 018 019import org.springframework.util.Assert; 020 021/** 022 * Implementation of {@link BackOff} that increases the back off period for each 023 * retry attempt. When the interval has reached the {@link #setMaxInterval(long) 024 * max interval}, it is no longer increased. Stops retrying once the 025 * {@link #setMaxElapsedTime(long) max elapsed time} has been reached. 026 * 027 * <p>Example: The default interval is {@value #DEFAULT_INITIAL_INTERVAL} ms, 028 * the default multiplier is {@value #DEFAULT_MULTIPLIER}, and the default max 029 * interval is {@value #DEFAULT_MAX_INTERVAL}. For 10 attempts the sequence will be 030 * as follows: 031 * 032 * <pre> 033 * request# back off 034 * 035 * 1 2000 036 * 2 3000 037 * 3 4500 038 * 4 6750 039 * 5 10125 040 * 6 15187 041 * 7 22780 042 * 8 30000 043 * 9 30000 044 * 10 30000 045 * </pre> 046 * 047 * <p>Note that the default max elapsed time is {@link Long#MAX_VALUE}. Use 048 * {@link #setMaxElapsedTime(long)} to limit the maximum length of time 049 * that an instance should accumulate before returning 050 * {@link BackOffExecution#STOP}. 051 * 052 * @author Stephane Nicoll 053 * @since 4.1 054 */ 055public class ExponentialBackOff implements BackOff { 056 057 /** 058 * The default initial interval. 059 */ 060 public static final long DEFAULT_INITIAL_INTERVAL = 2000L; 061 062 /** 063 * The default multiplier (increases the interval by 50%). 064 */ 065 public static final double DEFAULT_MULTIPLIER = 1.5; 066 067 /** 068 * The default maximum back off time. 069 */ 070 public static final long DEFAULT_MAX_INTERVAL = 30000L; 071 072 /** 073 * The default maximum elapsed time. 074 */ 075 public static final long DEFAULT_MAX_ELAPSED_TIME = Long.MAX_VALUE; 076 077 078 private long initialInterval = DEFAULT_INITIAL_INTERVAL; 079 080 private double multiplier = DEFAULT_MULTIPLIER; 081 082 private long maxInterval = DEFAULT_MAX_INTERVAL; 083 084 private long maxElapsedTime = DEFAULT_MAX_ELAPSED_TIME; 085 086 087 /** 088 * Create an instance with the default settings. 089 * @see #DEFAULT_INITIAL_INTERVAL 090 * @see #DEFAULT_MULTIPLIER 091 * @see #DEFAULT_MAX_INTERVAL 092 * @see #DEFAULT_MAX_ELAPSED_TIME 093 */ 094 public ExponentialBackOff() { 095 } 096 097 /** 098 * Create an instance with the supplied settings. 099 * @param initialInterval the initial interval in milliseconds 100 * @param multiplier the multiplier (should be greater than or equal to 1) 101 */ 102 public ExponentialBackOff(long initialInterval, double multiplier) { 103 checkMultiplier(multiplier); 104 this.initialInterval = initialInterval; 105 this.multiplier = multiplier; 106 } 107 108 109 /** 110 * The initial interval in milliseconds. 111 */ 112 public void setInitialInterval(long initialInterval) { 113 this.initialInterval = initialInterval; 114 } 115 116 /** 117 * Return the initial interval in milliseconds. 118 */ 119 public long getInitialInterval() { 120 return this.initialInterval; 121 } 122 123 /** 124 * The value to multiply the current interval by for each retry attempt. 125 */ 126 public void setMultiplier(double multiplier) { 127 checkMultiplier(multiplier); 128 this.multiplier = multiplier; 129 } 130 131 /** 132 * Return the value to multiply the current interval by for each retry attempt. 133 */ 134 public double getMultiplier() { 135 return this.multiplier; 136 } 137 138 /** 139 * The maximum back off time. 140 */ 141 public void setMaxInterval(long maxInterval) { 142 this.maxInterval = maxInterval; 143 } 144 145 /** 146 * Return the maximum back off time. 147 */ 148 public long getMaxInterval() { 149 return this.maxInterval; 150 } 151 152 /** 153 * The maximum elapsed time in milliseconds after which a call to 154 * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. 155 */ 156 public void setMaxElapsedTime(long maxElapsedTime) { 157 this.maxElapsedTime = maxElapsedTime; 158 } 159 160 /** 161 * Return the maximum elapsed time in milliseconds after which a call to 162 * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. 163 */ 164 public long getMaxElapsedTime() { 165 return this.maxElapsedTime; 166 } 167 168 @Override 169 public BackOffExecution start() { 170 return new ExponentialBackOffExecution(); 171 } 172 173 private void checkMultiplier(double multiplier) { 174 Assert.isTrue(multiplier >= 1, () -> "Invalid multiplier '" + multiplier + "'. Should be greater than " + 175 "or equal to 1. A multiplier of 1 is equivalent to a fixed interval."); 176 } 177 178 179 private class ExponentialBackOffExecution implements BackOffExecution { 180 181 private long currentInterval = -1; 182 183 private long currentElapsedTime = 0; 184 185 @Override 186 public long nextBackOff() { 187 if (this.currentElapsedTime >= maxElapsedTime) { 188 return STOP; 189 } 190 191 long nextInterval = computeNextInterval(); 192 this.currentElapsedTime += nextInterval; 193 return nextInterval; 194 } 195 196 private long computeNextInterval() { 197 long maxInterval = getMaxInterval(); 198 if (this.currentInterval >= maxInterval) { 199 return maxInterval; 200 } 201 else if (this.currentInterval < 0) { 202 long initialInterval = getInitialInterval(); 203 this.currentInterval = (initialInterval < maxInterval 204 ? initialInterval : maxInterval); 205 } 206 else { 207 this.currentInterval = multiplyInterval(maxInterval); 208 } 209 return this.currentInterval; 210 } 211 212 private long multiplyInterval(long maxInterval) { 213 long i = this.currentInterval; 214 i *= getMultiplier(); 215 return (i > maxInterval ? maxInterval : i); 216 } 217 218 219 @Override 220 public String toString() { 221 StringBuilder sb = new StringBuilder("ExponentialBackOff{"); 222 sb.append("currentInterval=").append(this.currentInterval < 0 ? "n/a" : this.currentInterval + "ms"); 223 sb.append(", multiplier=").append(getMultiplier()); 224 sb.append('}'); 225 return sb.toString(); 226 } 227 } 228 229}