Skip to content

Commit

Permalink
Merge pull request quarkusio#40012 from Ladicek/fault-tolerance-6.3.0
Browse files Browse the repository at this point in the history
Upgrade to SmallRye Fault Tolerance 6.3.0
gsmet authored Apr 11, 2024
2 parents 25a82a8 + e3c7965 commit 6b171d0
Showing 23 changed files with 252 additions and 13 deletions.
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@
<smallrye-metrics.version>4.0.0</smallrye-metrics.version>
<smallrye-open-api.version>3.10.0</smallrye-open-api.version>
<smallrye-graphql.version>2.8.2</smallrye-graphql.version>
<smallrye-fault-tolerance.version>6.2.6</smallrye-fault-tolerance.version>
<smallrye-fault-tolerance.version>6.3.0</smallrye-fault-tolerance.version>
<smallrye-jwt.version>4.5.0</smallrye-jwt.version>
<smallrye-context-propagation.version>2.1.0</smallrye-context-propagation.version>
<smallrye-reactive-streams-operators.version>1.0.13</smallrye-reactive-streams-operators.version>
4 changes: 2 additions & 2 deletions docs/src/main/asciidoc/smallrye-fault-tolerance.adoc
Original file line number Diff line number Diff line change
@@ -503,7 +503,7 @@ implementation("io.quarkus:quarkus-smallrye-fault-tolerance")
== Additional resources

SmallRye Fault Tolerance has more features than shown here.
Please check the link:https://smallrye.io/docs/smallrye-fault-tolerance/6.2.6/index.html[SmallRye Fault Tolerance documentation] to learn about them.
Please check the link:https://smallrye.io/docs/smallrye-fault-tolerance/6.3.0/index.html[SmallRye Fault Tolerance documentation] to learn about them.

In Quarkus, you can use the SmallRye Fault Tolerance optional features out of the box.

@@ -535,7 +535,7 @@ smallrye.faulttolerance.mp-compatibility=true
----
====

The link:https://smallrye.io/docs/smallrye-fault-tolerance/6.2.6/reference/programmatic-api.html[programmatic API] is present, including Mutiny support, and integrated with the declarative, annotation-based API.
The link:https://smallrye.io/docs/smallrye-fault-tolerance/6.3.0/reference/programmatic-api.html[programmatic API] is present, including Mutiny support, and integrated with the declarative, annotation-based API.
You can use the `FaultTolerance` and `MutinyFaultTolerance` APIs out of the box.

Support for Kotlin is present (assuming you use the Quarkus extension for Kotlin), so you can guard your `suspend` functions with fault tolerance annotations.
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
import io.smallrye.faulttolerance.api.ExponentialBackoff;
import io.smallrye.faulttolerance.api.FibonacciBackoff;
import io.smallrye.faulttolerance.api.RateLimit;
import io.smallrye.faulttolerance.api.RetryWhen;

