From f6a09f3fad2d5b960fef87f1e3ee633ca16c93a9 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Wed, 16 Jun 2021 13:17:02 -0400 Subject: [PATCH] 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); + } + }