This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 64e26c8e51 docs: Clarify retry attempts parameter (#3266)
64e26c8e51 is described below
commit 64e26c8e514e30d2c619ede82ad700965bd9ba62
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Mon Jun 29 03:40:00 2026 +0800
docs: Clarify retry attempts parameter (#3266)
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 | 22 +++++++++++++
.../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, 90 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 39211d6214..c57f4c9b90 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
@@ -262,6 +262,28 @@ public class PatternsTest {
assertEquals(expected, actual);
}
+ @Test
+ public void testRetryCompletionStageNoDelayUsesAttemptsAfterInitialAttempt()
{
+ 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);
+
+ ExecutionException exception =
+ assertThrows(
+ ExecutionException.class, () ->
retriedFuture.toCompletableFuture().get(3, SECONDS));
+ assertEquals("4", exception.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 bc7168876c..1ba1bcd032 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 1920d29efd..80b4fd61bf 100644
--- a/actor/src/main/scala/org/apache/pekko/pattern/Patterns.scala
+++ b/actor/src/main/scala/org/apache/pekko/pattern/Patterns.scala
@@ -489,7 +489,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).
*/
@@ -506,13 +506,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
*
@@ -532,7 +532,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).
*
@@ -567,13 +567,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
@@ -608,7 +608,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).
*
@@ -643,13 +643,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
@@ -687,7 +687,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).
*/
@@ -706,7 +706,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).
*/
@@ -725,13 +725,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
@@ -751,7 +751,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).
*/
@@ -773,13 +773,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
@@ -808,7 +808,7 @@ object Patterns {
* You could provide a function to generate the next delay duration after
first attempt,
* this function should never return `null`, otherwise a
[[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).
*/
@@ -838,13 +838,13 @@ object Patterns {
* You could provide a function to generate the next delay duration after
first attempt,
* this function should never return `null`, otherwise a
[[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 ad94610fd5..16f842238e 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 a
[[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 a
[[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]