001/* 002 * Copyright 2002-2016 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 019/** 020 * Implementation of {@link BackOff} that increases the back off period for each 021 * retry attempt. When the interval has reached the {@link #setMaxInterval(long) 022 * max interval}, it is no longer increased. Stops retrying once the 023 * {@link #setMaxElapsedTime(long) max elapsed time} has been reached. 024 * 025 * <p>Example: The default interval is {@value #DEFAULT_INITIAL_INTERVAL} ms, 026 * the default multiplier is {@value #DEFAULT_MULTIPLIER}, and the default max 027 * interval is {@value #DEFAULT_MAX_INTERVAL}. For 10 attempts the sequence will be 028 * as follows: 029 * 030 * <pre> 031 * request# back off 032 * 033 * 1 2000 034 * 2 3000 035 * 3 4500 036 * 4 6750 037 * 5 10125 038 * 6 15187 039 * 7 22780 040 * 8 30000 041 * 9 30000 042 * 10 30000 043 * </pre> 044 * 045 * <p>Note that the default max elapsed time is {@link Long#MAX_VALUE}. Use 046 * {@link #setMaxElapsedTime(long)} to limit the maximum length of time 047 * that an instance should accumulate before returning 048 * {@link BackOffExecution#STOP}. 049 * 050 * @author Stephane Nicoll 051 * @since 4.1 052 */ 053public class ExponentialBackOff implements BackOff { 054 055 /** 056 * The default initial interval. 057 */ 058 public static final long DEFAULT_INITIAL_INTERVAL = 2000L; 059 060 /** 061 * The default multiplier (increases the interval by 50%). 062 */ 063 public static final double DEFAULT_MULTIPLIER = 1.5; 064 065 /** 066 * The default maximum back off time. 067 */ 068 public static final long DEFAULT_MAX_INTERVAL = 30000L; 069 070 /** 071 * The default maximum elapsed time. 072 */ 073 public static final long DEFAULT_MAX_ELAPSED_TIME = Long.MAX_VALUE; 074 075 076 private long initialInterval = DEFAULT_INITIAL_INTERVAL; 077 078 private double multiplier = DEFAULT_MULTIPLIER; 079 080 private long maxInterval = DEFAULT_MAX_INTERVAL; 081 082 private long maxElapsedTime = DEFAULT_MAX_ELAPSED_TIME; 083 084 085 /** 086 * Create an instance with the default settings. 087 * @see #DEFAULT_INITIAL_INTERVAL 088 * @see #DEFAULT_MULTIPLIER 089 * @see #DEFAULT_MAX_INTERVAL 090 * @see #DEFAULT_MAX_ELAPSED_TIME 091 */ 092 public ExponentialBackOff() { 093 } 094 095 /** 096 * Create an instance with the supplied settings. 097 * @param initialInterval the initial interval in milliseconds 098 * @param multiplier the multiplier (should be greater than or equal to 1) 099 */ 100 public ExponentialBackOff(long initialInterval, double multiplier) { 101 checkMultiplier(multiplier); 102 this.initialInterval = initialInterval; 103 this.multiplier = multiplier; 104 } 105 106 107 /** 108 * The initial interval in milliseconds. 109 */ 110 public void setInitialInterval(long initialInterval) { 111 this.initialInterval = initialInterval; 112 } 113 114 /** 115 * Return the initial interval in milliseconds. 116 */ 117 public long getInitialInterval() { 118 return initialInterval; 119 } 120 121 /** 122 * The value to multiply the current interval by for each retry attempt. 123 */ 124 public void setMultiplier(double multiplier) { 125 checkMultiplier(multiplier); 126 this.multiplier = multiplier; 127 } 128 129 /** 130 * Return the value to multiply the current interval by for each retry attempt. 131 */ 132 public double getMultiplier() { 133 return multiplier; 134 } 135 136 /** 137 * The maximum back off time. 138 */ 139 public void setMaxInterval(long maxInterval) { 140 this.maxInterval = maxInterval; 141 } 142 143 /** 144 * Return the maximum back off time. 145 */ 146 public long getMaxInterval() { 147 return maxInterval; 148 } 149 150 /** 151 * The maximum elapsed time in milliseconds after which a call to 152 * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. 153 */ 154 public void setMaxElapsedTime(long maxElapsedTime) { 155 this.maxElapsedTime = maxElapsedTime; 156 } 157 158 /** 159 * Return the maximum elapsed time in milliseconds after which a call to 160 * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. 161 */ 162 public long getMaxElapsedTime() { 163 return maxElapsedTime; 164 } 165 166 @Override 167 public BackOffExecution start() { 168 return new ExponentialBackOffExecution(); 169 } 170 171 private void checkMultiplier(double multiplier) { 172 if (multiplier < 1) { 173 throw new IllegalArgumentException("Invalid multiplier '" + multiplier + "'. Should be equal" + 174 "or higher than 1. A multiplier of 1 is equivalent to a fixed interval"); 175 } 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}