diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 910a800db07c3..f618010642819 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -48,7 +48,7 @@
2.1.7
1.2.8
2.0.0
- 5.1.0
+ 5.2.0
3.2.0
1.2.0
1.0.13
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java
index 18a9f9e103c7a..c79c8285ea7c4 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java
@@ -1,5 +1,6 @@
package io.quarkus.deployment.util;
+import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
@@ -375,4 +376,56 @@ public ClassNotIndexedException(DotName dotName) {
this.dotName = dotName;
}
}
+
+ public static Class> loadRawType(Type type) {
+ switch (type.kind()) {
+ case VOID:
+ return void.class;
+ case PRIMITIVE:
+ switch (type.asPrimitiveType().primitive()) {
+ case BOOLEAN:
+ return boolean.class;
+ case CHAR:
+ return char.class;
+ case BYTE:
+ return byte.class;
+ case SHORT:
+ return short.class;
+ case INT:
+ return int.class;
+ case LONG:
+ return long.class;
+ case FLOAT:
+ return float.class;
+ case DOUBLE:
+ return double.class;
+ default:
+ throw new IllegalArgumentException("Unknown primitive type: " + type);
+ }
+ case CLASS:
+ return load(type.asClassType().name());
+ case PARAMETERIZED_TYPE:
+ return load(type.asParameterizedType().name());
+ case ARRAY:
+ Class> component = loadRawType(type.asArrayType().component());
+ int dimensions = type.asArrayType().dimensions();
+ return Array.newInstance(component, new int[dimensions]).getClass();
+ case WILDCARD_TYPE:
+ return loadRawType(type.asWildcardType().extendsBound());
+ case TYPE_VARIABLE:
+ return load(type.asTypeVariable().name());
+ case UNRESOLVED_TYPE_VARIABLE:
+ return Object.class; // can't do better here
+ default:
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+ }
+
+ private static Class> load(DotName name) {
+ try {
+ return Thread.currentThread().getContextClassLoader().loadClass(name.toString());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
index e2b8a47b0130c..3b86dd1d18e84 100644
--- a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
@@ -2,13 +2,16 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.io.InputStream;
+import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
@@ -302,6 +305,72 @@ public static class MultiBoundedRepo implements Repo {
public static class ErasedRepo2 extends MultiBoundedRepo {
}
+ @Test
+ public void testLoadRawType() {
+ Index index = index(TestMethods.class);
+ ClassInfo clazz = index.getClassByName(DotName.createSimple(TestMethods.class.getName()));
+
+ assertEquals(void.class, JandexUtil.loadRawType(clazz.method("aaa").returnType()));
+ assertEquals(int.class, JandexUtil.loadRawType(clazz.method("bbb").returnType()));
+ assertEquals(String.class, JandexUtil.loadRawType(clazz.method("ccc").returnType()));
+ assertEquals(List.class, JandexUtil.loadRawType(clazz.method("ddd").returnType()));
+ assertEquals(String[][].class, JandexUtil.loadRawType(clazz.method("eee").returnType()));
+ assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("fff").returnType()));
+ assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("ggg").returnType()));
+ assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("hhh").returnType()));
+ assertEquals(Comparable.class, JandexUtil.loadRawType(clazz.method("iii").returnType()));
+ assertEquals(Comparable.class, JandexUtil.loadRawType(clazz.method("jjj").returnType()));
+ assertEquals(Serializable.class, JandexUtil.loadRawType(clazz.method("kkk").returnType()));
+ assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("lll").returnType()
+ .asParameterizedType().arguments().get(0)));
+ assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("mmm").returnType()
+ .asParameterizedType().arguments().get(0)));
+ assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("nnn").returnType()
+ .asParameterizedType().arguments().get(0)));
+ assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("ooo").returnType()
+ .asParameterizedType().arguments().get(0)));
+ assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("ppp").returnType()
+ .asParameterizedType().arguments().get(0)));
+ assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("qqq").returnType()
+ .asParameterizedType().arguments().get(0)));
+ }
+
+ public interface TestMethods {
+ void aaa();
+
+ int bbb();
+
+ String ccc();
+
+ List ddd();
+
+ String[][] eee();
+
+ X fff();
+
+ Y ggg();
+
+ > Y hhh();
+
+ > Y iii();
+
+ & Serializable> Y jjj();
+
+ > Y kkk();
+
+ List> lll();
+
+ List extends Number> mmm();
+
+ List super String> nnn();
+
+ List extends X> ooo();
+
+ List extends Y> ppp();
+
+ List super Y> qqq();
+ }
+
private static Index index(Class>... classes) {
Indexer indexer = new Indexer();
for (Class> clazz : classes) {
diff --git a/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc b/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc
index 0e89e240f5da1..9626a1cb467d4 100644
--- a/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc
+++ b/docs/src/main/asciidoc/smallrye-fault-tolerance.adoc
@@ -504,13 +504,13 @@ All that is needed to enable the fault tolerance features in Quarkus is:
== Additional resources
SmallRye Fault Tolerance has more features than shown here.
-Please check the link:https://smallrye.io/docs/smallrye-fault-tolerance/5.1.0/index.html[SmallRye Fault Tolerance documentation] to learn about them.
+Please check the link:https://smallrye.io/docs/smallrye-fault-tolerance/5.2.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.
-Support for Mutiny is present, so your `@Asynchronous` methods can return `Uni`.
+Support for Mutiny is present, so your asynchronous methods can return `Uni` in addition to `CompletionStage`.
-MicroProfile Context Propagation is integrated with Fault Tolerance, so existing contexts are automatically propagated to your `@Asynchronous` methods.
+MicroProfile Context Propagation is integrated with Fault Tolerance, so existing contexts are automatically propagated to your asynchronous methods.
[NOTE]
====
@@ -520,3 +520,16 @@ This is contrary to MicroProfile Fault Tolerance specification, which states tha
We believe that in presence of MicroProfile Context Propagation, this requirement should not apply.
The entire point of context propagation is to make sure the new thread has the same contexts as the original thread.
====
+
+Non-compatible mode is enabled by default, so methods that return `CompletionStage` (or `Uni`) have asynchronous fault tolerance applied without any `@Asynchronous`, `@Blocking` or `@NonBlocking` annotation.
+
+[NOTE]
+====
+This mode is not compatible with the MicroProfile Fault Tolerance specification, albeit the incompatibility is very small.
+To restore full compatibility, add this configuration property:
+
+[source,properties]
+----
+smallrye.faulttolerance.mp-compatibility=true
+----
+====
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/DotNames.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/DotNames.java
new file mode 100644
index 0000000000000..a5539f818e695
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/DotNames.java
@@ -0,0 +1,46 @@
+package io.quarkus.smallrye.faulttolerance.deployment;
+
+import java.util.Set;
+
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+import org.eclipse.microprofile.faulttolerance.Bulkhead;
+import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
+import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.faulttolerance.Retry;
+import org.eclipse.microprofile.faulttolerance.Timeout;
+import org.jboss.jandex.DotName;
+
+import io.smallrye.common.annotation.Blocking;
+import io.smallrye.common.annotation.NonBlocking;
+import io.smallrye.faulttolerance.api.CircuitBreakerName;
+import io.smallrye.faulttolerance.api.CustomBackoff;
+import io.smallrye.faulttolerance.api.CustomBackoffStrategy;
+import io.smallrye.faulttolerance.api.ExponentialBackoff;
+import io.smallrye.faulttolerance.api.FibonacciBackoff;
+
+public final class DotNames {
+ public static final DotName OBJECT = DotName.createSimple(Object.class.getName());
+
+ public static final DotName ASYNCHRONOUS = DotName.createSimple(Asynchronous.class.getName());
+ public static final DotName BULKHEAD = DotName.createSimple(Bulkhead.class.getName());
+ public static final DotName CIRCUIT_BREAKER = DotName.createSimple(CircuitBreaker.class.getName());
+ public static final DotName FALLBACK = DotName.createSimple(Fallback.class.getName());
+ public static final DotName RETRY = DotName.createSimple(Retry.class.getName());
+ public static final DotName TIMEOUT = DotName.createSimple(Timeout.class.getName());
+
+ // SmallRye annotations (@CircuitBreakerName, @[Non]Blocking, @*Backoff) alone do _not_ trigger
+ // the fault tolerance interceptor, only in combination with other fault tolerance annotations
+ public static final Set FT_ANNOTATIONS = Set.of(ASYNCHRONOUS, BULKHEAD, CIRCUIT_BREAKER, FALLBACK, RETRY, TIMEOUT);
+
+ public static final DotName BLOCKING = DotName.createSimple(Blocking.class.getName());
+ public static final DotName NON_BLOCKING = DotName.createSimple(NonBlocking.class.getName());
+
+ public static final DotName CIRCUIT_BREAKER_NAME = DotName.createSimple(CircuitBreakerName.class.getName());
+
+ public static final DotName EXPONENTIAL_BACKOFF = DotName.createSimple(ExponentialBackoff.class.getName());
+ public static final DotName FIBONACCI_BACKOFF = DotName.createSimple(FibonacciBackoff.class.getName());
+ public static final DotName CUSTOM_BACKOFF = DotName.createSimple(CustomBackoff.class.getName());
+ public static final DotName CUSTOM_BACKOFF_STRATEGY = DotName.createSimple(CustomBackoffStrategy.class.getName());
+
+ public static final Set BACKOFF_ANNOTATIONS = Set.of(EXPONENTIAL_BACKOFF, FIBONACCI_BACKOFF, CUSTOM_BACKOFF);
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java
new file mode 100644
index 0000000000000..e787009138a8e
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java
@@ -0,0 +1,181 @@
+package io.quarkus.smallrye.faulttolerance.deployment;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+import org.eclipse.microprofile.faulttolerance.Bulkhead;
+import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
+import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.faulttolerance.Retry;
+import org.eclipse.microprofile.faulttolerance.Timeout;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexView;
+import org.jboss.jandex.MethodInfo;
+
+import io.quarkus.arc.processor.AnnotationStore;
+import io.quarkus.deployment.builditem.AnnotationProxyBuildItem;
+import io.quarkus.deployment.util.JandexUtil;
+import io.quarkus.gizmo.ClassOutput;
+import io.smallrye.common.annotation.Blocking;
+import io.smallrye.common.annotation.NonBlocking;
+import io.smallrye.faulttolerance.api.CircuitBreakerName;
+import io.smallrye.faulttolerance.api.CustomBackoff;
+import io.smallrye.faulttolerance.api.ExponentialBackoff;
+import io.smallrye.faulttolerance.api.FibonacciBackoff;
+import io.smallrye.faulttolerance.autoconfig.FaultToleranceMethod;
+import io.smallrye.faulttolerance.autoconfig.MethodDescriptor;
+
+final class FaultToleranceScanner {
+ private final IndexView index;
+ private final AnnotationStore annotationStore;
+
+ private final AnnotationProxyBuildItem proxy;
+ private final ClassOutput output;
+
+ FaultToleranceScanner(IndexView index, AnnotationStore annotationStore, AnnotationProxyBuildItem proxy,
+ ClassOutput output) {
+ this.index = index;
+ this.annotationStore = annotationStore;
+ this.proxy = proxy;
+ this.output = output;
+ }
+
+ boolean hasFTAnnotations(ClassInfo clazz) {
+ // first check annotations on type
+ if (annotationStore.hasAnyAnnotation(clazz, DotNames.FT_ANNOTATIONS)) {
+ return true;
+ }
+
+ // then check on the methods
+ for (MethodInfo method : clazz.methods()) {
+ if (annotationStore.hasAnyAnnotation(method, DotNames.FT_ANNOTATIONS)) {
+ return true;
+ }
+ }
+
+ // then check on the parent
+ DotName parentClassName = clazz.superName();
+ if (parentClassName == null || parentClassName.equals(DotNames.OBJECT)) {
+ return false;
+ }
+ ClassInfo parentClass = index.getClassByName(parentClassName);
+ if (parentClass == null) {
+ return false;
+ }
+ return hasFTAnnotations(parentClass);
+ }
+
+ void forEachMethod(ClassInfo clazz, Consumer action) {
+ for (MethodInfo method : clazz.methods()) {
+ if (method.name().startsWith("<")) {
+ // constructors (or static init blocks) can't be intercepted
+ continue;
+ }
+ if (method.isSynthetic()) {
+ // synthetic methods can't be intercepted
+ continue;
+ }
+
+ action.accept(method);
+ }
+
+ DotName parentClassName = clazz.superName();
+ if (parentClassName == null || parentClassName.equals(DotNames.OBJECT)) {
+ return;
+ }
+ ClassInfo parentClass = index.getClassByName(parentClassName);
+ if (parentClass == null) {
+ return;
+ }
+ forEachMethod(parentClass, action);
+ }
+
+ FaultToleranceMethod createFaultToleranceMethod(ClassInfo beanClass, MethodInfo method) {
+ Set> annotationsPresentDirectly = new HashSet<>();
+
+ FaultToleranceMethod result = new FaultToleranceMethod();
+
+ result.beanClass = load(beanClass.name());
+ result.method = createMethodDescriptor(method);
+
+ result.asynchronous = getAnnotation(Asynchronous.class, method, beanClass, annotationsPresentDirectly);
+ result.bulkhead = getAnnotation(Bulkhead.class, method, beanClass, annotationsPresentDirectly);
+ result.circuitBreaker = getAnnotation(CircuitBreaker.class, method, beanClass, annotationsPresentDirectly);
+ result.fallback = getAnnotation(Fallback.class, method, beanClass, annotationsPresentDirectly);
+ result.retry = getAnnotation(Retry.class, method, beanClass, annotationsPresentDirectly);
+ result.timeout = getAnnotation(Timeout.class, method, beanClass, annotationsPresentDirectly);
+
+ result.circuitBreakerName = getAnnotation(CircuitBreakerName.class, method, beanClass, annotationsPresentDirectly);
+ 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.blocking = getAnnotation(Blocking.class, method, beanClass, annotationsPresentDirectly);
+ result.nonBlocking = getAnnotation(NonBlocking.class, method, beanClass, annotationsPresentDirectly);
+
+ result.annotationsPresentDirectly = annotationsPresentDirectly;
+
+ return result;
+ }
+
+ private MethodDescriptor createMethodDescriptor(MethodInfo method) {
+ MethodDescriptor result = new MethodDescriptor();
+ result.declaringClass = load(method.declaringClass().name());
+ result.name = method.name();
+ result.parameterTypes = method.parameters()
+ .stream()
+ .map(JandexUtil::loadRawType)
+ .toArray(Class[]::new);
+ result.returnType = JandexUtil.loadRawType(method.returnType());
+ return result;
+ }
+
+ private A getAnnotation(Class annotationType, MethodInfo method,
+ ClassInfo beanClass, Set> directlyPresent) {
+
+ DotName annotationName = DotName.createSimple(annotationType.getName());
+ if (annotationStore.hasAnnotation(method, annotationName)) {
+ directlyPresent.add(annotationType);
+ AnnotationInstance annotation = annotationStore.getAnnotation(method, annotationName);
+ return createAnnotation(annotationType, annotation);
+ }
+
+ return getAnnotationFromClass(annotationType, beanClass);
+ }
+
+ private A getAnnotationFromClass(Class annotationType, ClassInfo clazz) {
+ DotName annotationName = DotName.createSimple(annotationType.getName());
+ if (annotationStore.hasAnnotation(clazz, annotationName)) {
+ AnnotationInstance annotation = annotationStore.getAnnotation(clazz, annotationName);
+ return createAnnotation(annotationType, annotation);
+ }
+
+ // then check on the parent
+ DotName parentClassName = clazz.superName();
+ if (parentClassName == null || parentClassName.equals(DotNames.OBJECT)) {
+ return null;
+ }
+ ClassInfo parentClass = index.getClassByName(parentClassName);
+ if (parentClass == null) {
+ return null;
+ }
+ return getAnnotationFromClass(annotationType, parentClass);
+ }
+
+ private A createAnnotation(Class annotationType, AnnotationInstance instance) {
+ return proxy.builder(instance, annotationType).build(output);
+ }
+
+ private static Class> load(DotName name) {
+ try {
+ return Thread.currentThread().getContextClassLoader().loadClass(name.toString());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java
index bdb9d6a90a53b..0f8b969f90ccc 100644
--- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java
+++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java
@@ -17,13 +17,7 @@
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
-import org.eclipse.microprofile.faulttolerance.Asynchronous;
-import org.eclipse.microprofile.faulttolerance.Bulkhead;
-import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
-import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.FallbackHandler;
-import org.eclipse.microprofile.faulttolerance.Retry;
-import org.eclipse.microprofile.faulttolerance.Timeout;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationTarget.Kind;
@@ -43,20 +37,24 @@
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.BuiltinScope;
-import io.quarkus.arc.processor.DotNames;
import io.quarkus.deployment.Feature;
+import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.AnnotationProxyBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
+import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
+import io.quarkus.gizmo.ClassOutput;
import io.quarkus.smallrye.faulttolerance.runtime.QuarkusAsyncExecutorProvider;
import io.quarkus.smallrye.faulttolerance.runtime.QuarkusExistingCircuitBreakerNames;
import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFallbackHandlerProvider;
@@ -67,7 +65,8 @@
import io.smallrye.faulttolerance.FaultToleranceBinding;
import io.smallrye.faulttolerance.FaultToleranceInterceptor;
import io.smallrye.faulttolerance.RequestContextIntegration;
-import io.smallrye.faulttolerance.api.CircuitBreakerName;
+import io.smallrye.faulttolerance.SpecCompatibility;
+import io.smallrye.faulttolerance.autoconfig.FaultToleranceMethod;
import io.smallrye.faulttolerance.core.util.RunnableWrapper;
import io.smallrye.faulttolerance.internal.RequestContextControllerProvider;
import io.smallrye.faulttolerance.internal.StrategyCache;
@@ -77,18 +76,6 @@
public class SmallRyeFaultToleranceProcessor {
- private static final Set FT_ANNOTATIONS = new HashSet<>();
- static {
- // @Blocking and @NonBlocking alone do _not_ trigger the fault tolerance interceptor,
- // only in combination with other fault tolerance annotations
- FT_ANNOTATIONS.add(DotName.createSimple(Asynchronous.class.getName()));
- FT_ANNOTATIONS.add(DotName.createSimple(Bulkhead.class.getName()));
- FT_ANNOTATIONS.add(DotName.createSimple(CircuitBreaker.class.getName()));
- FT_ANNOTATIONS.add(DotName.createSimple(Fallback.class.getName()));
- FT_ANNOTATIONS.add(DotName.createSimple(Retry.class.getName()));
- FT_ANNOTATIONS.add(DotName.createSimple(Timeout.class.getName()));
- }
-
@BuildStep
public void build(BuildProducer annotationsTransformer,
BuildProducer feature, BuildProducer additionalBean,
@@ -98,7 +85,8 @@ public void build(BuildProducer annotationsTran
BuildProducer systemProperty,
CombinedIndexBuildItem combinedIndexBuildItem,
BuildProducer reflectiveClass,
- BuildProducer reflectiveMethod) {
+ BuildProducer reflectiveMethod,
+ BuildProducer config) {
feature.produce(new FeatureBuildItem(Feature.SMALLRYE_FAULT_TOLERANCE));
@@ -125,7 +113,7 @@ public void build(BuildProducer annotationsTran
additionalBean.produce(fallbackHandlersBeans.build());
}
// Add reflective access to fallback methods
- for (AnnotationInstance annotation : index.getAnnotations(DotName.createSimple(Fallback.class.getName()))) {
+ for (AnnotationInstance annotation : index.getAnnotations(DotNames.FALLBACK)) {
AnnotationValue fallbackMethodValue = annotation.value("fallbackMethod");
if (fallbackMethodValue == null) {
continue;
@@ -161,8 +149,12 @@ public void build(BuildProducer annotationsTran
classesToScan.addAll(clazz.interfaceNames());
}
}
+ // Add reflective access to custom backoff strategies
+ for (ClassInfo strategy : index.getAllKnownImplementors(DotNames.CUSTOM_BACKOFF_STRATEGY)) {
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, strategy.name().toString()));
+ }
- for (DotName annotation : FT_ANNOTATIONS) {
+ for (DotName annotation : DotNames.FT_ANNOTATIONS) {
reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, annotation.toString()));
// also make them bean defining annotations
additionalBda.produce(new BeanDefiningAnnotationBuildItem(annotation));
@@ -177,7 +169,7 @@ public boolean appliesTo(Kind kind) {
@Override
public void transform(TransformationContext context) {
- if (FT_ANNOTATIONS.contains(context.getTarget().asClass().name())) {
+ if (DotNames.FT_ANNOTATIONS.contains(context.getTarget().asClass().name())) {
context.transform().add(FaultToleranceBinding.class).done();
}
}
@@ -187,7 +179,7 @@ public void transform(TransformationContext context) {
AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder();
// Also register MP FT annotations so that they are recognized as interceptor bindings
// Note that MP FT API jar is nor indexed, nor contains beans.xml so it is not part of the app index
- for (DotName ftAnnotation : FT_ANNOTATIONS) {
+ for (DotName ftAnnotation : DotNames.FT_ANNOTATIONS) {
builder.addBeanClass(ftAnnotation.toString());
}
builder.addBeanClasses(FaultToleranceInterceptor.class,
@@ -199,13 +191,16 @@ public void transform(TransformationContext context) {
QuarkusAsyncExecutorProvider.class,
MetricsProvider.class,
CircuitBreakerMaintenanceImpl.class,
- RequestContextIntegration.class);
+ RequestContextIntegration.class,
+ SpecCompatibility.class);
additionalBean.produce(builder.build());
if (!metricsCapability.isPresent()) {
//disable fault tolerance metrics with the MP sys props
systemProperty.produce(new SystemPropertyBuildItem("MP_Fault_Tolerance_Metrics_Enabled", "false"));
}
+
+ config.produce(new RunTimeConfigurationDefaultBuildItem("smallrye.faulttolerance.mp-compatibility", "false"));
}
@BuildStep
@@ -243,73 +238,81 @@ public void transform(TransformationContext ctx) {
void validateFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder,
ValidationPhaseBuildItem validationPhase,
BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
+ AnnotationProxyBuildItem annotationProxy,
+ BuildProducer generatedClasses,
BuildProducer errors) {
+
AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE);
- Set beanNames = new HashSet<>();
IndexView index = beanArchiveIndexBuildItem.getIndex();
+ // only generating annotation literal classes for MicroProfile/SmallRye Fault Tolerance annotations,
+ // none of them are application classes
+ ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, false);
+
+ FaultToleranceScanner scaner = new FaultToleranceScanner(index, annotationStore, annotationProxy, classOutput);
+
+ List ftMethods = new ArrayList<>();
+ List exceptions = new ArrayList<>();
for (BeanInfo info : validationPhase.getContext().beans()) {
- if (hasFTAnnotations(index, annotationStore, info.getImplClazz())) {
- beanNames.add(info.getBeanClass().toString());
+ ClassInfo beanClass = info.getImplClazz();
+ if (beanClass == null) {
+ continue;
}
- }
- recorder.createFaultToleranceOperation(beanNames);
+ if (scaner.hasFTAnnotations(beanClass)) {
+ scaner.forEachMethod(beanClass, method -> {
+ FaultToleranceMethod ftMethod = scaner.createFaultToleranceMethod(beanClass, method);
+ if (ftMethod.isLegitimate()) {
+ ftMethods.add(ftMethod);
+
+ if (method.hasAnnotation(DotNames.BLOCKING) && method.hasAnnotation(DotNames.NON_BLOCKING)) {
+ exceptions.add(
+ new DefinitionException("Both @Blocking and @NonBlocking present on '" + method + "'"));
+ }
+ }
+ });
+
+ if (beanClass.classAnnotation(DotNames.BLOCKING) != null
+ && beanClass.classAnnotation(DotNames.NON_BLOCKING) != null) {
+ exceptions.add(new DefinitionException("Both @Blocking and @NonBlocking present on '" + beanClass + "'"));
+ }
+ }
+ }
- DotName circuitBreakerName = DotName.createSimple(CircuitBreakerName.class.getName());
+ recorder.createFaultToleranceOperation(ftMethods);
Map> existingCircuitBreakerNames = new HashMap<>();
- for (AnnotationInstance it : index.getAnnotations(circuitBreakerName)) {
+ for (AnnotationInstance it : index.getAnnotations(DotNames.CIRCUIT_BREAKER_NAME)) {
if (it.target().kind() == Kind.METHOD) {
MethodInfo method = it.target().asMethod();
existingCircuitBreakerNames.computeIfAbsent(it.value().asString(), ignored -> new HashSet<>())
.add(method + " @ " + method.declaringClass());
}
}
-
- List exceptions = new ArrayList<>();
for (Map.Entry> entry : existingCircuitBreakerNames.entrySet()) {
if (entry.getValue().size() > 1) {
exceptions.add(new DefinitionException("Multiple circuit breakers have the same name '"
+ entry.getKey() + "': " + entry.getValue()));
}
}
- if (!exceptions.isEmpty()) {
- errors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(exceptions));
- }
- recorder.initExistingCircuitBreakerNames(existingCircuitBreakerNames.keySet());
- }
-
- private boolean hasFTAnnotations(IndexView index, AnnotationStore annotationStore, ClassInfo info) {
- if (info == null) {
- //should not happen, but guard against it
- //happens in this case due to a bug involving array types
-
- return false;
- }
- // first check annotations on type
- if (annotationStore.hasAnyAnnotation(info, FT_ANNOTATIONS)) {
- return true;
- }
-
- // then check on the methods
- for (MethodInfo method : info.methods()) {
- if (annotationStore.hasAnyAnnotation(method, FT_ANNOTATIONS)) {
- return true;
+ for (DotName backoffAnnotation : DotNames.BACKOFF_ANNOTATIONS) {
+ for (AnnotationInstance it : index.getAnnotations(backoffAnnotation)) {
+ if (it.target().kind() == Kind.CLASS && it.target().asClass().classAnnotation(DotNames.RETRY) == null) {
+ exceptions.add(new DefinitionException("Backoff annotation @" + backoffAnnotation.withoutPackagePrefix()
+ + " present on '" + it.target() + "', but @Retry is missing"));
+ } else if (it.target().kind() == Kind.METHOD && !it.target().asMethod().hasAnnotation(DotNames.RETRY)) {
+ exceptions.add(new DefinitionException("Backoff annotation @" + backoffAnnotation.withoutPackagePrefix()
+ + " present on '" + it.target() + "', but @Retry is missing"));
+ }
}
}
- // then check on the parent
- DotName parentClassName = info.superName();
- if (parentClassName == null || parentClassName.equals(DotNames.OBJECT)) {
- return false;
- }
- ClassInfo parentClassInfo = index.getClassByName(parentClassName);
- if (parentClassInfo == null) {
- return false;
+ if (!exceptions.isEmpty()) {
+ errors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(exceptions));
}
- return hasFTAnnotations(index, annotationStore, parentClassInfo);
+
+ recorder.initExistingCircuitBreakerNames(existingCircuitBreakerNames.keySet());
}
@BuildStep
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnClassService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnClassService.java
new file mode 100644
index 0000000000000..d4c7c60cc709a
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnClassService.java
@@ -0,0 +1,20 @@
+package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;
+
+import java.util.concurrent.CompletionStage;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+import io.smallrye.common.annotation.Blocking;
+import io.smallrye.common.annotation.NonBlocking;
+
+@ApplicationScoped
+@Blocking
+@NonBlocking
+public class BlockingNonBlockingOnClassService {
+ @Retry
+ public CompletionStage hello() {
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnClassTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnClassTest.java
new file mode 100644
index 0000000000000..9904ec64ce31c
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnClassTest.java
@@ -0,0 +1,30 @@
+package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class BlockingNonBlockingOnClassTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(BlockingNonBlockingOnClassService.class))
+ .assertException(e -> {
+ assertEquals(DefinitionException.class, e.getClass());
+ assertTrue(e.getMessage().contains("Both @Blocking and @NonBlocking present"));
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnMethodService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnMethodService.java
new file mode 100644
index 0000000000000..fe945d873b067
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnMethodService.java
@@ -0,0 +1,20 @@
+package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;
+
+import java.util.concurrent.CompletionStage;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+import io.smallrye.common.annotation.Blocking;
+import io.smallrye.common.annotation.NonBlocking;
+
+@ApplicationScoped
+public class BlockingNonBlockingOnMethodService {
+ @Retry
+ @Blocking
+ @NonBlocking
+ public CompletionStage hello() {
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnMethodTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnMethodTest.java
new file mode 100644
index 0000000000000..383c523220138
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/additional/BlockingNonBlockingOnMethodTest.java
@@ -0,0 +1,30 @@
+package io.quarkus.smallrye.faulttolerance.test.asynchronous.additional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class BlockingNonBlockingOnMethodTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(BlockingNonBlockingOnMethodService.class))
+ .assertException(e -> {
+ assertEquals(DefinitionException.class, e.getClass());
+ assertTrue(e.getMessage().contains("Both @Blocking and @NonBlocking present"));
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/noncompat/NoncompatAsyncTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/noncompat/NoncompatAsyncTest.java
new file mode 100644
index 0000000000000..1d44a741f056f
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/noncompat/NoncompatAsyncTest.java
@@ -0,0 +1,47 @@
+package io.quarkus.smallrye.faulttolerance.test.asynchronous.noncompat;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.concurrent.CompletionStage;
+
+import javax.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class NoncompatAsyncTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(NoncompatHelloService.class));
+
+ @Inject
+ NoncompatHelloService service;
+
+ @Test
+ public void noThreadOffloadAndFallback() throws Exception {
+ Thread mainThread = Thread.currentThread();
+
+ CompletionStage future = service.hello();
+ assertThat(future.toCompletableFuture().get()).isEqualTo("hello");
+
+ // no delay between retries, all executions happen on the same thread
+ // if there _was_ a delay, subsequent retries would be offloaded to another thread
+ assertThat(service.getHelloThreads()).allSatisfy(thread -> {
+ assertThat(thread).isSameAs(mainThread);
+ });
+ assertThat(service.getHelloStackTraces()).allSatisfy(stackTrace -> {
+ assertThat(stackTrace).anySatisfy(frame -> {
+ assertThat(frame.getClassName()).contains("io.smallrye.faulttolerance.core");
+ });
+ });
+
+ // 1 initial execution + 3 retries
+ assertThat(service.getInvocationCounter()).hasValue(4);
+
+ assertThat(service.getFallbackThread()).isSameAs(mainThread);
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/noncompat/NoncompatHelloService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/noncompat/NoncompatHelloService.java
new file mode 100644
index 0000000000000..264fa6fd4f375
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/noncompat/NoncompatHelloService.java
@@ -0,0 +1,54 @@
+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 java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+@ApplicationScoped
+@Retry(maxRetries = 3, delay = 0, jitter = 0)
+public class NoncompatHelloService {
+ private final List helloThreads = new CopyOnWriteArrayList<>();
+ private final List helloStackTraces = new CopyOnWriteArrayList<>();
+
+ private final AtomicInteger invocationCounter = new AtomicInteger();
+
+ private volatile Thread fallbackThread;
+
+ @Fallback(fallbackMethod = "fallback")
+ public CompletionStage hello() {
+ invocationCounter.incrementAndGet();
+ helloThreads.add(Thread.currentThread());
+ helloStackTraces.add(new Throwable().getStackTrace());
+ return failedFuture(new RuntimeException());
+ }
+
+ public CompletionStage fallback() {
+ fallbackThread = Thread.currentThread();
+ return completedFuture("hello");
+ }
+
+ List getHelloThreads() {
+ return helloThreads;
+ }
+
+ List getHelloStackTraces() {
+ return helloStackTraces;
+ }
+
+ AtomicInteger getInvocationCounter() {
+ return invocationCounter;
+ }
+
+ Thread getFallbackThread() {
+ return fallbackThread;
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/ClassAndMethodBackoffService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/ClassAndMethodBackoffService.java
new file mode 100644
index 0000000000000..42d90507219e7
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/ClassAndMethodBackoffService.java
@@ -0,0 +1,18 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+import io.smallrye.faulttolerance.api.ExponentialBackoff;
+import io.smallrye.faulttolerance.api.FibonacciBackoff;
+
+@ApplicationScoped
+@Retry
+@ExponentialBackoff
+public class ClassAndMethodBackoffService {
+ @FibonacciBackoff
+ public void hello() {
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/ClassAndMethodBackoffTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/ClassAndMethodBackoffTest.java
new file mode 100644
index 0000000000000..0f3a6bc35582f
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/ClassAndMethodBackoffTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ClassAndMethodBackoffTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(ClassAndMethodBackoffService.class))
+ .assertException(e -> {
+ assertEquals(DefinitionException.class, e.getClass());
+ assertTrue(e.getMessage().contains("Backoff annotation"));
+ assertTrue(e.getMessage().contains("@Retry is missing"));
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnClassBackoffOnMethodService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnClassBackoffOnMethodService.java
new file mode 100644
index 0000000000000..15c795ff0bcd7
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnClassBackoffOnMethodService.java
@@ -0,0 +1,16 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+import io.smallrye.faulttolerance.api.ExponentialBackoff;
+
+@ApplicationScoped
+@Retry
+public class RetryOnClassBackoffOnMethodService {
+ @ExponentialBackoff
+ public void hello() {
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnClassBackoffOnMethodTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnClassBackoffOnMethodTest.java
new file mode 100644
index 0000000000000..1d20c9acadd3c
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnClassBackoffOnMethodTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class RetryOnClassBackoffOnMethodTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(RetryOnClassBackoffOnMethodService.class))
+ .assertException(e -> {
+ assertEquals(DefinitionException.class, e.getClass());
+ assertTrue(e.getMessage().contains("Backoff annotation"));
+ assertTrue(e.getMessage().contains("@Retry is missing"));
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnMethodBackoffOnClassService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnMethodBackoffOnClassService.java
new file mode 100644
index 0000000000000..fbcd7093d7746
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnMethodBackoffOnClassService.java
@@ -0,0 +1,16 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+import io.smallrye.faulttolerance.api.FibonacciBackoff;
+
+@ApplicationScoped
+@FibonacciBackoff
+public class RetryOnMethodBackoffOnClassService {
+ @Retry
+ public void hello() {
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnMethodBackoffOnClassTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnMethodBackoffOnClassTest.java
new file mode 100644
index 0000000000000..135991aa5b26a
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/RetryOnMethodBackoffOnClassTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class RetryOnMethodBackoffOnClassTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(RetryOnMethodBackoffOnClassService.class))
+ .assertException(e -> {
+ assertEquals(DefinitionException.class, e.getClass());
+ assertTrue(e.getMessage().contains("Backoff annotation"));
+ assertTrue(e.getMessage().contains("@Retry is missing"));
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/TwoBackoffsOnMethodService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/TwoBackoffsOnMethodService.java
new file mode 100644
index 0000000000000..45a58027ba6b4
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/TwoBackoffsOnMethodService.java
@@ -0,0 +1,18 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.faulttolerance.Retry;
+
+import io.smallrye.faulttolerance.api.ExponentialBackoff;
+import io.smallrye.faulttolerance.api.FibonacciBackoff;
+
+@ApplicationScoped
+public class TwoBackoffsOnMethodService {
+ @Retry
+ @ExponentialBackoff
+ @FibonacciBackoff
+ public void hello() {
+ throw new IllegalArgumentException();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/TwoBackoffsOnMethodTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/TwoBackoffsOnMethodTest.java
new file mode 100644
index 0000000000000..4c6e9cdb39f96
--- /dev/null
+++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/backoff/TwoBackoffsOnMethodTest.java
@@ -0,0 +1,30 @@
+package io.quarkus.smallrye.faulttolerance.test.retry.backoff;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import javax.enterprise.inject.spi.DeploymentException;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class TwoBackoffsOnMethodTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(TwoBackoffsOnMethodService.class))
+ .assertException(e -> {
+ assertEquals(DeploymentException.class, e.getClass());
+ assertTrue(e.getMessage().contains("More than one backoff defined"));
+ });
+
+ @Test
+ public void test() {
+ fail();
+ }
+}
diff --git a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java
index 319f309f4ddb0..f807114ab8756 100644
--- a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java
+++ b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java
@@ -11,7 +11,6 @@
import org.eclipse.microprofile.faulttolerance.FallbackHandler;
import io.smallrye.faulttolerance.FallbackHandlerProvider;
-import io.smallrye.faulttolerance.config.FallbackConfig;
import io.smallrye.faulttolerance.config.FaultToleranceOperation;
@Dependent
@@ -30,7 +29,7 @@ public FallbackHandler get(FaultToleranceOperation operation) {
@SuppressWarnings("unchecked")
@Override
public T handle(ExecutionContext context) {
- Class extends FallbackHandler>> clazz = operation.getFallback().get(FallbackConfig.VALUE);
+ Class extends FallbackHandler>> clazz = operation.getFallback().value();
FallbackHandler fallbackHandlerInstance = (FallbackHandler) instance.select(clazz).get();
try {
return fallbackHandlerInstance.handle(context);
diff --git a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFaultToleranceOperationProvider.java b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFaultToleranceOperationProvider.java
index ca6effaa750de..85b41aa7b784c 100644
--- a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFaultToleranceOperationProvider.java
+++ b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFaultToleranceOperationProvider.java
@@ -13,6 +13,7 @@
import org.jboss.logging.Logger;
import io.smallrye.faulttolerance.FaultToleranceOperationProvider;
+import io.smallrye.faulttolerance.config.FaultToleranceMethods;
import io.smallrye.faulttolerance.config.FaultToleranceOperation;
@Singleton
@@ -45,7 +46,7 @@ public FaultToleranceOperation get(Class> beanClass, Method method) {
private FaultToleranceOperation createAtRuntime(CacheKey key) {
LOG.debugf("FaultToleranceOperation not found in the cache for %s creating it at runtime", key);
- return FaultToleranceOperation.of(key.beanClass, key.method);
+ return FaultToleranceOperation.create(FaultToleranceMethods.create(key.beanClass, key.method));
}
static class CacheKey {
diff --git a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/SmallRyeFaultToleranceRecorder.java b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/SmallRyeFaultToleranceRecorder.java
index c6730f50f61a3..f1443fb32d4cf 100644
--- a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/SmallRyeFaultToleranceRecorder.java
+++ b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/SmallRyeFaultToleranceRecorder.java
@@ -1,9 +1,7 @@
package io.quarkus.smallrye.faulttolerance.runtime;
-import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -14,42 +12,29 @@
import io.quarkus.arc.Arc;
import io.quarkus.runtime.annotations.Recorder;
+import io.smallrye.faulttolerance.autoconfig.FaultToleranceMethod;
import io.smallrye.faulttolerance.config.FaultToleranceOperation;
@Recorder
public class SmallRyeFaultToleranceRecorder {
- public void createFaultToleranceOperation(Set beanNames) {
+ public void createFaultToleranceOperation(List ftMethods) {
List allExceptions = new ArrayList<>();
Map operationCache = new HashMap<>(
- beanNames.size());
- for (String beanName : beanNames) {
+ ftMethods.size());
+ for (FaultToleranceMethod ftMethod : ftMethods) {
+ FaultToleranceOperation operation = FaultToleranceOperation.create(ftMethod);
try {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- if (classLoader == null) {
- classLoader = SmallRyeFaultToleranceRecorder.class.getClassLoader();
- }
- Class> beanClass = Class.forName(beanName, true, classLoader);
-
- for (Method method : getAllMethods(beanClass)) {
- FaultToleranceOperation operation = FaultToleranceOperation.of(beanClass, method);
- if (operation.isLegitimate()) {
- try {
- operation.validate();
+ operation.validate();
- // register the operation at validation time to avoid re-creating it at runtime
- QuarkusFaultToleranceOperationProvider.CacheKey cacheKey = new QuarkusFaultToleranceOperationProvider.CacheKey(
- beanClass, method);
- operationCache.put(cacheKey, operation);
- } catch (FaultToleranceDefinitionException e) {
- allExceptions.add(e);
- }
- }
- }
- } catch (ClassNotFoundException e) {
- // Ignore
+ QuarkusFaultToleranceOperationProvider.CacheKey cacheKey = new QuarkusFaultToleranceOperationProvider.CacheKey(
+ ftMethod.beanClass, ftMethod.method.reflect());
+ operationCache.put(cacheKey, operation);
+ } catch (FaultToleranceDefinitionException | NoSuchMethodException e) {
+ allExceptions.add(e);
}
}
+
if (!allExceptions.isEmpty()) {
if (allExceptions.size() == 1) {
Throwable error = allExceptions.get(0);
@@ -71,19 +56,8 @@ public void createFaultToleranceOperation(Set beanNames) {
throw deploymentException;
}
}
- Arc.container().instance(QuarkusFaultToleranceOperationProvider.class).get().init(operationCache);
- }
- private Set getAllMethods(Class> beanClass) {
- Set allMethods = new HashSet<>();
- Class> currentClass = beanClass;
- while (currentClass != null && !currentClass.equals(Object.class)) {
- for (Method m : currentClass.getDeclaredMethods()) {
- allMethods.add(m);
- }
- currentClass = currentClass.getSuperclass(); // this will be null for interfaces
- }
- return allMethods;
+ Arc.container().instance(QuarkusFaultToleranceOperationProvider.class).get().init(operationCache);
}
public void initExistingCircuitBreakerNames(Set names) {
diff --git a/tcks/microprofile-fault-tolerance/pom.xml b/tcks/microprofile-fault-tolerance/pom.xml
index 9d954b32e59f6..57109ab08dfcf 100644
--- a/tcks/microprofile-fault-tolerance/pom.xml
+++ b/tcks/microprofile-fault-tolerance/pom.xml
@@ -21,6 +21,7 @@
false
+ true
@@ -32,6 +33,9 @@
org.eclipse.microprofile.fault.tolerance.tck.interceptor.xmlInterceptorEnabling.FaultToleranceInterceptorEnableByXmlTest
+
+ org.eclipse.microprofile.fault.tolerance.tck.invalidParameters.InvalidCircuitBreakerFailureRatioNegTest
+ org.eclipse.microprofile.fault.tolerance.tck.invalidParameters.InvalidCircuitBreakerFailureRatioPosTest