Skip to content

Commit

Permalink
add support for configuration to @ApplyGuard
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed Jan 14, 2025
1 parent 6e69f87 commit b708691
Show file tree
Hide file tree
Showing 117 changed files with 3,088 additions and 1,067 deletions.
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/reference/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Or globally:

[source,properties]
----
smallrye.faulttolerance.global.retry.enabled=5
smallrye.faulttolerance.global.retry.enabled=false
# alternatively, a specification-defined property can be used
Retry/enabled=false
Expand Down
91 changes: 90 additions & 1 deletion doc/modules/ROOT/pages/reference/reusable.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,95 @@ In other words, in non-compatible mode, the `Guard` / `TypedGuard` thread offloa
See xref:reference/non-compat.adoc[] for more information about the non-compatible mode.
****

== Configuration

Even though the programmatic API of `Guard` and `TypedGuard` does not support configuration, the declarative API of `@ApplyGuard` does.

The configuration closely resembles xref:reference/config.adoc[the configuration of the declarative API], where instead of the `<classname>` or `<classname>/<methodname>`, you use the identifier that's used in the `@Identifier` qualifier and in the `@ApplyGuard` annotation.

CAUTION: When configuring the `Guard` or `TypedGuard` using the following configuration properties, it is recommended to never use the `[Typed]Guard` programmatically.
The reason is that configuration is applied on the first invocation, and if that invocation is programmatic, the configuration key is unknown.

For example, let's assume the following preconfigured `Guard` object:

[source,java]
----
@ApplicationScoped
public class PreconfiguredFaultTolerance {
@Produces
@Identifier("my-fault-tolerance")
public static final Guard GUARD = Guard.create()
.withRetry().maxRetries(2).done()
.withTimeout().done()
.build();
}
----

This `Guard` supports retry and timeout; any attempt to configure other strategies will be ignored.

To reconfigure the number of maximum retries, the following configuration properties may be used:

[source,properties]
----
smallrye.faulttolerance."my-fault-tolerance".retry.max-retries=5
# alternatively, a specification-defined property can be used
my-fault-tolerance/Retry/maxRetries=5
----

Global configuration that applies to all usages of retry is also respected by `@ApplyGuard`:

[source,properties]
----
smallrye.faulttolerance.global.retry.max-retries=5
# alternatively, a specification-defined property can be used
Retry/maxRetries=5
----

Note that the configuration keys follow the configuration of the declarative API, even if the methods in the programmatic API builders are named differently.
For example, to set the rate limit in the programmatic API, the `RateLimitBuilder` uses a method called `limit()`, while the corresponding annotation member of `@RateLimit` is named `value()`.
The configuration key in both cases is `rate-limit.value` (or `RateLimit/value`).

=== Disabling Fault Tolerance

Configuration properties for disabling individual strategies also work:

[source,properties]
----
smallrye.faulttolerance."my-fault-tolerance".retry.enabled=false
# alternatively, a specification-defined property can be used
my-fault-tolerance/Retry/enabled=false
----

Or globally:

[source,properties]
----
smallrye.faulttolerance.global.retry.enabled=false
# alternatively, a specification-defined property can be used
Retry/enabled=false
----

=== Supported Strategies

The following fault tolerance strategies, when defined on a `Guard` (or `TypedGuard`) and used through `@ApplyGuard`, may be configured:

* bulkhead (except of `BulkheadBuilder.enableSynchronousQueueing()`)
* circuit breaker
* rate limit
* retry, including exponential backoff and Fibonacci backoff
* timeout

In the programmatic API, the following strategies are not built declaratively, and so their configuration is ignored:

* fallback
* circuit breaker name (cannot be configured in the declarative API either)
* certain parts of retry: custom backoff, before retry action and retry predicates
* thread offload

== Metrics

