Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions core/src/main/java/com/google/adk/workflow/RetryConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public abstract class RetryConfig {

/**
* Returns the simple type names of the failures to retry on, matched exactly with no subclass
* match. When unset, any failure is retried; an empty list retries none.
* match. When unset, any failure is retried; an empty list retries none. A {@link
* WorkflowConfigurationException} is never retried, even when listed, because a retry fails the
* same way.
*/
public abstract Optional<ImmutableList<String>> exceptions();

Expand All @@ -92,7 +94,7 @@ public static Builder builder() {
* from 1, has an attempt left and failed with a type this config retries.
*/
boolean shouldRetry(Exception failure, int attempt) {
if (attempt >= maxAttempts()) {
if (attempt >= maxAttempts() || failure instanceof WorkflowConfigurationException) {
return false;
}
return exceptions().map(names -> names.contains(errorTypeName(failure))).orElse(true);
Expand Down
12 changes: 12 additions & 0 deletions core/src/test/java/com/google/adk/workflow/RetryConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ public void shouldRetry_withAnEmptyExceptionsList_retriesNothing() {
assertThat(config.shouldRetry(new IllegalStateException(), /* attempt= */ 1)).isFalse();
}

@Test
public void shouldRetry_neverRetriesAWorkflowConfigurationException() {
Exception failure = new WorkflowConfigurationException("misconfigured");
RetryConfig listingIt =
RetryConfig.builder()
.exceptions(ImmutableList.of("WorkflowConfigurationException"))
.build();

assertThat(RetryConfig.builder().build().shouldRetry(failure, /* attempt= */ 1)).isFalse();
assertThat(listingIt.shouldRetry(failure, /* attempt= */ 1)).isFalse();
}

@Test
public void delayFor_withoutJitter_growsByTheBackoffFactorUntilMaxDelay() {
RetryConfig config =
Expand Down
18 changes: 18 additions & 0 deletions core/src/test/java/com/google/adk/workflow/WorkflowRetryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.adk.workflow;

import static com.google.adk.testing.WorkflowTestNodes.dispatcher;
import static com.google.adk.testing.WorkflowTestNodes.emitter;
import static com.google.adk.testing.WorkflowTestNodes.throwing;
import static com.google.adk.testing.WorkflowTestNodes.workflow;
import static com.google.adk.workflow.WorkflowResumeTesting.START;
Expand Down Expand Up @@ -383,6 +384,23 @@ public void anOuterTimeout_isNeitherBlamedOnNorRetriedByTheNodeItCancels() {
assertThat(errorPaths(turn.events())).containsExactly("wf@1");
}

@Test
public void aWorkflowWithTwoTerminalOutputs_failsWithoutARetry() {
Workflow root =
Workflow.builder()
.name("wf")
.edges(
new Edge(START, emitter("first", "first_out")),
new Edge(START, emitter("second", "second_out")))
.config(retrying(3))
.build();

FailedTurn turn = new WorkflowSession(root).failingTurn(go());

assertThat(turn.error()).isInstanceOf(WorkflowConfigurationException.class);
assertThat(errorPaths(turn.events())).containsExactly("wf@1");
}

@Test
public void aWorkflowRetriedAfterANodeFailed_replaysTheNodesThatFinished() {
CountingNode first = new CountingNode("first", (ctx, run) -> Flowable.just("first_out"));
Expand Down
Loading