public final class DotNames {
public static final DotName OBJECT = DotName.createSimple(Object.class);
@@ -52,8 +53,9 @@ public final class DotNames {
public static final DotName FIBONACCI_BACKOFF = DotName.createSimple(FibonacciBackoff.class);
public static final DotName CUSTOM_BACKOFF = DotName.createSimple(CustomBackoff.class);
public static final DotName CUSTOM_BACKOFF_STRATEGY = DotName.createSimple(CustomBackoffStrategy.class);
public static final DotName RETRY_WHEN = DotName.createSimple(RetryWhen.class);

// certain SmallRye annotations (@CircuitBreakerName, @[Non]Blocking, @*Backoff) alone do _not_ trigger
// certain SmallRye annotations (@CircuitBreakerName, @[Non]Blocking, @*Backoff, @RetryWhen) alone do _not_ trigger
// the fault tolerance interceptor, only in combination with other fault tolerance annotations
public static final Set<DotName> FT_ANNOTATIONS = Set.of(APPLY_FAULT_TOLERANCE, ASYNCHRONOUS,
ASYNCHRONOUS_NON_BLOCKING, BULKHEAD, CIRCUIT_BREAKER, FALLBACK, RATE_LIMIT, RETRY, TIMEOUT);
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@
import io.smallrye.faulttolerance.api.ExponentialBackoff;
import io.smallrye.faulttolerance.api.FibonacciBackoff;
import io.smallrye.faulttolerance.api.RateLimit;
import io.smallrye.faulttolerance.api.RetryWhen;
import io.smallrye.faulttolerance.autoconfig.FaultToleranceMethod;
import io.smallrye.faulttolerance.autoconfig.MethodDescriptor;

@@ -80,7 +81,7 @@ boolean hasFTAnnotations(ClassInfo clazz) {
void forEachMethod(ClassInfo clazz, Consumer<MethodInfo> action) {
for (MethodInfo method : clazz.methods()) {
if (method.name().startsWith("<")) {
// constructors and static inititalizers can't be intercepted
// constructors and static initializers can't be intercepted
continue;
}
if (method.isSynthetic()) {
@@ -134,6 +135,7 @@ FaultToleranceMethod createFaultToleranceMethod(ClassInfo beanClass, MethodInfo
result.customBackoff = getAnnotation(CustomBackoff.class, method, beanClass, annotationsPresentDirectly);
result.exponentialBackoff = getAnnotation(ExponentialBackoff.class, method, beanClass, annotationsPresentDirectly);
result.fibonacciBackoff = getAnnotation(FibonacciBackoff.class, method, beanClass, annotationsPresentDirectly);
result.retryWhen = getAnnotation(RetryWhen.class, method, beanClass, annotationsPresentDirectly);

result.annotationsPresentDirectly = annotationsPresentDirectly;

Original file line number Diff line number Diff line change
@@ -153,6 +153,15 @@ public void build(BuildProducer<AnnotationsTransformerBuildItem> annotationsTran
for (ClassInfo strategy : index.getAllKnownImplementors(DotNames.CUSTOM_BACKOFF_STRATEGY)) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder(strategy.name().toString()).methods().build());
}
// Add reflective access to retry predicates
for (AnnotationInstance annotation : index.getAnnotations(DotNames.RETRY_WHEN)) {
for (String memberName : List.of("result", "exception")) {
AnnotationValue member = annotation.value(memberName);
if (member != null) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder(member.asClass().name().toString()).build());
}
}
}

for (DotName annotation : DotNames.FT_ANNOTATIONS) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder(annotation.toString()).methods().build());
@@ -350,8 +359,6 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder,
}
}

// since annotation transformations are applied lazily, we can't know
// all transformed `@*Backoff`s and have to rely on Jandex here
for (DotName backoffAnnotation : DotNames.BACKOFF_ANNOTATIONS) {
for (AnnotationInstance it : index.getAnnotations(backoffAnnotation)) {
if (!annotationStore.hasAnnotation(it.target(), DotNames.RETRY)) {
@@ -360,6 +367,11 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder,
}
}
}
for (AnnotationInstance it : index.getAnnotations(DotNames.RETRY_WHEN)) {
if (!annotationStore.hasAnnotation(it.target(), DotNames.RETRY)) {
exceptions.add(new DefinitionException("@RetryWhen present on '" + it.target() + "', but @Retry is missing"));
}
}

if (!exceptions.isEmpty()) {
errors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(exceptions));
Original file line number Diff line number Diff line change
@@ -85,6 +85,7 @@ export class QwcFaultToleranceMethods extends LitElement {
${guardedMethod.ExponentialBackoff ? this._renderExponentialBackoff(guardedMethod.ExponentialBackoff) : html``}
${guardedMethod.FibonacciBackoff ? this._renderFibonacciBackoff(guardedMethod.FibonacciBackoff) : html``}
${guardedMethod.CustomBackoff ? this._renderCustomBackoff(guardedMethod.CustomBackoff) : html``}
${guardedMethod.RetryWhen ? this._renderRetryWhen(guardedMethod.RetryWhen) : html``}
${guardedMethod.Timeout ? this._renderTimeout(guardedMethod.Timeout) : html``}
</vaadin-vertical-layout>
`;
@@ -179,6 +180,15 @@ export class QwcFaultToleranceMethods extends LitElement {
`;
}

_renderRetryWhen(retryWhen) {
return html`
<span>
&rarrhk;
@RetryWhen(result = ${retryWhen.result}, exception = ${retryWhen.exception})
</span>
`;
}

_renderTimeout(timeout) {
return html`
<span>@Timeout(${timeout.value} ${timeout.valueUnit})</span>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;

import static io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.failedFuture;

import java.util.List;
import java.util.concurrent.CompletionStage;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;

import static io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.failedFuture;

import java.util.List;
import java.util.concurrent.CompletionStage;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;

import static io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.failedFuture;

import java.util.List;
import java.util.concurrent.CompletionStage;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.smallrye.faulttolerance.test.asynchronous.noncompat;

import static io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.failedFuture;

import java.util.List;
import java.util.concurrent.CompletionStage;
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.inject.Inject;

@@ -26,6 +27,7 @@ public void test() {
assertEquals(3, rateLimit.hello());
assertEquals(4, rateLimit.hello());
assertEquals(5, rateLimit.hello());
assertThrows(RateLimitException.class, () -> rateLimit.hello());
RateLimitException rateLimitException = assertThrows(RateLimitException.class, () -> rateLimit.hello());
assertTrue(rateLimitException.getRetryAfterMillis() > 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import java.util.function.Predicate;

public class IsIllegalArgumentException implements Predicate<Throwable> {
@Override
public boolean test(Throwable throwable) {
return throwable instanceof IllegalArgumentException;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import java.util.function.Predicate;

public class IsNull implements Predicate<Object> {
@Override
public boolean test(Object o) {
return o == null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import jakarta.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.faulttolerance.Retry;

import io.smallrye.faulttolerance.api.RetryWhen;

@ApplicationScoped
public class RetryOnAndRetryWhenExceptionService {
@Retry(retryOn = IllegalStateException.class)
@RetryWhen(exception = IsIllegalArgumentException.class)
public void hello() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.enterprise.inject.spi.DeploymentException;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class RetryOnAndRetryWhenExceptionTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(RetryOnAndRetryWhenExceptionService.class, IsIllegalArgumentException.class))
.assertException(e -> {
assertEquals(DeploymentException.class, e.getClass());
assertTrue(e.getMessage().contains("Invalid @RetryWhen.exception"));
assertTrue(e.getMessage().contains("must not be combined with @Retry.retryOn"));
});

@Test
public void test() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import jakarta.enterprise.context.Dependent;

import org.eclipse.microprofile.faulttolerance.Retry;

import io.smallrye.faulttolerance.api.RetryWhen;

@Dependent
@Retry
public class RetryOnClassRetryWhenOnMethodService {
@RetryWhen
public void hello() {
throw new IllegalArgumentException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.enterprise.inject.spi.DefinitionException;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class RetryOnClassRetryWhenOnMethodTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(RetryOnClassRetryWhenOnMethodService.class))
.assertException(e -> {
assertEquals(DefinitionException.class, e.getClass());
assertTrue(e.getMessage().contains("@RetryWhen present"));
assertTrue(e.getMessage().contains("@Retry is missing"));
});

@Test
public void test() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import jakarta.enterprise.context.Dependent;

import org.eclipse.microprofile.faulttolerance.Retry;

import io.smallrye.faulttolerance.api.RetryWhen;

@Dependent
@RetryWhen
public class RetryOnMethodRetryWhenOnClassService {
@Retry
public void hello() {
throw new IllegalArgumentException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.enterprise.inject.spi.DefinitionException;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class RetryOnMethodRetryWhenOnClassTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(RetryOnMethodRetryWhenOnClassService.class))
.assertException(e -> {
assertEquals(DefinitionException.class, e.getClass());
assertTrue(e.getMessage().contains("@RetryWhen present"));
assertTrue(e.getMessage().contains("@Retry is missing"));
});

@Test
public void test() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import java.util.concurrent.atomic.AtomicInteger;

import jakarta.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.faulttolerance.Retry;

import io.smallrye.faulttolerance.api.RetryWhen;

@ApplicationScoped
public class RetryWhenResultAndExceptionService {
private final AtomicInteger attempts = new AtomicInteger();

@Retry
@RetryWhen(result = IsNull.class, exception = IsIllegalArgumentException.class)
public String hello() {
int current = attempts.incrementAndGet();
if (current == 1) {
return null;
} else if (current == 2) {
throw new IllegalArgumentException();
} else {
return "hello";
}
}

public AtomicInteger getAttempts() {
return attempts;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.smallrye.faulttolerance.test.retry.when;

import static org.assertj.core.api.Assertions.assertThat;

import jakarta.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class RetryWhenResultAndExceptionTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(RetryWhenResultAndExceptionService.class, IsNull.class, IsIllegalArgumentException.class));

@Inject
RetryWhenResultAndExceptionService service;

@Test
public void test() {
assertThat(service.hello()).isEqualTo("hello");
assertThat(service.getAttempts()).hasValue(3);
}
}
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ public void createFaultToleranceOperation(List<FaultToleranceMethod> ftMethods)
if (error instanceof DeploymentException) {
throw (DeploymentException) error;
} else {
throw new DeploymentException(allExceptions.get(0));
throw new DeploymentException(error);
}
} else {
StringBuilder message = new StringBuilder("Found " + allExceptions.size() + " deployment problems: ");
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
import io.smallrye.faulttolerance.api.ExponentialBackoff;
import io.smallrye.faulttolerance.api.FibonacciBackoff;
import io.smallrye.faulttolerance.api.RateLimit;
import io.smallrye.faulttolerance.api.RetryWhen;
import io.smallrye.faulttolerance.config.FaultToleranceOperation;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
@@ -127,6 +128,11 @@ private JsonObject convert(FaultToleranceOperation operation) {
result.put(CustomBackoff.class.getSimpleName(), new JsonObject()
.put("value", operation.getCustomBackoff().value().getName()));
}
if (operation.hasRetryWhen()) {
result.put(RetryWhen.class.getSimpleName(), new JsonObject()
.put("result", operation.getRetryWhen().result().getName())
.put("exception", operation.getRetryWhen().exception().getName()));
}
if (operation.hasTimeout()) {
result.put(Timeout.class.getSimpleName(), new JsonObject()
.put("value", operation.getTimeout().value())

0 comments on commit 6b171d0

Please sign in to comment.