This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 20d6357f [api][java] Validate retry parameters in RetryExecutor 
builder (#1142)
20d6357f is described below

commit 20d6357f51ce29b7774cccd314345497ceeb44c7
Author: william <[email protected]>
AuthorDate: Sun Sep 20 16:43:58 2026 +0800

    [api][java] Validate retry parameters in RetryExecutor builder (#1142)
    
    Generated-by: CodeBuddy (Auto model)
---
 .../org/apache/flink/agents/api/RetryExecutor.java | 21 ++++++++
 .../apache/flink/agents/api/RetryExecutorTest.java | 56 ++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/api/src/main/java/org/apache/flink/agents/api/RetryExecutor.java 
b/api/src/main/java/org/apache/flink/agents/api/RetryExecutor.java
index b369e1d1..b2bd174b 100644
--- a/api/src/main/java/org/apache/flink/agents/api/RetryExecutor.java
+++ b/api/src/main/java/org/apache/flink/agents/api/RetryExecutor.java
@@ -199,7 +199,28 @@ public class RetryExecutor {
             return this;
         }
 
+        /**
+         * Builds a {@link RetryExecutor} after checking the configured values.
+         *
+         * @throws IllegalArgumentException if {@code maxRetries} is negative, 
if {@code
+         *     initialBackoffMs} is negative, or if {@code maxBackoffMs} is 
smaller than {@code
+         *     initialBackoffMs}
+         */
         public RetryExecutor build() {
+            if (maxRetries < 0) {
+                throw new IllegalArgumentException(
+                        "maxRetries must be >= 0, but was " + maxRetries);
+            }
+            if (initialBackoffMs < 0) {
+                throw new IllegalArgumentException(
+                        "initialBackoffMs must be >= 0, but was " + 
initialBackoffMs);
+            }
+            if (maxBackoffMs < initialBackoffMs) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "maxBackoffMs must be >= initialBackoffMs, but 
was %d < %d",
+                                maxBackoffMs, initialBackoffMs));
+            }
             return new RetryExecutor(
                     maxRetries, initialBackoffMs, maxBackoffMs, 
retryablePredicate);
         }
diff --git 
a/api/src/test/java/org/apache/flink/agents/api/RetryExecutorTest.java 
b/api/src/test/java/org/apache/flink/agents/api/RetryExecutorTest.java
index 982ea3ce..13bd52eb 100644
--- a/api/src/test/java/org/apache/flink/agents/api/RetryExecutorTest.java
+++ b/api/src/test/java/org/apache/flink/agents/api/RetryExecutorTest.java
@@ -274,4 +274,60 @@ class RetryExecutorTest {
         assertThat(result).isEqualTo("success");
         assertThat(attempts.get()).isEqualTo(2);
     }
+
+    @Test
+    @DisplayName("Reject a negative maxRetries at build time")
+    void testBuildRejectsNegativeMaxRetries() {
+        assertThatThrownBy(() -> 
RetryExecutor.builder().maxRetries(-1).build())
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("maxRetries must be >= 0")
+                .hasMessageContaining("-1");
+    }
+
+    @Test
+    @DisplayName("Reject a negative initialBackoffMs at build time")
+    void testBuildRejectsNegativeInitialBackoffMs() {
+        assertThatThrownBy(() -> 
RetryExecutor.builder().initialBackoffMs(-5).build())
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("initialBackoffMs must be >= 0")
+                .hasMessageContaining("-5");
+    }
+
+    @Test
+    @DisplayName("Reject a maxBackoffMs smaller than initialBackoffMs")
+    void testBuildRejectsMaxBackoffBelowInitialBackoff() {
+        assertThatThrownBy(
+                        () ->
+                                RetryExecutor.builder()
+                                        .initialBackoffMs(100)
+                                        .maxBackoffMs(50)
+                                        .build())
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("maxBackoffMs must be >= 
initialBackoffMs");
+    }
+
+    @Test
+    @DisplayName("Boundary values are accepted, and zero maxRetries still runs 
the operation")
+    void testBuildAcceptsBoundaryValues() {
+        RetryExecutor executor =
+                
RetryExecutor.builder().maxRetries(0).initialBackoffMs(0).maxBackoffMs(0).build();
+
+        assertThat(executor.getMaxRetries()).isZero();
+        assertThat(executor.getInitialBackoffMs()).isZero();
+        assertThat(executor.getMaxBackoffMs()).isZero();
+
+        AtomicInteger attempts = new AtomicInteger(0);
+        Callable<String> operation =
+                () -> {
+                    attempts.incrementAndGet();
+                    throw new SocketTimeoutException("Always fails");
+                };
+
+        assertThatThrownBy(() -> executor.execute(operation, "testOperation"))
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("failed after 0 retries")
+                .hasMessageContaining("Always fails");
+
+        assertThat(attempts.get()).isEqualTo(1);
+    }
 }

Reply via email to