diff --git a/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java b/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java index 0331699a5b6..483ee8b6eea 100644 --- a/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java +++ b/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,12 +44,14 @@ import org.springframework.util.Assert; * 10 30000 * * - *

Note that the default max elapsed time is {@link Long#MAX_VALUE}. Use - * {@link #setMaxElapsedTime(long)} to limit the maximum length of time - * that an instance should accumulate before returning - * {@link BackOffExecution#STOP}. + *

Note that the default max elapsed time and maximum number of attempts are both + * {@link Long#MAX_VALUE}. Use {@link #setMaxElapsedTime(long)} to limit the maximum + * length of time that an instance should accumulate before returning + * {@link BackOffExecution#STOP}. Alternatively, use {@link #setMaxAttempts} to limit + * the number of attempts. The execution stops when any of those two limit is reached. * * @author Stephane Nicoll + * @author Gary Russell * @since 4.1 */ public class ExponentialBackOff implements BackOff { @@ -74,6 +76,10 @@ public class ExponentialBackOff implements BackOff { */ public static final long DEFAULT_MAX_ELAPSED_TIME = Long.MAX_VALUE; + /** + * The default maximum attempts. + */ + public static final int DEFAULT_MAX_ATTEMPTS = Integer.MAX_VALUE; private long initialInterval = DEFAULT_INITIAL_INTERVAL; @@ -83,6 +89,8 @@ public class ExponentialBackOff implements BackOff { private long maxElapsedTime = DEFAULT_MAX_ELAPSED_TIME; + private int maxAttempts = DEFAULT_MAX_ATTEMPTS; + /** * Create an instance with the default settings. @@ -90,6 +98,7 @@ public class ExponentialBackOff implements BackOff { * @see #DEFAULT_MULTIPLIER * @see #DEFAULT_MAX_INTERVAL * @see #DEFAULT_MAX_ELAPSED_TIME + * @see #DEFAULT_MAX_ATTEMPTS */ public ExponentialBackOff() { } @@ -152,6 +161,8 @@ public class ExponentialBackOff implements BackOff { /** * The maximum elapsed time in milliseconds after which a call to * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. + * @param maxElapsedTime the maximum elapsed time + * @see #setMaxAttempts(int) */ public void setMaxElapsedTime(long maxElapsedTime) { this.maxElapsedTime = maxElapsedTime; @@ -160,11 +171,35 @@ public class ExponentialBackOff implements BackOff { /** * Return the maximum elapsed time in milliseconds after which a call to * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. + * @return the maximum elapsed time + * @see #getMaxAttempts() */ public long getMaxElapsedTime() { return this.maxElapsedTime; } + /** + * The maximum number of attempts after which a call to + * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. + * @param maxAttempts the maximum number of attempts. + * @since 6.1 + * @see #setMaxElapsedTime(long) + */ + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + /** + * Return the maximum number of attempts after which a call to + * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. + * @return the maximum number of attempts + * @since 6.1 + * @see #getMaxElapsedTime() + */ + public int getMaxAttempts() { + return this.maxAttempts; + } + @Override public BackOffExecution start() { return new ExponentialBackOffExecution(); @@ -182,14 +217,18 @@ public class ExponentialBackOff implements BackOff { private long currentElapsedTime = 0; + private int attempts; + @Override public long nextBackOff() { - if (this.currentElapsedTime >= maxElapsedTime) { + if (this.currentElapsedTime >= getMaxElapsedTime() + || this.attempts >= getMaxAttempts()) { return STOP; } long nextInterval = computeNextInterval(); this.currentElapsedTime += nextInterval; + this.attempts++; return nextInterval; } diff --git a/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java b/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java index 8080f82fe0a..a539479c77c 100644 --- a/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java +++ b/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package org.springframework.util; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + import org.junit.jupiter.api.Test; import org.springframework.util.backoff.BackOffExecution; @@ -25,6 +29,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** + * Tests for {@link ExponentialBackOff}. + * * @author Stephane Nicoll */ class ExponentialBackOffTests { @@ -131,4 +137,17 @@ class ExponentialBackOffTests { assertThat(execution.toString()).isEqualTo("ExponentialBackOff{currentInterval=4000ms, multiplier=2.0}"); } + @Test + void maxAttempts() { + ExponentialBackOff backOff = new ExponentialBackOff(); + backOff.setInitialInterval(1000L); + backOff.setMultiplier(2.0); + backOff.setMaxInterval(10000L); + backOff.setMaxAttempts(6); + List delays = new ArrayList<>(); + BackOffExecution execution = backOff.start(); + IntStream.range(0, 7).forEach(i -> delays.add(execution.nextBackOff())); + assertThat(delays).containsExactly(1000L, 2000L, 4000L, 8000L, 10000L, 10000L, BackOffExecution.STOP); + } + }