Skip to content
Open
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
14 changes: 10 additions & 4 deletions docs/content/en/docs/documentation/error-handling-retries.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ JOSDK automatically schedules retries whenever your `Reconciler` throws an excep
The default retry implementation covers most typical use cases with exponential backoff:

```java
GenericRetry.defaultLimitedExponentialRetry()
.setInitialInterval(5000) // Start with 5-second delay
.setIntervalMultiplier(1.5D) // Increase delay by 1.5x each retry
.setMaxAttempts(5); // Maximum 5 attempts
GenericRetry.builder()
.withInitialInterval(Duration.ofSeconds(5)) // Start with 5-second delay
.withIntervalMultiplier(1.5D) // Increase delay by 1.5x each retry
.withMaxAttempts(5) // Maximum 5 attempts
.withMaxInterval(Duration.ofMinutes(1)) // Cap the delay at 1 minute
.build();
```

Prefer the builder for creating `GenericRetry` instances, and use `toBuilder()` to derive a variant
of an existing configuration. The `set*` methods are deprecated and will be removed in a future
major version, at which point `GenericRetry` becomes immutable.

### Configuration Options

**Using the `@GradualRetry` annotation:**
Expand Down
2 changes: 1 addition & 1 deletion docs/content/en/docs/documentation/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ LocallyRunOperatorExtension.builder()
// Override controller configuration
.withReconciler(new MyReconciler(), config -> config
.settingNamespace("specific-namespace")
.withRetry(new GenericRetry().withLinearRetry()))
.withRetry(GenericRetry.builder().withLinearRetry().build()))
// Pre-deploy infrastructure resources before operator starts
.withInfrastructure(configMap, secret)
// Register CRDs for resources not managed by a reconciler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,42 @@ public class GenericRetry implements Retry, AnnotationConfigurable<GradualRetry>
@Deprecated(forRemoval = true)
public static final Retry DEFAULT = new GenericRetry();

/**
* Creates an instance with the default values. Prefer {@link #builder()} or {@link
* #defaultLimitedExponentialRetry()}; this constructor is kept public so that the class can be
* instantiated and configured from the {@link GradualRetry} annotation.
*/
public GenericRetry() {}

private GenericRetry(Builder builder) {
this.maxAttempts = builder.maxAttempts;
this.initialInterval = builder.initialInterval;
this.intervalMultiplier = builder.intervalMultiplier;
this.maxInterval = builder.maxInterval;
}

public static Builder builder() {
return new Builder();
}

public static GenericRetry defaultLimitedExponentialRetry() {
return new GenericRetry();
return builder().build();
}

public static GenericRetry noRetry() {
return new GenericRetry().setMaxAttempts(0);
return builder().withMaxAttempts(0).build();
}

public static GenericRetry every10second10TimesRetry() {
return new GenericRetry().withLinearRetry().setMaxAttempts(10).setInitialInterval(10000);
return builder()
.withLinearRetry()
.withMaxAttempts(10)
.withInitialInterval(Duration.ofSeconds(10))
.build();
}

public Builder toBuilder() {
return new Builder(this);
}

@Override
Expand All @@ -53,6 +79,10 @@ public int getMaxAttempts() {
return maxAttempts;
}

