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}