Methods annotated `@ApplyGuard` gather metrics similarly to methods annotated with {microprofile-fault-tolerance} annotations.
Expand All @@ -89,7 +178,7 @@ That is, each method gets its own metrics, with the `method` tag being `<fully q
At the same time, state is still shared.
All methods annotated `@ApplyGuard` share the same bulkhead, circuit breaker and/or rate limit.

If the `Guard` or `TypedGuard` object used for `@ApplyGuard` is also used xref:reference/programmatic-api.adoc[programmatically], that usage is coalesced in metrics under the description as the `method` tag.
If the `Guard` or `TypedGuard` object used for `@ApplyGuard` is also used xref:reference/programmatic-api.adoc[programmatically], that usage is coalesced in metrics (where the `method` tag is set to the description).

== Differences to the Specification

Expand Down
76 changes: 76 additions & 0 deletions implementation/apiimpl/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-fault-tolerance-implementation-parent</artifactId>
<version>6.7.4-SNAPSHOT</version>
</parent>

<artifactId>smallrye-fault-tolerance-apiimpl</artifactId>

<name>SmallRye Fault Tolerance: Programmatic API Implementation</name>

<dependencies>
<dependency>
<groupId>org.eclipse.microprofile.fault-tolerance</groupId>
<artifactId>microprofile-fault-tolerance-api</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-fault-tolerance-api</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-fault-tolerance-core</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-fault-tolerance-autoconfig-core</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-fault-tolerance-autoconfig-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</path>
<path>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
</path>

<path>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-fault-tolerance-autoconfig-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import static io.smallrye.faulttolerance.core.util.Preconditions.checkNotNull;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import java.util.Collection;
import java.util.Set;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import io.smallrye.faulttolerance.core.metrics.MeteredOperation;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

// dependencies that may be accessed eagerly; these must be safe to use during static initialization
public interface BuilderEagerDependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import java.util.concurrent.ExecutorService;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import java.util.concurrent.Callable;
import java.util.function.Function;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import java.util.function.Consumer;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import static io.smallrye.faulttolerance.core.Invocation.invocation;
import static io.smallrye.faulttolerance.core.util.Durations.timeInMillis;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package io.smallrye.faulttolerance.core.apiimpl;
package io.smallrye.faulttolerance.apiimpl;

import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Predicate;

import io.smallrye.faulttolerance.core.FaultToleranceContext;
import io.smallrye.faulttolerance.core.FaultToleranceStrategy;
Expand All @@ -14,6 +15,12 @@
import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry;
import io.smallrye.faulttolerance.core.invocation.Invoker;
import io.smallrye.faulttolerance.core.invocation.StrategyInvoker;
import io.smallrye.faulttolerance.core.util.ExceptionDecision;
import io.smallrye.faulttolerance.core.util.PredicateBasedExceptionDecision;
import io.smallrye.faulttolerance.core.util.PredicateBasedResultDecision;
import io.smallrye.faulttolerance.core.util.ResultDecision;
import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision;
import io.smallrye.faulttolerance.core.util.SetOfThrowables;

final class GuardCommon {
private static final Class<?>[] NO_PARAMS = new Class<?>[0];
Expand Down Expand Up @@ -77,4 +84,35 @@ static <V, T> T guard(Callable<T> action, FaultToleranceStrategy<V> strategy, As
Invoker<Future<V>> fromFutureInvoker = new StrategyInvoker<>(asyncInvocation.arguments, strategy, ctx);
return asyncSupport.fromFuture(fromFutureInvoker);
}

// ---

static ResultDecision createResultDecision(Predicate<Object> whenResultPredicate) {
if (whenResultPredicate != null) {
// the builder API accepts a predicate that returns `true` when a result is considered failure,
// but `Retry` accepts a predicate that returns `true` when a result is considered success,
// hence the negation
return new PredicateBasedResultDecision(whenResultPredicate.negate());
}
return ResultDecision.ALWAYS_EXPECTED;
}

static ExceptionDecision createExceptionDecision(Class<? extends Throwable>[] consideredExpected,
Class<? extends Throwable>[] consideredFailure, Predicate<Throwable> whenExceptionPredicate) {
if (whenExceptionPredicate != null) {
// the builder API accepts a predicate that returns `true` when an exception is considered failure,
// but `PredicateBasedExceptionDecision` accepts a predicate that returns `true` when an exception
// is considered success -- hence the negation
return new PredicateBasedExceptionDecision(whenExceptionPredicate.negate());
}
return new SetBasedExceptionDecision(createSetOfThrowables(consideredFailure),
createSetOfThrowables(consideredExpected), true);
}

private static SetOfThrowables createSetOfThrowables(Class<? extends Throwable>[] throwableClasses) {
if (throwableClasses == null || throwableClasses.length == 0) {
return SetOfThrowables.EMPTY;
}
return SetOfThrowables.create(throwableClasses);
}
}
Loading

0 comments on commit b708691

Please sign in to comment.