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}