This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch 1.7.x
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/1.7.x by this push:
new 083040df84 docs: Clarify retry attempts parameter (#3266) (#3267)
083040df84 is described below
commit 083040df842ada624eb803f00af174af972a72ce
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Mon Jun 29 05:35:40 2026 +0800
docs: Clarify retry attempts parameter (#3266) (#3267)
Motivation:
The retry API documentation described attempts as the maximum number of
attempts, while the established behavior treats attempts as the maximum
number of retries after the first attempt. Reviewer feedback further
noted that the result-future wording should describe it as the result
of the last invoke attempt.
Modification:
Clarify the Scala and Java retry documentation and futures guide to
describe attempts as retries after the initial attempt, and describe
the result future as the result of the last invoke attempt. Add focused
Scala and Java assertions that lock the existing retry count semantics,
including attempts = 0.
Result:
The documented contract now matches the existing retry behavior without
changing runtime logic.
Tests:
- scalafmt --mode diff-ref=origin/main / passed
- scalafmt --list --mode diff-ref=origin/main / passed
- git diff --check / passed
- sbt actor-tests/Test/testOnly org.apache.pekko.pattern.RetrySpec / passed
(prior round)
- sbt actor-tests/Test/testOnly org.apache.pekko.pattern.PatternsTest / Not
run - no runtime change
References:
Refs #3233
---
.../org/apache/pekko/pattern/PatternsTest.java | 24 ++++++++++++++
.../scala/org/apache/pekko/pattern/RetrySpec.scala | 37 +++++++++++++++++++--
.../scala/org/apache/pekko/pattern/Patterns.scala | 38 +++++++++++-----------
.../org/apache/pekko/pattern/RetrySupport.scala | 24 +++++++-------
docs/src/main/paradox/futures.md | 2 +-
.../src/test/scala/docs/future/FutureDocSpec.scala | 2 +-
6 files changed, 92 insertions(+), 35 deletions(-)
diff --git
a/actor-tests/src/test/java/org/apache/pekko/pattern/PatternsTest.java
b/actor-tests/src/test/java/org/apache/pekko/pattern/PatternsTest.java
index 3b95d0190b..9c14a6373f 100644
--- a/actor-tests/src/test/java/org/apache/pekko/pattern/PatternsTest.java
+++ b/actor-tests/src/test/java/org/apache/pekko/pattern/PatternsTest.java
@@ -263,6 +263,30 @@ public class PatternsTest extends JUnitSuite {
assertEquals(expected, actual);
}
+ @Test
+ public void testRetryCompletionStageNoDelayUsesAttemptsAfterInitialAttempt()
throws Exception {
+ final AtomicInteger counter = new AtomicInteger(0);
+
+ CompletionStage<String> retriedFuture =
+ Patterns.retry(
+ () -> {
+ final int attempt = counter.incrementAndGet();
+ final CompletableFuture<String> failed = new
CompletableFuture<>();
+ failed.completeExceptionally(new
RuntimeException(String.valueOf(attempt)));
+ return failed;
+ },
+ 3,
+ ec);
+
+ try {
+ retriedFuture.toCompletableFuture().get(3, SECONDS);
+ Assert.fail("Expected ExecutionException");
+ } catch (ExecutionException e) {
+ assertEquals("4", e.getCause().getMessage());
+ }
+ assertEquals(4, counter.get());
+ }
+
@Test
public void testRetryCompletionStageRandomDelay() throws Exception {
final String expected = "hello";
diff --git
a/actor-tests/src/test/scala/org/apache/pekko/pattern/RetrySpec.scala
b/actor-tests/src/test/scala/org/apache/pekko/pattern/RetrySpec.scala
index 26b78b37a9..609d433a17 100644
--- a/actor-tests/src/test/scala/org/apache/pekko/pattern/RetrySpec.scala
+++ b/actor-tests/src/test/scala/org/apache/pekko/pattern/RetrySpec.scala
@@ -76,7 +76,7 @@ class RetrySpec extends PekkoSpec with RetrySupport {
}
}
- "return a failure for a Future that would have succeeded but retires were
exhausted" in {
+ "return a failure for a Future that would have succeeded but additional
attempts were exhausted" in {
@volatile var failCount = 0
def attempt() = {
@@ -90,10 +90,11 @@ class RetrySpec extends PekkoSpec with RetrySupport {
within(3.seconds) {
intercept[IllegalStateException] { Await.result(retried, remaining)
}.getMessage should ===("6")
+ failCount should ===(6)
}
}
- "return a failure for a Future that would have succeeded but retires were
exhausted with delay function" in {
+ "return a failure for a Future that would have succeeded but additional
attempts were exhausted with delay function" in {
@volatile var failCount = 0
@volatile var attemptedCount = 0;
@@ -111,6 +112,7 @@ class RetrySpec extends PekkoSpec with RetrySupport {
})
within(30000000.seconds) {
intercept[IllegalStateException] { Await.result(retried, remaining)
}.getMessage should ===("6")
+ failCount should ===(6)
attemptedCount shouldBe 5
}
}
@@ -131,11 +133,42 @@ class RetrySpec extends PekkoSpec with RetrySupport {
intercept[IllegalStateException] {
Await.result(retried, remaining)
}.getMessage should ===("1000")
+ failCount should ===(1000)
val elapse = System.currentTimeMillis() - start
elapse <= 100 shouldBe true
}
}
+ "make one attempt when no additional attempts are configured" in {
+ val counter = new AtomicInteger(0)
+
+ val retried =
+ retry(
+ () => Future.successful(counter.incrementAndGet()),
+ 0,
+ 100.milliseconds)
+
+ within(3.seconds) {
+ Await.result(retried, remaining) should ===(1)
+ }
+ counter.get() should ===(1)
+ }
+
+ "not make an additional attempt when none are configured" in {
+ val counter = new AtomicInteger(0)
+
+ val retried =
+ retry(
+ () => Future.failed(new
IllegalStateException(counter.incrementAndGet().toString)),
+ 0,
+ 100.milliseconds)
+
+ within(3.seconds) {
+ intercept[IllegalStateException] { Await.result(retried, remaining)
}.getMessage should ===("1")
+ }
+ counter.get() should ===(1)
+ }
+
"handle thrown exceptions in same way as failed Future" in {
@volatile var failCount = 0
diff --git a/actor/src/main/scala/org/apache/pekko/pattern/Patterns.scala
b/actor/src/main/scala/org/apache/pekko/pattern/Patterns.scala
index 97f876c299..214a75eb48 100644
--- a/actor/src/main/scala/org/apache/pekko/pattern/Patterns.scala
+++ b/actor/src/main/scala/org/apache/pekko/pattern/Patterns.scala
@@ -507,7 +507,7 @@ object Patterns {
* The first attempt will be made immediately, each subsequent attempt will
be made immediately
* if the previous attempt failed.
*
- * If attempts are exhausted the returned completion operator is simply the
result of invoking attempt.
+ * If all additional attempts are exhausted the returned completion operator
is simply the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries
* and therefore must be thread safe (i.e. not touch unsafe mutable state).
*/
@@ -524,13 +524,13 @@ object Patterns {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Return an empty [[Optional]] instance for no delay.
*
- * If attempts are exhausted the returned completion operator is simply the
result of invoking attempt.
+ * If all additional attempts are exhausted the returned completion operator
is simply the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries
* and therefore must be thread safe (i.e. not touch unsafe mutable state).
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the
initial attempt
* @param ec the execution context
* @return the result [[java.util.concurrent.CompletionStage]] which maybe
retried
*
@@ -550,7 +550,7 @@ object Patterns {
* The first attempt will be made immediately, each subsequent attempt will
be made with a backoff time,
* if the previous attempt failed.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*
@@ -585,13 +585,13 @@ object Patterns {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Return an empty [[Optional]] instance for no delay.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the
initial attempt
* @param minBackoff minimum (initial) duration until the child actor will
* started again, if it is terminated
* @param maxBackoff the exponential back-off is capped to this duration
@@ -626,7 +626,7 @@ object Patterns {
* The first attempt will be made immediately, each subsequent attempt will
be made with a backoff time,
* if the previous attempt failed.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*
@@ -661,13 +661,13 @@ object Patterns {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Return an empty [[Optional]] instance for no delay.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the
initial attempt
* @param minBackoff minimum (initial) duration until the child actor will
* started again, if it is terminated
* @param maxBackoff the exponential back-off is capped to this duration
@@ -705,7 +705,7 @@ object Patterns {
* The first attempt will be made immediately, and each subsequent attempt
will be made after 'delay'.
* A scheduler (eg context.system.scheduler) must be provided to delay each
retry
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*/
@@ -724,7 +724,7 @@ object Patterns {
* The first attempt will be made immediately, and each subsequent attempt
will be made after 'delay'.
* A scheduler (eg context.system.scheduler) must be provided to delay each
retry
*
- * If attempts are exhausted the returned completion operator is simply the
result of invoking attempt.
+ * If all additional attempts are exhausted the returned completion operator
is simply the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries
* and therefore must be thread safe (i.e. not touch unsafe mutable state).
*/
@@ -743,13 +743,13 @@ object Patterns {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Return an empty [[Optional]] instance for no delay.
*
- * If attempts are exhausted the returned completion operator is simply the
result of invoking attempt.
+ * If all additional attempts are exhausted the returned completion operator
is simply the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries
* and therefore must be thread safe (i.e. not touch unsafe mutable state).
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the
initial attempt
* @param delay the delay between each attempt
* @param system the actor system
* @return the result [[java.util.concurrent.CompletionStage]] which maybe
retried
@@ -769,7 +769,7 @@ object Patterns {
* The first attempt will be made immediately, and each subsequent attempt
will be made after 'delay'.
* A scheduler (eg context.system.scheduler) must be provided to delay each
retry
*
- * If attempts are exhausted the returned completion operator is simply the
result of invoking attempt.
+ * If all additional attempts are exhausted the returned completion operator
is simply the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries
* and therefore must be thread safe (i.e. not touch unsafe mutable state).
*/
@@ -791,13 +791,13 @@ object Patterns {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Return an empty [[Optional]] instance for no delay.
*
- * If attempts are exhausted the returned completion operator is simply the
result of invoking attempt.
+ * If all additional attempts are exhausted the returned completion operator
is simply the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries
* and therefore must be thread safe (i.e. not touch unsafe mutable state).
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the
initial attempt
* @param delay the delay between each attempt
* @param scheduler the scheduler for scheduling a delay
* @param ec the execution context
@@ -826,7 +826,7 @@ object Patterns {
* You could provide a function to generate the next delay duration after
first attempt,
* this function should never return `null`, otherwise an
[[java.lang.IllegalArgumentException]] will be through.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*/
@@ -856,13 +856,13 @@ object Patterns {
* You could provide a function to generate the next delay duration after
first attempt,
* this function should never return `null`, otherwise an
[[java.lang.IllegalArgumentException]] will be through.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent tries and
* therefore must be thread safe (i.e. not touch unsafe mutable state).
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the
initial attempt
* @param delayFunction the function to generate the next delay duration,
`None` for no delay
* @param scheduler the scheduler for scheduling a delay
* @param context the execution context
diff --git a/actor/src/main/scala/org/apache/pekko/pattern/RetrySupport.scala
b/actor/src/main/scala/org/apache/pekko/pattern/RetrySupport.scala
index 5582d360cd..ed9ff0e6ab 100644
--- a/actor/src/main/scala/org/apache/pekko/pattern/RetrySupport.scala
+++ b/actor/src/main/scala/org/apache/pekko/pattern/RetrySupport.scala
@@ -34,7 +34,7 @@ trait RetrySupport {
* The first attempt will be made immediately, each subsequent attempt will
be made immediately
* if the previous attempt failed.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -57,7 +57,7 @@ trait RetrySupport {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Returns [[scala.None]] for no delay.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -71,7 +71,7 @@ trait RetrySupport {
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the initial
attempt
* @param ec the execution context
* @return the result future which maybe retried
*
@@ -88,7 +88,7 @@ trait RetrySupport {
* The first attempt will be made immediately, each subsequent attempt will
be made with a backoff time,
* if the previous attempt failed.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -135,7 +135,7 @@ trait RetrySupport {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Returns [[scala.None]] for no delay.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -156,7 +156,7 @@ trait RetrySupport {
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the initial
attempt
* @param minBackoff minimum (initial) duration until the child actor will
* started again, if it is terminated
* @param maxBackoff the exponential back-off is capped to this duration
@@ -193,7 +193,7 @@ trait RetrySupport {
* The first attempt will be made immediately, each subsequent attempt will
be made after 'delay'.
* A scheduler (eg context.system.scheduler) must be provided to delay each
retry.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -222,7 +222,7 @@ trait RetrySupport {
* each subsequent attempt will be made after the 'delay' return by
`delayFunction` (the input next attempt count start from 1).
* Returns [[scala.None]] for no delay.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -241,7 +241,7 @@ trait RetrySupport {
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the initial
attempt
* @param delay the delay duration
* @param ec the execution context
* @param scheduler the scheduler for scheduling a delay
@@ -268,7 +268,7 @@ trait RetrySupport {
* You could provide a function to generate the next delay duration after
first attempt,
* this function should never return `null`, otherwise an
[[java.lang.IllegalArgumentException]] will be through.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -303,7 +303,7 @@ trait RetrySupport {
* You could provide a function to generate the next delay duration after
first attempt,
* this function should never return `null`, otherwise an
[[java.lang.IllegalArgumentException]] will be through.
*
- * If attempts are exhausted the returned future is simply the result of
invoking attempt.
+ * If all additional attempts are exhausted the returned future is simply
the result of the last invoke attempt.
* Note that the attempt function will be invoked on the given execution
context for subsequent
* tries and therefore must be thread safe (i.e. not touch unsafe mutable
state).
*
@@ -323,7 +323,7 @@ trait RetrySupport {
*
* @param attempt the function to be attempted
* @param shouldRetry the predicate to determine if the attempt should be
retried
- * @param attempts the maximum number of attempts
+ * @param attempts the maximum number of retry attempts after the initial
attempt
* @param delayFunction the function to generate the next delay duration,
`None` for no delay
* @param ec the execution context
* @param scheduler the scheduler for scheduling a delay
diff --git a/docs/src/main/paradox/futures.md b/docs/src/main/paradox/futures.md
index 454336c386..1f8a069fd3 100644
--- a/docs/src/main/paradox/futures.md
+++ b/docs/src/main/paradox/futures.md
@@ -25,7 +25,7 @@ Java
## Retry
-@scala[`org.apache.pekko.pattern.retry`]@java[@javadoc[org.apache.pekko.pattern.Patterns.retry](pekko.pattern.Patterns#retry)]
will retry a
@scala[@scaladoc[Future](scala.concurrent.Future)]@java[@javadoc[CompletionStage](java.util.concurrent.CompletionStage)]
some number of times with a delay between each attempt.
+@scala[`org.apache.pekko.pattern.retry`]@java[@javadoc[org.apache.pekko.pattern.Patterns.retry](pekko.pattern.Patterns#retry)]
will retry a
@scala[@scaladoc[Future](scala.concurrent.Future)]@java[@javadoc[CompletionStage](java.util.concurrent.CompletionStage)]
some number of times with a delay between each attempt. The `attempts`
parameter is the maximum number of retry attempts after the initial attempt.
Scala
: @@snip
[FutureDocSpec.scala](/docs/src/test/scala/docs/future/FutureDocSpec.scala) {
#retry }
diff --git a/docs/src/test/scala/docs/future/FutureDocSpec.scala
b/docs/src/test/scala/docs/future/FutureDocSpec.scala
index 274c0f8ba2..ae4c891933 100644
--- a/docs/src/test/scala/docs/future/FutureDocSpec.scala
+++ b/docs/src/test/scala/docs/future/FutureDocSpec.scala
@@ -521,7 +521,7 @@ class FutureDocSpec extends PekkoSpec {
} else Future.successful(5)
}
- // Return a new future that will retry up to 10 times
+ // Return a new future with up to 10 retry attempts after the initial
attempt
val retried: Future[Int] = pekko.pattern.retry(() => futureToAttempt(),
attempts = 10, 100.milliseconds)
// #retry
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]