From f6a09f3fad2d5b960fef87f1e3ee633ca16c93a9 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Wed, 16 Jun 2021 13:17:02 -0400 Subject: [PATCH 1/2] Add maxAttempts to ExponentialBackOff If you wish to stop after a certain number of attempts with an `ExponentialBackOff` you have to calculate the `maxElapsedTime` corresponding to the number of attempts. Add a new property to make it more convenient to stop after a certain number of attempts. See gh-27071 --- .../util/backoff/ExponentialBackOff.java | 44 +++++++++++++++++-- .../util/ExponentialBackOffTests.java | 19 +++++++- 2 files changed, 59 insertions(+), 4 deletions(-) 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..7e572a0eec5 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-2021 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. @@ -47,9 +47,11 @@ import org.springframework.util.Assert; *

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}. + * {@link BackOffExecution#STOP}. Or use {@link #setMaxAttempts} to limit + * the number of attempts. * * @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 maxElapsedTime. + * @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 maxElapsedTime. + * @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 maxAttempts. + * @since 5.3.8 + * @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 maxAttempts. + * @since 5.3.8 + * @see #getMaxElapsedTime() + */ + public int getMaxAttempts() { + return this.maxAttempts; + } + @Override public BackOffExecution start() { return new ExponentialBackOffExecution(); @@ -182,14 +217,17 @@ public class ExponentialBackOff implements BackOff { private long currentElapsedTime = 0; + private int attempts; + @Override public long nextBackOff() { - if (this.currentElapsedTime >= maxElapsedTime) { + if (this.currentElapsedTime >= maxElapsedTime || this.attempts >= maxAttempts) { 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..b24d349c91a 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-2021 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; @@ -131,4 +135,17 @@ class ExponentialBackOffTests { assertThat(execution.toString()).isEqualTo("ExponentialBackOff{currentInterval=4000ms, multiplier=2.0}"); } + @Test + void maxAttempts() { + ExponentialBackOff bo = new ExponentialBackOff(); + bo.setInitialInterval(1_000L); + bo.setMultiplier(2.0); + bo.setMaxInterval(10_000L); + bo.setMaxAttempts(6); + List delays = new ArrayList<>(); + BackOffExecution boEx = bo.start(); + IntStream.range(0, 7).forEach(i -> delays.add(boEx.nextBackOff())); + assertThat(delays).containsExactly(1_000L, 2_000L, 4_000L, 8_000L, 10_000L, 10_000L, -1L); + } + } From 609580bfb991a59e98def31a3fec85d714b8eeeb Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sat, 26 Aug 2023 12:54:24 +0200 Subject: [PATCH 2/2] Polish "Add maxAttempts to ExponentialBackOff" See gh-27071 --- .../util/backoff/ExponentialBackOff.java | 27 ++++++++++--------- .../util/ExponentialBackOffTests.java | 20 +++++++------- 2 files changed, 25 insertions(+), 22 deletions(-) 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 7e572a0eec5..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-2021 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,11 +44,11 @@ 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}. Or use {@link #setMaxAttempts} to limit - * the number of attempts. + *

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 @@ -161,7 +161,7 @@ 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 maxElapsedTime. + * @param maxElapsedTime the maximum elapsed time * @see #setMaxAttempts(int) */ public void setMaxElapsedTime(long maxElapsedTime) { @@ -171,7 +171,7 @@ 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 maxElapsedTime. + * @return the maximum elapsed time * @see #getMaxAttempts() */ public long getMaxElapsedTime() { @@ -181,8 +181,8 @@ public class ExponentialBackOff implements BackOff { /** * The maximum number of attempts after which a call to * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. - * @param maxAttempts the maxAttempts. - * @since 5.3.8 + * @param maxAttempts the maximum number of attempts. + * @since 6.1 * @see #setMaxElapsedTime(long) */ public void setMaxAttempts(int maxAttempts) { @@ -192,8 +192,8 @@ public class ExponentialBackOff implements BackOff { /** * Return the maximum number of attempts after which a call to * {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}. - * @return the maxAttempts. - * @since 5.3.8 + * @return the maximum number of attempts + * @since 6.1 * @see #getMaxElapsedTime() */ public int getMaxAttempts() { @@ -221,7 +221,8 @@ public class ExponentialBackOff implements BackOff { @Override public long nextBackOff() { - if (this.currentElapsedTime >= maxElapsedTime || this.attempts >= maxAttempts) { + if (this.currentElapsedTime >= getMaxElapsedTime() + || this.attempts >= getMaxAttempts()) { return STOP; } 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 b24d349c91a..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-2021 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. @@ -29,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 { @@ -137,15 +139,15 @@ class ExponentialBackOffTests { @Test void maxAttempts() { - ExponentialBackOff bo = new ExponentialBackOff(); - bo.setInitialInterval(1_000L); - bo.setMultiplier(2.0); - bo.setMaxInterval(10_000L); - bo.setMaxAttempts(6); + ExponentialBackOff backOff = new ExponentialBackOff(); + backOff.setInitialInterval(1000L); + backOff.setMultiplier(2.0); + backOff.setMaxInterval(10000L); + backOff.setMaxAttempts(6); List delays = new ArrayList<>(); - BackOffExecution boEx = bo.start(); - IntStream.range(0, 7).forEach(i -> delays.add(boEx.nextBackOff())); - assertThat(delays).containsExactly(1_000L, 2_000L, 4_000L, 8_000L, 10_000L, 10_000L, -1L); + 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); } }