/**
* @deprecated use {@link Builder#withMaxAttempts(int)} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry setMaxAttempts(int maxRetryAttempts) {
this.maxAttempts = maxRetryAttempts;
return this;
Expand All @@ -62,11 +92,19 @@ public long getInitialInterval() {
return initialInterval;
}

/**
* @deprecated use {@link Builder#withInitialInterval(Duration)} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry setInitialInterval(Duration initialInterval) {
setInitialInterval(initialInterval.toMillis());
this.initialInterval = initialInterval.toMillis();
return this;
}

/**
* @deprecated use {@link Builder#withInitialInterval(long)} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry setInitialInterval(long initialInterval) {
this.initialInterval = initialInterval;
return this;
Expand All @@ -76,6 +114,10 @@ public double getIntervalMultiplier() {
return intervalMultiplier;
}

/**
* @deprecated use {@link Builder#withIntervalMultiplier(double)} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry setIntervalMultiplier(double intervalMultiplier) {
this.intervalMultiplier = intervalMultiplier;
return this;
Expand All @@ -85,24 +127,46 @@ public long getMaxInterval() {
return maxInterval;
}

/**
* @deprecated use {@link Builder#withMaxInterval(long)} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry setMaxInterval(long maxInterval) {
this.maxInterval = maxInterval;
return this;
}

/**
* @deprecated use {@link Builder#withMaxInterval(Duration)} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry setMaxInterval(Duration maxInterval) {
return setMaxInterval(maxInterval.toMillis());
this.maxInterval = maxInterval.toMillis();
return this;
}

/**
* @deprecated use {@link Builder#withoutMaxInterval()} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry withoutMaxInterval() {
this.maxInterval = -1;
return this;
}

/**
* @deprecated use {@link Builder#withoutMaxAttempts()} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry withoutMaxAttempts() {
return this.setMaxAttempts(-1);
this.maxAttempts = -1;
return this;
}

/**
* @deprecated use {@link Builder#withLinearRetry()} instead
*/
@Deprecated(forRemoval = true)
public GenericRetry withLinearRetry() {
this.intervalMultiplier = 1;
return this;
Expand All @@ -118,4 +182,77 @@ public void initFrom(GradualRetry configuration) {
? GradualRetry.DEFAULT_MAX_INTERVAL
: configuration.maxInterval();
}

@Override
public String toString() {
return "GenericRetry{maxAttempts="
+ maxAttempts
+ ", initialInterval="
+ initialInterval
+ ", intervalMultiplier="
+ intervalMultiplier
+ ", maxInterval="
+ maxInterval
+ '}';
}

public static final class Builder {
private int maxAttempts = GradualRetry.DEFAULT_MAX_ATTEMPTS;
private long initialInterval = GradualRetry.DEFAULT_INITIAL_INTERVAL;
private double intervalMultiplier = GradualRetry.DEFAULT_MULTIPLIER;
private long maxInterval = GradualRetry.DEFAULT_MAX_INTERVAL;

private Builder() {}

private Builder(GenericRetry retry) {
this.maxAttempts = retry.maxAttempts;
this.initialInterval = retry.initialInterval;
this.intervalMultiplier = retry.intervalMultiplier;
this.maxInterval = retry.maxInterval;
}

public Builder withMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
return this;
}

public Builder withoutMaxAttempts() {
return withMaxAttempts(-1);
}

public Builder withInitialInterval(long initialIntervalMillis) {
this.initialInterval = initialIntervalMillis;
return this;
}

public Builder withInitialInterval(Duration initialInterval) {
return withInitialInterval(initialInterval.toMillis());
}

public Builder withIntervalMultiplier(double intervalMultiplier) {
this.intervalMultiplier = intervalMultiplier;
return this;
}

public Builder withLinearRetry() {
return withIntervalMultiplier(1);
}

public Builder withMaxInterval(long maxIntervalMillis) {
this.maxInterval = maxIntervalMillis;
return this;
}

public Builder withMaxInterval(Duration maxInterval) {
return withMaxInterval(maxInterval.toMillis());
}

public Builder withoutMaxInterval() {
return withMaxInterval(-1);
}

public GenericRetry build() {
return new GenericRetry(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -765,9 +765,7 @@ void processesAdditionalEventWhileInDeleteModeRetry() {
spy(
new EventProcessor(
controllerConfigTriggerAllEvent(
GenericRetry.defaultLimitedExponentialRetry()
.setInitialInterval(1000)
.setMaxAttempts(-1),
GenericRetry.builder().withInitialInterval(1000).withMaxAttempts(-1).build(),
rateLimiterMock),
reconciliationDispatcherMock,
eventSourceManagerMock,
Expand Down Expand Up @@ -796,10 +794,11 @@ void afterRetryExhaustedAdditionalEventTriggerReconciliationWhenDeleteEventPrese
spy(
new EventProcessor(
controllerConfigTriggerAllEvent(
GenericRetry.defaultLimitedExponentialRetry()
.setInitialInterval(100)
.setIntervalMultiplier(1)
.setMaxAttempts(1),
GenericRetry.builder()
.withInitialInterval(100)
.withIntervalMultiplier(1)
.withMaxAttempts(1)
.build(),
rateLimiterMock),
reconciliationDispatcherMock,
eventSourceManagerMock,
Expand Down Expand Up @@ -831,10 +830,11 @@ void rateLimitsDeleteEventInAllEventMode() {
spy(
new EventProcessor(
controllerConfigTriggerAllEvent(
GenericRetry.defaultLimitedExponentialRetry()
.setInitialInterval(100)
.setIntervalMultiplier(1)
.setMaxAttempts(1),
GenericRetry.builder()
.withInitialInterval(100)
.withIntervalMultiplier(1)
.withMaxAttempts(1)
.build(),
rateLimiterMock),
reconciliationDispatcherMock,
eventSourceManagerMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ void ifRetryLimitedToZeroMaxAttemptsErrorHandlerGetsCorrectLastAttempt() {
var configuration =
MockControllerConfiguration.forResource(
(Class<TestCustomResource>) testCustomResource.getClass());
when(configuration.getRetry()).thenReturn(new GenericRetry().setMaxAttempts(0));
when(configuration.getRetry()).thenReturn(GenericRetry.builder().withMaxAttempts(0).build());
reconciliationDispatcher =
init(testCustomResource, reconciler, configuration, customResourceFacade, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class GenericRetryExecutionTest {
@Test
void noNextDelayIfMaxAttemptLimitReached() {
RetryExecution retryExecution =
GenericRetry.defaultLimitedExponentialRetry().setMaxAttempts(3).initExecution();
GenericRetry.builder().withMaxAttempts(3).build().initExecution();
Optional<Long> res = callNextDelayNTimes(retryExecution, 2);
assertThat(res).isNotEmpty();

Expand All @@ -37,10 +37,11 @@ void noNextDelayIfMaxAttemptLimitReached() {
@Test
void canLimitMaxIntervalLength() {
RetryExecution retryExecution =
GenericRetry.defaultLimitedExponentialRetry()
.setInitialInterval(2000)
.setMaxInterval(4500)
.setIntervalMultiplier(2)
GenericRetry.builder()
.withInitialInterval(2000)
.withMaxInterval(4500)
.withIntervalMultiplier(2)
.build()
.initExecution();

Optional<Long> res = callNextDelayNTimes(retryExecution, 4);
Expand All @@ -56,7 +57,8 @@ void supportsNoRetry() {

@Test
void supportsIsLastExecution() {
GenericRetryExecution execution = new GenericRetry().setMaxAttempts(2).initExecution();
GenericRetryExecution execution =
GenericRetry.builder().withMaxAttempts(2).build().initExecution();
assertThat(execution.isLastAttempt()).isFalse();

execution.nextDelay();
Expand All @@ -83,7 +85,8 @@ void remainingDurationEmptyBeforeFirstNextDelay() {
@Test
void remainingDurationPresentAfterNextDelay() {
long interval = 60_000L;
RetryExecution retryExecution = new GenericRetry().setInitialInterval(interval).initExecution();
RetryExecution retryExecution =
GenericRetry.builder().withInitialInterval(interval).build().initExecution();

retryExecution.nextDelay();

Expand All @@ -94,7 +97,8 @@ void remainingDurationPresentAfterNextDelay() {

@Test
void remainingDurationEmptyAfterIntervalElapsed() throws InterruptedException {
RetryExecution retryExecution = new GenericRetry().setInitialInterval(20).initExecution();
RetryExecution retryExecution =
GenericRetry.builder().withInitialInterval(20).build().initExecution();

retryExecution.nextDelay();
Thread.sleep(60);
Expand All @@ -107,9 +111,10 @@ void remainingDurationReflectsUpdatedIntervalAfterSubsequentNextDelay() {
long initialInterval = 1000L;
double multiplier = 2.0;
RetryExecution retryExecution =
new GenericRetry()
.setInitialInterval(initialInterval)
.setIntervalMultiplier(multiplier)
GenericRetry.builder()
.withInitialInterval(initialInterval)
.withIntervalMultiplier(multiplier)
.build()
.initExecution();

// first two calls keep the initial interval (multiplier only kicks in after attempt 1)
Expand Down
Loading
Loading