Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failsafe 3 #1416

Merged
merged 28 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1716824
failsafe 3 - update version, package names
Semernitskaya Feb 16, 2023
ba34428
failsafe 3 - fix package names
Semernitskaya Feb 16, 2023
ff22533
failsafe 3 - replace DelayFunction with ContextualSupplier
Semernitskaya Feb 16, 2023
dfb5edb
failsafe 3 - fix ContextualSupplier usage
Semernitskaya Feb 17, 2023
209568c
failsafe 3 - not use AbstractExecution
Semernitskaya Feb 22, 2023
599f8cf
failsafe 3 - fix tests
Semernitskaya Feb 22, 2023
25aff4e
failsafe 3 - fix tests
Semernitskaya Feb 22, 2023
6bf111c
failsafe 3 - fix tests
Semernitskaya Feb 22, 2023
3b87deb
failsafe 3 - fix other modules
Semernitskaya Feb 26, 2023
706e31d
failsafe 3 - fix autoconfigure module
Semernitskaya Feb 26, 2023
d9be48c
failsafe 3 - fix TODOs
Semernitskaya Feb 26, 2023
3d52cb7
failsafe 3 - fix test coverage
Semernitskaya Feb 26, 2023
ec22ef5
failsafe 3 - fix test coverage
Semernitskaya Feb 27, 2023
7d8bc84
failsafe 3 - fix TODOs
Semernitskaya Feb 28, 2023
a6fc49f
failsafe 3 - fix build
Semernitskaya Feb 28, 2023
46a1299
failsafe 3 - fix build
Semernitskaya Feb 28, 2023
dca2189
Merge branch 'main' into failsafe_3
fatroom Feb 28, 2023
2a577f8
failsafe 3 - minor fixes
Semernitskaya Feb 28, 2023
d533d1d
failsafe 3 - fix docs
Semernitskaya Feb 28, 2023
2da55e6
failsafe 3 - fix docs
Semernitskaya Feb 28, 2023
7ff6773
failsafe 3 - fix docs
Semernitskaya Feb 28, 2023
76aa240
Update MIGRATION.md
Semernitskaya Mar 8, 2023
2bb5fe3
failsafe 3 - fixes after CR
Semernitskaya Mar 8, 2023
0f506a2
failsafe 3 - fixes after CR
Semernitskaya Mar 8, 2023
d5f54e6
failsafe 3 - fixes after CR
Semernitskaya Mar 8, 2023
f9c8706
failsafe 3 - fix build
Semernitskaya Mar 8, 2023
93bc51a
failsafe 3 - fixes after CR
Semernitskaya Mar 9, 2023
43102e4
failsafe 3 - fixes after CR
Semernitskaya Mar 9, 2023
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
17 changes: 17 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Riptide 4.0 Migration Guide

## Failsafe

**Failsafe version was upgrade from 2.4.3 to 3.3.0**

There are many breaking changes between Failsafe version 2.4.3 and version 3.3.0,
see [Failsafe CHANGELOG](https://github.com/failsafe-lib/failsafe/blob/master/CHANGELOG.md#330) for all details.
Here are some of the breaking changes that can affect `riptide-failsafe` users:

- The maven group id for Failsafe has changed to `dev.failsafe`
- All files have been moved to the `dev.failsafe` package
- `Scheduler`, `DefaultScheduledFuture` and `PolicyExecutor` were moved to the spi package
- All policies now use a builder API instead of constructors
- `DelayFunction` interface has been removed, `ContextualSupplier` should be used instead since it provides access to the same information
- `CircuitBreakerBuilder` `onOpen`, `onClose`, and `onHalfOpen` methods now accept an `EventListener<CircuitBreakerStateChangedEvent>` argument

# Riptide 3.0 Migration Guide

## Before You Start
Expand Down
2 changes: 2 additions & 0 deletions cve-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
<cve>CVE-2021-4277</cve>
<cve>CVE-2022-3064</cve>
<cve>CVE-2021-4235</cve>
<cve>CVE-2022-45688</cve>
<cve>CVE-2020-8908</cve>
</suppress>

<!-- https://github.com/jeremylong/DependencyCheck/issues/1921 -->
Expand Down
12 changes: 7 additions & 5 deletions docs/resilience.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ riptide.clients:
The [riptide-faults](../riptide-faults) module provides a set `TransientFaults` predicates that detects transient faults:

```java
Http.builder()
.plugin(new FailsafePlugin()
.withPolicy(new RetryRequestPolicy(
new RetryPolicy().handleIf(transientSocketFaults()))
))
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(new RetryRequestPolicy(
RetryPolicy.<ClientHttpResponse>builder()
.handleIf(CheckedPredicateConverter.toCheckedPredicate(transientSocketFaults()))
.build())
));
```

```yaml
Expand Down
45 changes: 25 additions & 20 deletions riptide-failsafe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ and a circuit breaker to every remote call.
## Example

```java
Http.builder()
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(circuitBreaker)
.withPolicy(new RetryRequestPolicy(retryPolicy)))
Expand Down Expand Up @@ -45,21 +45,23 @@ Add the following dependency to your project:
The failsafe plugin will not perform retries nor apply circuit breakers unless they were explicitly configured:

```java
Http.builder()
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(
new RetryRequestPolicy(
new RetryPolicy<ClientHttpResponse>()
.withDelay(Duration.ofMillis(25))
.withDelay(new RetryAfterDelayFunction(clock))
.withMaxRetries(4))
RetryPolicy.<ClientHttpResponse>builder()
.withDelay(Duration.ofMillis(25))
.withDelayFn(new RetryAfterDelayFunction(clock))
.withMaxRetries(4)
.build())
.withListener(myRetryListener))
.withPolicy(
new CircuitBreaker<ClientHttpResponse>()
CircuitBreaker.<ClientHttpResponse>builder()
.withFailureThreshold(3, 10)
.withSuccessThreshold(5)
.withDelay(Duration.ofMinutes(1)))
.build();
.withDelay(Duration.ofMinutes(1))
.build()))
.build();
```

Please visit the [Failsafe readme](https://github.com/jhalterman/failsafe#readme) in order to see possible configurations.
Expand All @@ -70,9 +72,10 @@ Please visit the [Failsafe readme](https://github.com/jhalterman/failsafe#readme
You'll need to register `RetryException` in order for the `retry()` route to work:

```java
new RetryPolicy<ClientHttpResponse>()
RetryPolicy.<ClientHttpResponse>builder()
.handle(SocketTimeoutException.class)
.handle(RetryException.class);
.handle(RetryException.class)
.build();
```

Failsafe supports dynamically computed delays using a custom function.
Expand All @@ -82,14 +85,15 @@ Riptide: Failsafe offers implementations that understand:
- [`X-RateLimit-Reset` (RESTful API Guidelines)](https://opensource.zalando.com/restful-api-guidelines/#153)

```java
Http.builder()
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(new RetryPolicy<ClientHttpResponse>()
.withDelay(composite(
.withPolicy(RetryPolicy.<ClientHttpResponse>builder()
.withDelayFn(new CompositeDelayFunction<>(Arrays.asList(
new RetryAfterDelayFunction(clock),
new RateLimitResetDelayFunction(clock)
))
.withMaxDuration(Duration.ofSeconds(5))))
)))
.withMaxDuration(Duration.ofSeconds(5))
.build()))
.build();
```

Expand All @@ -104,7 +108,7 @@ Failsafe and Spring Boot.
The `BackupRequest` policy implements the [*backup request*][abstract] pattern, also known as [*hedged requests*][article]:

```java
Http.builder()
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(new BackupRequest(1, SECONDS)))
.build();
Expand Down Expand Up @@ -165,9 +169,10 @@ http.post("/subscriptions/{id}/cursors", subscriptionId)
In case those options are insufficient you may specify your own method detector:

```java
Http.builder()
.plugin(new FailsafePlugin(ImmutableList.of(retryPolicy))
.withIdempontentMethodDetector(new CustomIdempotentMethodDetector()))
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(retryPolicy)
.withDecorator(new CustomIdempotentMethodDetector()))
.build();
```

Expand Down
4 changes: 2 additions & 2 deletions riptide-failsafe/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<description>Client side response routing</description>

<properties>
<failsafe.version>2.4.3</failsafe.version>
<failsafe.version>3.3.0</failsafe.version>
</properties>

<dependencies>
Expand All @@ -31,7 +31,7 @@
<artifactId>riptide-idempotency</artifactId>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<groupId>dev.failsafe</groupId>
<artifactId>failsafe</artifactId>
<version>${failsafe.version}</version>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
package org.zalando.riptide.failsafe;

import lombok.AllArgsConstructor;
import dev.failsafe.Policy;
import dev.failsafe.PolicyConfig;
import dev.failsafe.spi.PolicyExecutor;
import lombok.Getter;
import net.jodah.failsafe.AbstractExecution;
import net.jodah.failsafe.Policy;
import net.jodah.failsafe.PolicyExecutor;
import org.apiguardian.api.API;

import java.util.concurrent.TimeUnit;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

@API(status = EXPERIMENTAL)
@AllArgsConstructor
@Getter
public final class BackupRequest<R> implements Policy<R> {

private final long delay;
private final TimeUnit unit;
private final PolicyConfig<R> config = new PolicyConfig<R>() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added for Policy<R> interface compatibility

};

public BackupRequest(long delay, TimeUnit unit) {
this.delay = delay;
this.unit = unit;
}

@Override
@SuppressWarnings("unchecked")
public PolicyExecutor<Policy<R>> toExecutor(
final AbstractExecution execution) {
return (PolicyExecutor<Policy<R>>) create(execution);
public PolicyExecutor<R> toExecutor(int policyIndex) {
return create(policyIndex);
}

private PolicyExecutor<? extends Policy<R>> create(
final AbstractExecution execution) {
return new BackupRequestExecutor<R>(this, execution);
private PolicyExecutor<R> create(int policyIndex) {
return new BackupRequestExecutor<>(this, policyIndex);
}

}
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
package org.zalando.riptide.failsafe;

import net.jodah.failsafe.AbstractExecution;
import net.jodah.failsafe.ExecutionResult;
import net.jodah.failsafe.FailsafeFuture;
import net.jodah.failsafe.PolicyExecutor;
import net.jodah.failsafe.util.concurrent.Scheduler;
import dev.failsafe.spi.AsyncExecutionInternal;
import dev.failsafe.spi.ExecutionResult;
import dev.failsafe.spi.FailsafeFuture;
import dev.failsafe.spi.PolicyExecutor;
import dev.failsafe.spi.Scheduler;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.function.Function;

import static org.zalando.riptide.CompletableFutures.forwardTo;

final class BackupRequestExecutor<R> extends PolicyExecutor<BackupRequest<R>> {
final class BackupRequestExecutor<R> extends PolicyExecutor<R> {

BackupRequestExecutor(final BackupRequest<R> policy, final AbstractExecution execution) {
super(policy, execution);
private final BackupRequest<R> policy;

BackupRequestExecutor(final BackupRequest<R> policy, int policyIndex) {
super(policy, policyIndex);
this.policy = policy;
}

@Override
protected Supplier<CompletableFuture<ExecutionResult>> supplyAsync(
final Supplier<CompletableFuture<ExecutionResult>> supplier,
public Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult<R>>> applyAsync(
Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult<R>>> innerFn,
final Scheduler scheduler,
final FailsafeFuture<Object> future) {
final FailsafeFuture<R> future) {

return () -> {
final CompletableFuture<ExecutionResult> original = supplier.get();
final CompletableFuture<ExecutionResult> backup = new CompletableFuture<>();
return (asyncExecutionInternal) -> {
final CompletableFuture<ExecutionResult<R>> original = innerFn.apply(asyncExecutionInternal);
final CompletableFuture<ExecutionResult<R>> backup = new CompletableFuture<>();

final Future<?> scheduledBackup = delay(scheduler, backup(supplier, backup));
final Future<?> scheduledBackup = delay(scheduler, backup(innerFn, asyncExecutionInternal, backup));

original.whenComplete(cancel(scheduledBackup));
backup.whenComplete(cancel(original));
Expand All @@ -41,11 +44,12 @@ protected Supplier<CompletableFuture<ExecutionResult>> supplyAsync(
};
}

private Callable<CompletableFuture<ExecutionResult>> backup(
final Supplier<CompletableFuture<ExecutionResult>> supplier,
final CompletableFuture<ExecutionResult> target) {
private Callable<CompletableFuture<ExecutionResult<R>>> backup(
final Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult<R>>> innerFn,
final AsyncExecutionInternal<R> asyncExecutionInternal,
final CompletableFuture<ExecutionResult<R>> target) {

return () -> supplier.get().whenComplete(forwardTo(target));
return () -> innerFn.apply(asyncExecutionInternal).whenComplete(forwardTo(target));
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.zalando.riptide.failsafe;

import dev.failsafe.function.CheckedPredicate;

import java.util.function.Predicate;

public class CheckedPredicateConverter {

private CheckedPredicateConverter() {
}

public static <T> CheckedPredicate<T> toCheckedPredicate(Predicate<T> predicate) {
return predicate::test;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.zalando.riptide.failsafe;

import dev.failsafe.function.ContextualSupplier;
import lombok.AllArgsConstructor;
import net.jodah.failsafe.ExecutionContext;
import net.jodah.failsafe.function.DelayFunction;
import dev.failsafe.ExecutionContext;
import org.apiguardian.api.API;

import java.time.Duration;
Expand All @@ -14,23 +14,28 @@

@API(status = EXPERIMENTAL)
@AllArgsConstructor
public final class CompositeDelayFunction<R, X extends Throwable>
implements DelayFunction<R, X> {
public final class CompositeDelayFunction<R> implements ContextualSupplier<R, Duration> {

private final Collection<DelayFunction<R, X>> functions;
private final Collection<ContextualSupplier<R, Duration>> functions;

@Override
public Duration computeDelay(final R result, final X failure, final ExecutionContext context) {
public Duration get(final ExecutionContext<R> context) throws Throwable {
return functions.stream()
.map(function -> function.computeDelay(result, failure, context))
.map(function -> {
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the method signature changed to throw instances of Throwable, why do we need to wrap this into a RuntimeException?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map method can't accept function that throws checked exception

return function.get(context);
} catch (Throwable e) {
throw new RuntimeException(e);
}
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

@SafeVarargs
public static <R, X extends Throwable> DelayFunction<R, X> composite(
final DelayFunction<R, X>... functions) {
public static <R, X extends Throwable> ContextualSupplier<R, Duration> composite(
final ContextualSupplier<R, Duration>... functions) {
return new CompositeDelayFunction<>(Arrays.asList(functions));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.zalando.riptide.failsafe;

import lombok.AllArgsConstructor;
import net.jodah.failsafe.event.ExecutionAttemptedEvent;
import dev.failsafe.event.ExecutionAttemptedEvent;
import org.apiguardian.api.API;
import org.springframework.http.client.ClientHttpResponse;
import org.zalando.riptide.RequestArguments;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.zalando.riptide.failsafe;

import lombok.AllArgsConstructor;
import net.jodah.failsafe.function.ContextualSupplier;
import dev.failsafe.function.ContextualSupplier;

import java.util.Collection;

Expand All @@ -11,8 +11,8 @@ final class CompositeTaskDecorator implements TaskDecorator {
private final Collection<TaskDecorator> decorators;

@Override
public <T> ContextualSupplier<T> decorate(final ContextualSupplier<T> supplier) {
ContextualSupplier<T> result = supplier;
public <T, R> ContextualSupplier<T, R> decorate(final ContextualSupplier<T, R> supplier) {
ContextualSupplier<T, R> result = supplier;
for (final TaskDecorator decorator : decorators) {
result = decorator.decorate(result);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.zalando.riptide.failsafe;

import lombok.AllArgsConstructor;
import net.jodah.failsafe.Policy;
import dev.failsafe.Policy;
import org.springframework.http.client.ClientHttpResponse;
import org.zalando.riptide.RequestArguments;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.zalando.riptide.failsafe;

import lombok.AllArgsConstructor;
import net.jodah.failsafe.Policy;
import dev.failsafe.Policy;
import org.springframework.http.client.ClientHttpResponse;
import org.zalando.riptide.RequestArguments;

Expand Down
Loading